mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge branch 'rewrite/master' of github.com:FunkinCrew/Funkin-secret into feature/chart-waveform
This commit is contained in:
commit
88409df992
23 changed files with 868 additions and 212 deletions
2
hmm.json
2
hmm.json
|
@ -49,7 +49,7 @@
|
||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "91ed8d7867c52af5ea2a9513204057d69ab33c8e",
|
"ref": "5d4ac180f85b39e72624f4b8d17925d91ebe4278",
|
||||||
"url": "https://github.com/haxeui/haxeui-core"
|
"url": "https://github.com/haxeui/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,8 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public static var songPosition(default, null):Float = 0;
|
public static var songPosition(default, null):Float = 0;
|
||||||
|
|
||||||
|
public static var songPositionNoOffset(default, null):Float = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beats per minute of the current song at the current time.
|
* Beats per minute of the current song at the current time.
|
||||||
*/
|
*/
|
||||||
|
@ -144,13 +146,27 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public static var currentStepTime(default, null):Float;
|
public static var currentStepTime(default, null):Float;
|
||||||
|
|
||||||
|
public static var currentStepTimeNoOffset(default, null):Float;
|
||||||
|
|
||||||
public static var beatHit(default, null):FlxSignal = new FlxSignal();
|
public static var beatHit(default, null):FlxSignal = new FlxSignal();
|
||||||
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
public static var lastSongPos:Float;
|
public static var lastSongPos:Float;
|
||||||
public static var visualOffset:Float = 0;
|
|
||||||
public static var audioOffset:Float = 0;
|
/**
|
||||||
public static var offset:Float = 0;
|
* An offset tied to the current chart file to compensate for a delay in the instrumental.
|
||||||
|
*/
|
||||||
|
public static var instrumentalOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An offset tied to the file format of the audio file being played.
|
||||||
|
*/
|
||||||
|
public static var formatOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An offset set by the user to compensate for input lag.
|
||||||
|
*/
|
||||||
|
public static var inputOffset:Float = 0;
|
||||||
|
|
||||||
public static var beatsPerMeasure(get, never):Float;
|
public static var beatsPerMeasure(get, never):Float;
|
||||||
|
|
||||||
|
@ -200,15 +216,24 @@ class Conductor
|
||||||
* @param songPosition The current position in the song in milliseconds.
|
* @param songPosition The current position in the song in milliseconds.
|
||||||
* Leave blank to use the FlxG.sound.music position.
|
* Leave blank to use the FlxG.sound.music position.
|
||||||
*/
|
*/
|
||||||
public static function update(songPosition:Float = null)
|
public static function update(?songPosition:Float)
|
||||||
{
|
{
|
||||||
if (songPosition == null) songPosition = (FlxG.sound.music != null) ? FlxG.sound.music.time + Conductor.offset : 0.0;
|
if (songPosition == null)
|
||||||
|
{
|
||||||
|
// Take into account instrumental and file format song offsets.
|
||||||
|
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
var oldBeat = currentBeat;
|
var oldBeat = currentBeat;
|
||||||
var oldStep = currentStep;
|
var oldStep = currentStep;
|
||||||
|
|
||||||
|
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||||
Conductor.songPosition = songPosition;
|
Conductor.songPosition = songPosition;
|
||||||
|
|
||||||
|
// Exclude the offsets we just included earlier.
|
||||||
|
// This is the "true" song position that the audio should be using.
|
||||||
|
Conductor.songPositionNoOffset = Conductor.songPosition - instrumentalOffset - formatOffset;
|
||||||
|
|
||||||
currentTimeChange = timeChanges[0];
|
currentTimeChange = timeChanges[0];
|
||||||
for (i in 0...timeChanges.length)
|
for (i in 0...timeChanges.length)
|
||||||
{
|
{
|
||||||
|
@ -230,6 +255,9 @@ class Conductor
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentBeatTime);
|
currentBeat = Math.floor(currentBeatTime);
|
||||||
currentMeasure = Math.floor(currentMeasureTime);
|
currentMeasure = Math.floor(currentMeasureTime);
|
||||||
|
|
||||||
|
currentStepTimeNoOffset = FlxMath.roundDecimal((currentTimeChange.beatTime * 4)
|
||||||
|
+ (songPositionNoOffset - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -240,6 +268,8 @@ class Conductor
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentBeatTime);
|
currentBeat = Math.floor(currentBeatTime);
|
||||||
currentMeasure = Math.floor(currentMeasureTime);
|
currentMeasure = Math.floor(currentMeasureTime);
|
||||||
|
|
||||||
|
currentStepTimeNoOffset = FlxMath.roundDecimal((songPositionNoOffset / stepLengthMs), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlxSignals are really cool.
|
// FlxSignals are really cool.
|
||||||
|
|
|
@ -11,12 +11,22 @@ class VoicesGroup extends SoundGroup
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the player group.
|
* Control the volume of only the sounds in the player group.
|
||||||
*/
|
*/
|
||||||
public var playerVolume(default, set):Float;
|
public var playerVolume(default, set):Float = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the opponent group.
|
* Control the volume of only the sounds in the opponent group.
|
||||||
*/
|
*/
|
||||||
public var opponentVolume(default, set):Float;
|
public var opponentVolume(default, set):Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time offset for the player's vocal track.
|
||||||
|
*/
|
||||||
|
public var playerVoicesOffset(default, set):Float = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time offset for the opponent's vocal track.
|
||||||
|
*/
|
||||||
|
public var opponentVoicesOffset(default, set):Float = 0.0;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
|
@ -42,6 +52,57 @@ class VoicesGroup extends SoundGroup
|
||||||
return playerVolume = volume;
|
return playerVolume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function set_time(time:Float):Float
|
||||||
|
{
|
||||||
|
forEachAlive(function(snd) {
|
||||||
|
// account for different offsets per sound?
|
||||||
|
snd.time = time;
|
||||||
|
});
|
||||||
|
|
||||||
|
playerVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time -= playerVoicesOffset;
|
||||||
|
});
|
||||||
|
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time -= opponentVoicesOffset;
|
||||||
|
});
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_playerVoicesOffset(offset:Float):Float
|
||||||
|
{
|
||||||
|
playerVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time += playerVoicesOffset;
|
||||||
|
voice.time -= offset;
|
||||||
|
});
|
||||||
|
return playerVoicesOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_opponentVoicesOffset(offset:Float):Float
|
||||||
|
{
|
||||||
|
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time += opponentVoicesOffset;
|
||||||
|
voice.time -= offset;
|
||||||
|
});
|
||||||
|
return opponentVoicesOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
forEachAlive(function(snd) {
|
||||||
|
if (snd.time < 0)
|
||||||
|
{
|
||||||
|
// Sync the time without calling update().
|
||||||
|
// time gets reset if it's negative.
|
||||||
|
snd.time += elapsed * 1000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snd.update(elapsed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a voice to the opponent group.
|
* Add a voice to the opponent group.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,6 +34,12 @@ class SongMetadata
|
||||||
@:default(false)
|
@:default(false)
|
||||||
public var looped:Bool;
|
public var looped:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumental and vocal offsets. Optional, defaults to 0.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var offsets:SongOffsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data relating to the song's gameplay.
|
* Data relating to the song's gameplay.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +65,7 @@ class SongMetadata
|
||||||
this.artist = artist;
|
this.artist = artist;
|
||||||
this.timeFormat = 'ms';
|
this.timeFormat = 'ms';
|
||||||
this.divisions = null;
|
this.divisions = null;
|
||||||
|
this.offsets = new SongOffsets();
|
||||||
this.timeChanges = [new SongTimeChange(0, 100)];
|
this.timeChanges = [new SongTimeChange(0, 100)];
|
||||||
this.looped = false;
|
this.looped = false;
|
||||||
this.playData = new SongPlayData();
|
this.playData = new SongPlayData();
|
||||||
|
@ -196,6 +203,90 @@ class SongTimeChange
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offsets to apply to the song's instrumental and vocals, relative to the chart.
|
||||||
|
* These are intended to correct for issues with the chart, or with the song's audio (for example a 10ms delay before the song starts).
|
||||||
|
* This is independent of the offsets applied in the user's settings, which are applied after these offsets and intended to correct for the user's hardware.
|
||||||
|
*/
|
||||||
|
class SongOffsets
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The offset, in milliseconds, to apply to the song's instrumental relative to the chart.
|
||||||
|
* For example, setting this to `-10.0` will start the instrumental 10ms earlier than the chart.
|
||||||
|
*
|
||||||
|
* Setting this to `-5000.0` means the chart start 5 seconds into the song.
|
||||||
|
* Setting this to `5000.0` means there will be 5 seconds of silence before the song starts.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(0)
|
||||||
|
public var instrumental:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply different offsets to different alternate instrumentals.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var altInstrumentals:Map<String, Float>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset, in milliseconds, to apply to the song's vocals, relative to the chart.
|
||||||
|
* These are applied ON TOP OF the instrumental offset.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var vocals:Map<String, Float>;
|
||||||
|
|
||||||
|
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>)
|
||||||
|
{
|
||||||
|
this.instrumental = instrumental;
|
||||||
|
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
||||||
|
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInstrumentalOffset(?instrumental:String):Float
|
||||||
|
{
|
||||||
|
if (instrumental == null || instrumental == '') return this.instrumental;
|
||||||
|
|
||||||
|
if (!this.altInstrumentals.exists(instrumental)) return this.instrumental;
|
||||||
|
|
||||||
|
return this.altInstrumentals.get(instrumental);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInstrumentalOffset(value:Float, ?instrumental:String):Float
|
||||||
|
{
|
||||||
|
if (instrumental == null || instrumental == '')
|
||||||
|
{
|
||||||
|
this.instrumental = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.altInstrumentals.set(instrumental, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVocalOffset(charId:String):Float
|
||||||
|
{
|
||||||
|
if (!this.vocals.exists(charId)) return 0.0;
|
||||||
|
|
||||||
|
return this.vocals.get(charId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setVocalOffset(charId:String, value:Float):Float
|
||||||
|
{
|
||||||
|
this.vocals.set(charId, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a string representation suitable for debugging.
|
||||||
|
*/
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for a song only used for the music.
|
* Metadata for a song only used for the music.
|
||||||
* For example, the menu music.
|
* For example, the menu music.
|
||||||
|
@ -309,6 +400,7 @@ class SongPlayData
|
||||||
* The difficulty ratings for this song as displayed in Freeplay.
|
* The difficulty ratings for this song as displayed in Freeplay.
|
||||||
* Key is a difficulty ID or `default`.
|
* Key is a difficulty ID or `default`.
|
||||||
*/
|
*/
|
||||||
|
@:optional
|
||||||
@:default(['default' => 1])
|
@:default(['default' => 1])
|
||||||
public var ratings:Map<String, Int>;
|
public var ratings:Map<String, Int>;
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,7 @@ class SongDataUtils
|
||||||
return new SongNoteData(time, data, length, kind);
|
return new SongNoteData(time, data, length, kind);
|
||||||
};
|
};
|
||||||
|
|
||||||
trace(notes);
|
|
||||||
trace(notes[0]);
|
|
||||||
var result = [for (i in 0...notes.length) offsetNote(notes[i])];
|
var result = [for (i in 0...notes.length) offsetNote(notes[i])];
|
||||||
trace(result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +51,36 @@ class SongDataUtils
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of SongNoteData objects, return a new array of SongNoteData objects
|
||||||
|
* which excludes any notes whose timestamps are outside of the given range.
|
||||||
|
* @param notes The notes to modify.
|
||||||
|
* @param startTime The start of the range in milliseconds.
|
||||||
|
* @param endTime The end of the range in milliseconds.
|
||||||
|
* @return The filtered array of notes.
|
||||||
|
*/
|
||||||
|
public static function clampSongNoteData(notes:Array<SongNoteData>, startTime:Float, endTime:Float):Array<SongNoteData>
|
||||||
|
{
|
||||||
|
return notes.filter(function(note:SongNoteData):Bool {
|
||||||
|
return note.time >= startTime && note.time <= endTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of SongEventData objects, return a new array of SongEventData objects
|
||||||
|
* which excludes any events whose timestamps are outside of the given range.
|
||||||
|
* @param events The events to modify.
|
||||||
|
* @param startTime The start of the range in milliseconds.
|
||||||
|
* @param endTime The end of the range in milliseconds.
|
||||||
|
* @return The filtered array of events.
|
||||||
|
*/
|
||||||
|
public static function clampSongEventData(events:Array<SongEventData>, startTime:Float, endTime:Float):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return events.filter(function(event:SongEventData):Bool {
|
||||||
|
return event.time >= startTime && event.time <= endTime;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
|
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
|
||||||
* Does not mutate the original array.
|
* Does not mutate the original array.
|
||||||
|
|
|
@ -19,7 +19,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateStageData()` function.
|
* and adding migration to the `migrateStageData()` function.
|
||||||
*/
|
*/
|
||||||
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.0";
|
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.1";
|
||||||
|
|
||||||
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ class SongMetadata_v2_1_0
|
||||||
*/
|
*/
|
||||||
public var playData:SongPlayData_v2_1_0;
|
public var playData:SongPlayData_v2_1_0;
|
||||||
|
|
||||||
|
// In metadata `v2.2.1`, `SongOffsets` was added.
|
||||||
|
// var offsets:SongOffsets;
|
||||||
// ==========
|
// ==========
|
||||||
// UNMODIFIED VALUES
|
// UNMODIFIED VALUES
|
||||||
// ==========
|
// ==========
|
||||||
|
|
|
@ -142,12 +142,14 @@ class Cursor
|
||||||
};
|
};
|
||||||
static var assetCursorCell:Null<BitmapData> = null;
|
static var assetCursorCell:Null<BitmapData> = null;
|
||||||
|
|
||||||
// DESIRED CURSOR: Resize NS (vertical)
|
public static final CURSOR_SCROLL_PARAMS:CursorParams =
|
||||||
// DESIRED CURSOR: Resize EW (horizontal)
|
{
|
||||||
// DESIRED CURSOR: Resize NESW (diagonal)
|
graphic: "assets/images/cursor/cursor-scroll.png",
|
||||||
// DESIRED CURSOR: Resize NWSE (diagonal)
|
scale: 0.2,
|
||||||
// DESIRED CURSOR: Help (Cursor with question mark)
|
offsetX: -15,
|
||||||
// DESIRED CURSOR: Menu (Cursor with menu icon)
|
offsetY: -15,
|
||||||
|
};
|
||||||
|
static var assetCursorScroll:Null<BitmapData> = null;
|
||||||
|
|
||||||
static function set_cursorMode(value:Null<CursorMode>):Null<CursorMode>
|
static function set_cursorMode(value:Null<CursorMode>):Null<CursorMode>
|
||||||
{
|
{
|
||||||
|
@ -304,6 +306,18 @@ class Cursor
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Scroll:
|
||||||
|
if (assetCursorScroll == null)
|
||||||
|
{
|
||||||
|
var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_SCROLL_PARAMS.graphic);
|
||||||
|
assetCursorScroll = bitmapData;
|
||||||
|
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
setCursorGraphic(null);
|
setCursorGraphic(null);
|
||||||
}
|
}
|
||||||
|
@ -487,6 +501,21 @@ class Cursor
|
||||||
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Scroll:
|
||||||
|
if (assetCursorScroll == null)
|
||||||
|
{
|
||||||
|
var future:Future<BitmapData> = Assets.loadBitmapData(CURSOR_SCROLL_PARAMS.graphic);
|
||||||
|
future.onComplete(function(bitmapData:BitmapData) {
|
||||||
|
assetCursorScroll = bitmapData;
|
||||||
|
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
||||||
|
});
|
||||||
|
future.onError(onCursorError.bind(Scroll));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applyCursorParams(assetCursorScroll, CURSOR_SCROLL_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
loadCursorGraphic(null);
|
loadCursorGraphic(null);
|
||||||
}
|
}
|
||||||
|
@ -517,6 +546,7 @@ class Cursor
|
||||||
registerHaxeUICursor('zoom-out', CURSOR_ZOOM_OUT_PARAMS);
|
registerHaxeUICursor('zoom-out', CURSOR_ZOOM_OUT_PARAMS);
|
||||||
registerHaxeUICursor('crosshair', CURSOR_CROSSHAIR_PARAMS);
|
registerHaxeUICursor('crosshair', CURSOR_CROSSHAIR_PARAMS);
|
||||||
registerHaxeUICursor('cell', CURSOR_CELL_PARAMS);
|
registerHaxeUICursor('cell', CURSOR_CELL_PARAMS);
|
||||||
|
registerHaxeUICursor('scroll', CURSOR_SCROLL_PARAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function registerHaxeUICursor(id:String, params:CursorParams):Void
|
public static function registerHaxeUICursor(id:String, params:CursorParams):Void
|
||||||
|
@ -539,6 +569,7 @@ enum CursorMode
|
||||||
ZoomOut;
|
ZoomOut;
|
||||||
Crosshair;
|
Crosshair;
|
||||||
Cell;
|
Cell;
|
||||||
|
Scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -179,6 +179,13 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var songScore:Int = 0;
|
public var songScore:Int = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start at this point in the song once the countdown is done.
|
||||||
|
* For example, if `startTimestamp` is `30000`, the song will start at the 30 second mark.
|
||||||
|
* Used for chart playtesting or practice.
|
||||||
|
*/
|
||||||
|
public var startTimestamp:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An empty FlxObject contained in the scene.
|
* An empty FlxObject contained in the scene.
|
||||||
* The current gameplay camera will always follow this object. Tween its position to move the camera smoothly.
|
* The current gameplay camera will always follow this object. Tween its position to move the camera smoothly.
|
||||||
|
@ -254,10 +261,6 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var disableKeys:Bool = false;
|
public var disableKeys:Bool = false;
|
||||||
|
|
||||||
public var startTimestamp:Float = 0.0;
|
|
||||||
|
|
||||||
var overrideMusic:Bool = false;
|
|
||||||
|
|
||||||
public var isSubState(get, never):Bool;
|
public var isSubState(get, never):Bool;
|
||||||
|
|
||||||
function get_isSubState():Bool
|
function get_isSubState():Bool
|
||||||
|
@ -317,6 +320,18 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
var skipHeldTimer:Float = 0;
|
var skipHeldTimer:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the PlayState was started with instrumentals and vocals already provided.
|
||||||
|
* Used by the chart editor to prevent replacing the music.
|
||||||
|
*/
|
||||||
|
var overrideMusic:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After the song starts, the song offset may dictate we wait before the instrumental starts.
|
||||||
|
* This variable represents that delay, and is subtracted from until it reaches 0, before calling `music.play()`
|
||||||
|
*/
|
||||||
|
var songStartDelay:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcibly disables all update logic while the game moves back to the Menu state.
|
* Forcibly disables all update logic while the game moves back to the Menu state.
|
||||||
* This is used only when a critical error occurs and the game absolutely cannot continue.
|
* This is used only when a critical error occurs and the game absolutely cannot continue.
|
||||||
|
@ -468,6 +483,8 @@ class PlayState extends MusicBeatSubState
|
||||||
var generatedMusic:Bool = false;
|
var generatedMusic:Bool = false;
|
||||||
var perfectMode:Bool = false;
|
var perfectMode:Bool = false;
|
||||||
|
|
||||||
|
static final BACKGROUND_COLOR:FlxColor = FlxColor.MAGENTA;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate a new PlayState.
|
* Instantiate a new PlayState.
|
||||||
* @param params The parameters used to initialize the PlayState.
|
* @param params The parameters used to initialize the PlayState.
|
||||||
|
@ -551,6 +568,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Prepare the Conductor.
|
// Prepare the Conductor.
|
||||||
Conductor.forceBPM(null);
|
Conductor.forceBPM(null);
|
||||||
|
Conductor.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
|
||||||
Conductor.mapTimeChanges(currentChart.timeChanges);
|
Conductor.mapTimeChanges(currentChart.timeChanges);
|
||||||
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
|
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
|
||||||
|
|
||||||
|
@ -631,6 +649,24 @@ class PlayState extends MusicBeatSubState
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override function draw():Void
|
||||||
|
{
|
||||||
|
// if (FlxG.renderBlit)
|
||||||
|
// {
|
||||||
|
// camGame.fill(BACKGROUND_COLOR);
|
||||||
|
// }
|
||||||
|
// else if (FlxG.renderTile)
|
||||||
|
// {
|
||||||
|
// FlxG.log.warn("PlayState background not displayed properly on tile renderer!");
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// FlxG.log.warn("PlayState background not displayed properly, unknown renderer!");
|
||||||
|
// }
|
||||||
|
|
||||||
|
super.draw();
|
||||||
|
}
|
||||||
|
|
||||||
function assertChartExists():Bool
|
function assertChartExists():Bool
|
||||||
{
|
{
|
||||||
// Returns null if the song failed to load or doesn't have the selected difficulty.
|
// Returns null if the song failed to load or doesn't have the selected difficulty.
|
||||||
|
@ -699,8 +735,8 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Reset music properly.
|
// Reset music properly.
|
||||||
|
|
||||||
|
FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instrumentalOffset);
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
FlxG.sound.music.time = (startTimestamp);
|
|
||||||
|
|
||||||
if (!overrideMusic)
|
if (!overrideMusic)
|
||||||
{
|
{
|
||||||
|
@ -751,17 +787,40 @@ class PlayState extends MusicBeatSubState
|
||||||
if (isInCountdown)
|
if (isInCountdown)
|
||||||
{
|
{
|
||||||
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
||||||
if (Conductor.songPosition >= startTimestamp) startSong();
|
if (Conductor.songPosition >= (startTimestamp)) startSong();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM!
|
if (Constants.EXT_SOUND == 'mp3')
|
||||||
|
{
|
||||||
|
Conductor.formatOffset = Constants.MP3_DELAY_MS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conductor.formatOffset = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// :nerd: um ackshually it's not 13 it's 11.97278911564
|
if (songStartDelay > 0)
|
||||||
if (Constants.EXT_SOUND == 'mp3') Conductor.offset = Constants.MP3_DELAY_MS;
|
{
|
||||||
|
// Code to handle the song not starting yet (positive instrumental offset in metadata).
|
||||||
Conductor.update();
|
// Wait for offset to elapse before actually hitting play on the instrumental.
|
||||||
|
songStartDelay -= elapsed * 1000;
|
||||||
|
if (songStartDelay <= 0)
|
||||||
|
{
|
||||||
|
FlxG.sound.music.play();
|
||||||
|
Conductor.update(); // Normal conductor update.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Make beat events still happen.
|
||||||
|
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conductor.update(); // Normal conductor update.
|
||||||
|
}
|
||||||
|
|
||||||
if (!isGamePaused)
|
if (!isGamePaused)
|
||||||
{
|
{
|
||||||
|
@ -1137,12 +1196,12 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (!startingSong
|
if (!startingSong
|
||||||
&& FlxG.sound.music != null
|
&& FlxG.sound.music != null
|
||||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200
|
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPositionNoOffset)) > 200
|
||||||
|| Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200))
|
|| Math.abs(vocals.checkSyncError(Conductor.songPositionNoOffset)) > 200))
|
||||||
{
|
{
|
||||||
trace("VOCALS NEED RESYNC");
|
trace("VOCALS NEED RESYNC");
|
||||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition - Conductor.offset));
|
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPositionNoOffset));
|
||||||
trace(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset));
|
trace(FlxG.sound.music.time - (Conductor.songPositionNoOffset));
|
||||||
resyncVocals();
|
resyncVocals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,6 +1317,7 @@ class PlayState extends MusicBeatSubState
|
||||||
function initCameras():Void
|
function initCameras():Void
|
||||||
{
|
{
|
||||||
camGame = new SwagCamera();
|
camGame = new SwagCamera();
|
||||||
|
camGame.bgColor = BACKGROUND_COLOR; // Show a pink background behind the stage.
|
||||||
camHUD = new FlxCamera();
|
camHUD = new FlxCamera();
|
||||||
camHUD.bgColor.alpha = 0; // Show the game scene behind the camera.
|
camHUD.bgColor.alpha = 0; // Show the game scene behind the camera.
|
||||||
camCutscene = new FlxCamera();
|
camCutscene = new FlxCamera();
|
||||||
|
@ -1684,12 +1744,28 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.sound.music.onComplete = endSong;
|
FlxG.sound.music.onComplete = endSong;
|
||||||
FlxG.sound.music.play();
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
FlxG.sound.music.time = startTimestamp;
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
||||||
|
|
||||||
trace('Playing vocals...');
|
trace('Playing vocals...');
|
||||||
add(vocals);
|
add(vocals);
|
||||||
vocals.play();
|
|
||||||
resyncVocals();
|
if (FlxG.sound.music.time < 0)
|
||||||
|
{
|
||||||
|
// A positive instrumentalOffset means Conductor.songPosition will still be negative after the countdown elapses.
|
||||||
|
trace('POSITIVE OFFSET: Waiting to start song...');
|
||||||
|
songStartDelay = Math.abs(FlxG.sound.music.time);
|
||||||
|
FlxG.sound.music.time = 0;
|
||||||
|
FlxG.sound.music.pause();
|
||||||
|
vocals.pause();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.sound.music.play();
|
||||||
|
vocals.play();
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
|
||||||
#if discord_rpc
|
#if discord_rpc
|
||||||
// Updating Discord Rich Presence (with Time Left)
|
// Updating Discord Rich Presence (with Time Left)
|
||||||
|
@ -1698,7 +1774,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (startTimestamp > 0)
|
if (startTimestamp > 0)
|
||||||
{
|
{
|
||||||
FlxG.sound.music.time = startTimestamp;
|
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
||||||
handleSkippedNotes();
|
handleSkippedNotes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1710,13 +1786,13 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (_exiting || vocals == null) return;
|
if (_exiting || vocals == null) return;
|
||||||
|
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, etc.)
|
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||||
if (!FlxG.sound.music.playing) return;
|
if (!FlxG.sound.music.playing) return;
|
||||||
|
if (songStartDelay > 0) return;
|
||||||
|
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
|
|
||||||
FlxG.sound.music.play();
|
FlxG.sound.music.play();
|
||||||
Conductor.update();
|
|
||||||
|
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = FlxG.sound.music.time;
|
||||||
vocals.play(false, FlxG.sound.music.time);
|
vocals.play(false, FlxG.sound.music.time);
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
package funkin.play.song;
|
package funkin.play.song;
|
||||||
|
|
||||||
import funkin.util.SortUtil;
|
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import openfl.utils.Assets;
|
|
||||||
import funkin.modding.events.ScriptEvent;
|
|
||||||
import funkin.modding.IScriptedClass;
|
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.IRegistryEntry;
|
||||||
|
import funkin.data.song.SongData.SongCharacterData;
|
||||||
import funkin.data.song.SongData.SongChartData;
|
import funkin.data.song.SongData.SongChartData;
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
|
||||||
import funkin.data.song.SongRegistry;
|
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
import funkin.data.song.SongData.SongMetadata;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.SongOffsets;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.data.song.SongData.SongTimeFormat;
|
import funkin.data.song.SongData.SongTimeFormat;
|
||||||
import funkin.data.IRegistryEntry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.modding.IScriptedClass;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a data structure managing information about the current song.
|
* This is a data structure managing information about the current song.
|
||||||
|
@ -172,6 +173,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
difficulty.timeChanges = metadata.timeChanges;
|
difficulty.timeChanges = metadata.timeChanges;
|
||||||
difficulty.looped = metadata.looped;
|
difficulty.looped = metadata.looped;
|
||||||
difficulty.generatedBy = metadata.generatedBy;
|
difficulty.generatedBy = metadata.generatedBy;
|
||||||
|
difficulty.offsets = metadata.offsets;
|
||||||
|
|
||||||
difficulty.stage = metadata.playData.stage;
|
difficulty.stage = metadata.playData.stage;
|
||||||
difficulty.noteStyle = metadata.playData.noteStyle;
|
difficulty.noteStyle = metadata.playData.noteStyle;
|
||||||
|
@ -391,6 +393,7 @@ class SongDifficulty
|
||||||
public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT;
|
public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT;
|
||||||
public var divisions:Null<Int> = null;
|
public var divisions:Null<Int> = null;
|
||||||
public var looped:Bool = false;
|
public var looped:Bool = false;
|
||||||
|
public var offsets:SongOffsets = new SongOffsets();
|
||||||
public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY;
|
public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY;
|
||||||
|
|
||||||
public var timeChanges:Array<SongTimeChange> = [];
|
public var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
@ -542,6 +545,9 @@ class SongDifficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
|
||||||
|
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ abstract Save(RawSaveData)
|
||||||
theme: ChartEditorTheme.Light,
|
theme: ChartEditorTheme.Light,
|
||||||
playtestStartTime: false,
|
playtestStartTime: false,
|
||||||
downscroll: false,
|
downscroll: false,
|
||||||
metronomeEnabled: true,
|
metronomeVolume: 1.0,
|
||||||
hitsoundsEnabledPlayer: true,
|
hitsoundsEnabledPlayer: true,
|
||||||
hitsoundsEnabledOpponent: true,
|
hitsoundsEnabledOpponent: true,
|
||||||
instVolume: 1.0,
|
instVolume: 1.0,
|
||||||
|
@ -279,21 +279,38 @@ abstract Save(RawSaveData)
|
||||||
return this.optionsChartEditor.theme;
|
return this.optionsChartEditor.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var chartEditorMetronomeEnabled(get, set):Bool;
|
public var chartEditorMetronomeVolume(get, set):Float;
|
||||||
|
|
||||||
function get_chartEditorMetronomeEnabled():Bool
|
function get_chartEditorMetronomeVolume():Float
|
||||||
{
|
{
|
||||||
if (this.optionsChartEditor.metronomeEnabled == null) this.optionsChartEditor.metronomeEnabled = true;
|
if (this.optionsChartEditor.metronomeVolume == null) this.optionsChartEditor.metronomeVolume = 1.0;
|
||||||
|
|
||||||
return this.optionsChartEditor.metronomeEnabled;
|
return this.optionsChartEditor.metronomeVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_chartEditorMetronomeEnabled(value:Bool):Bool
|
function set_chartEditorMetronomeVolume(value:Float):Float
|
||||||
{
|
{
|
||||||
// Set and apply.
|
// Set and apply.
|
||||||
this.optionsChartEditor.metronomeEnabled = value;
|
this.optionsChartEditor.metronomeVolume = value;
|
||||||
flush();
|
flush();
|
||||||
return this.optionsChartEditor.metronomeEnabled;
|
return this.optionsChartEditor.metronomeVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorHitsoundVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorHitsoundVolume():Float
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.hitsoundVolume == null) this.optionsChartEditor.hitsoundVolume = 1.0;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.hitsoundVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorHitsoundVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.hitsoundVolume = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.hitsoundVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var chartEditorHitsoundsEnabledPlayer(get, set):Bool;
|
public var chartEditorHitsoundsEnabledPlayer(get, set):Bool;
|
||||||
|
@ -981,10 +998,16 @@ typedef SaveDataChartEditorOptions =
|
||||||
var ?downscroll:Bool;
|
var ?downscroll:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metronome sounds in the Chart Editor.
|
* Metronome volume in the Chart Editor.
|
||||||
* @default `true`
|
* @default `1.0`
|
||||||
*/
|
*/
|
||||||
var ?metronomeEnabled:Bool;
|
var ?metronomeVolume:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hitsound volume in the Chart Editor.
|
||||||
|
* @default `1.0`
|
||||||
|
*/
|
||||||
|
var ?hitsoundVolume:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, playtest songs from the current position in the Chart Editor.
|
* If true, playtest songs from the current position in the Chart Editor.
|
||||||
|
|
|
@ -84,6 +84,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||||
{
|
{
|
||||||
// Display Conductor info in the watch window.
|
// Display Conductor info in the watch window.
|
||||||
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
||||||
|
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||||
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
||||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
||||||
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
||||||
|
|
|
@ -66,6 +66,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
|
||||||
|
|
||||||
// Display Conductor info in the watch window.
|
// Display Conductor info in the watch window.
|
||||||
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
||||||
|
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||||
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
||||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
||||||
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
||||||
|
|
|
@ -92,6 +92,7 @@ import haxe.ui.backend.flixel.UIRuntimeState;
|
||||||
import haxe.ui.backend.flixel.UIState;
|
import haxe.ui.backend.flixel.UIState;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
import haxe.ui.components.Label;
|
import haxe.ui.components.Label;
|
||||||
|
import haxe.ui.components.Button;
|
||||||
import haxe.ui.components.NumberStepper;
|
import haxe.ui.components.NumberStepper;
|
||||||
import haxe.ui.components.Slider;
|
import haxe.ui.components.Slider;
|
||||||
import haxe.ui.components.TextField;
|
import haxe.ui.components.TextField;
|
||||||
|
@ -100,6 +101,7 @@ import haxe.ui.containers.Frame;
|
||||||
import haxe.ui.containers.menus.Menu;
|
import haxe.ui.containers.menus.Menu;
|
||||||
import haxe.ui.containers.menus.MenuBar;
|
import haxe.ui.containers.menus.MenuBar;
|
||||||
import haxe.ui.containers.menus.MenuItem;
|
import haxe.ui.containers.menus.MenuItem;
|
||||||
|
import haxe.ui.containers.menus.MenuCheckBox;
|
||||||
import haxe.ui.containers.TreeView;
|
import haxe.ui.containers.TreeView;
|
||||||
import haxe.ui.containers.TreeViewNode;
|
import haxe.ui.containers.TreeViewNode;
|
||||||
import haxe.ui.core.Component;
|
import haxe.ui.core.Component;
|
||||||
|
@ -615,9 +617,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Audio
|
// Audio
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to play a metronome sound while the playhead is moving.
|
* Whether to play a metronome sound while the playhead is moving, and what volume.
|
||||||
*/
|
*/
|
||||||
var isMetronomeEnabled:Bool = true;
|
var metronomeVolume:Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The volume to play hitsounds at.
|
||||||
|
*/
|
||||||
|
var hitsoundVolume:Float = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether hitsounds are enabled for the player.
|
* Whether hitsounds are enabled for the player.
|
||||||
|
@ -665,6 +672,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var currentScrollEase:Null<VarTween>;
|
var currentScrollEase:Null<VarTween>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position where the user middle clicked to place a scroll anchor.
|
||||||
|
* Scroll each frame with speed based on the distance between the mouse and the scroll anchor.
|
||||||
|
* `null` if no scroll anchor is present.
|
||||||
|
*/
|
||||||
|
var scrollAnchorScreenPos:Null<FlxPoint> = null;
|
||||||
|
|
||||||
// Note Placement
|
// Note Placement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1249,98 +1263,257 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var playbarHeadLayout:Null<ChartEditorPlaybarHead> = null;
|
var playbarHeadLayout:Null<ChartEditorPlaybarHead> = null;
|
||||||
|
|
||||||
// NOTE: All the components below are automatically assigned via HaxeUI macros.
|
// NOTE: All the components below are automatically assigned via HaxeUI macros.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The menubar at the top of the screen.
|
* The menubar at the top of the screen.
|
||||||
*/
|
*/
|
||||||
// var menubar:MenuBar;
|
var menubar:MenuBar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> New Chart` menu item.
|
* The `File -> New Chart` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemNewChart:MenuItem;
|
var menubarItemNewChart:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Open Chart` menu item.
|
* The `File -> Open Chart` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemOpenChart:MenuItem;
|
var menubarItemOpenChart:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Open Recent` menu.
|
* The `File -> Open Recent` menu.
|
||||||
*/
|
*/
|
||||||
// var menubarOpenRecent:Menu;
|
var menubarOpenRecent:Menu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Save Chart` menu item.
|
* The `File -> Save Chart` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemSaveChart:MenuItem;
|
var menubarItemSaveChart:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Save Chart As` menu item.
|
* The `File -> Save Chart As` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemSaveChartAs:MenuItem;
|
var menubarItemSaveChartAs:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Preferences` menu item.
|
* The `File -> Preferences` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemPreferences:MenuItem;
|
var menubarItemPreferences:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `File -> Exit` menu item.
|
* The `File -> Exit` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemExit:MenuItem;
|
var menubarItemExit:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Undo` menu item.
|
* The `Edit -> Undo` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemUndo:MenuItem;
|
var menubarItemUndo:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Redo` menu item.
|
* The `Edit -> Redo` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemRedo:MenuItem;
|
var menubarItemRedo:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Cut` menu item.
|
* The `Edit -> Cut` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemCut:MenuItem;
|
var menubarItemCut:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Copy` menu item.
|
* The `Edit -> Copy` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemCopy:MenuItem;
|
var menubarItemCopy:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Paste` menu item.
|
* The `Edit -> Paste` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemPaste:MenuItem;
|
var menubarItemPaste:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Paste Unsnapped` menu item.
|
* The `Edit -> Paste Unsnapped` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemPasteUnsnapped:MenuItem;
|
var menubarItemPasteUnsnapped:MenuItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Edit -> Delete` menu item.
|
* The `Edit -> Delete` menu item.
|
||||||
*/
|
*/
|
||||||
// var menubarItemDelete:MenuItem;
|
var menubarItemDelete:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Flip Notes` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemFlipNotes:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select All` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectAll:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select Inverse` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectInverse:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select None` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectNone:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select Region` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectRegion:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select Before Cursor` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectBeforeCursor:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Select After Cursor` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemSelectAfterCursor:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Decrease Note Snap Precision` menu item.
|
||||||
|
*/
|
||||||
|
var menuBarItemNoteSnapDecrease:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Edit -> Decrease Note Snap Precision` menu item.
|
||||||
|
*/
|
||||||
|
var menuBarItemNoteSnapIncrease:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `View -> Downscroll` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemDownscroll:MenuCheckBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `View -> Increase Difficulty` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemDifficultyUp:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `View -> Decrease Difficulty` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemDifficultyDown:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Play/Pause` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemPlayPause:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Load Instrumental` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemLoadInstrumental:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Load Vocals` menu item.
|
||||||
|
*/
|
||||||
|
var menubarItemLoadVocals:MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Metronome Volume` label.
|
||||||
|
*/
|
||||||
|
var menubarLabelVolumeMetronome:Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Metronome Volume` slider.
|
||||||
|
*/
|
||||||
|
var menubarItemVolumeMetronome:Slider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Enable Player Hitsounds` menu checkbox.
|
||||||
|
*/
|
||||||
|
var menubarItemPlayerHitsounds:MenuCheckBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Enable Opponent Hitsounds` menu checkbox.
|
||||||
|
*/
|
||||||
|
var menubarItemOpponentHitsounds:MenuCheckBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Hitsound Volume` label.
|
||||||
|
*/
|
||||||
|
var menubarLabelVolumeHitsounds:Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Hitsound Volume` slider.
|
||||||
|
*/
|
||||||
|
var menubarItemVolumeHitsounds:Slider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Instrumental Volume` label.
|
||||||
|
*/
|
||||||
|
var menubarLabelVolumeInstrumental:Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Instrumental Volume` slider.
|
||||||
|
*/
|
||||||
|
var menubarItemVolumeInstrumental:Slider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Vocal Volume` label.
|
||||||
|
*/
|
||||||
|
var menubarLabelVolumeVocals:Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Vocal Volume` slider.
|
||||||
|
*/
|
||||||
|
var menubarItemVolumeVocals:Slider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Playback Speed` label.
|
||||||
|
*/
|
||||||
|
var menubarLabelPlaybackSpeed:Label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Audio -> Playback Speed` slider.
|
||||||
|
*/
|
||||||
|
var menubarItemPlaybackSpeed:Slider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label by the playbar telling the song position.
|
* The label by the playbar telling the song position.
|
||||||
*/
|
*/
|
||||||
// var playbarSongPos:Label;
|
var playbarSongPos:Label;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label by the playbar telling the song time remaining.
|
* The label by the playbar telling the song time remaining.
|
||||||
*/
|
*/
|
||||||
// var playbarSongRemaining:Label;
|
var playbarSongRemaining:Label;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label by the playbar telling the note snap.
|
* The label by the playbar telling the note snap.
|
||||||
*/
|
*/
|
||||||
// var playbarNoteSnap:Label;
|
var playbarNoteSnap:Label;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button by the playbar to jump to the start of the song.
|
* The button by the playbar to jump to the start of the song.
|
||||||
*/
|
*/
|
||||||
// var playbarStart:Button;
|
var playbarStart:Button;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button by the playbar to jump backwards in the song.
|
* The button by the playbar to jump backwards in the song.
|
||||||
*/
|
*/
|
||||||
// var playbarBack:Button;
|
var playbarBack:Button;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button by the playbar to play or pause the song.
|
* The button by the playbar to play or pause the song.
|
||||||
*/
|
*/
|
||||||
// var playbarPlay:Button;
|
var playbarPlay:Button;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button by the playbar to jump forwards in the song.
|
* The button by the playbar to jump forwards in the song.
|
||||||
*/
|
*/
|
||||||
// var playbarForward:Button;
|
var playbarForward:Button;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button by the playbar to jump to the end of the song.
|
* The button by the playbar to jump to the end of the song.
|
||||||
*/
|
*/
|
||||||
// var playbarEnd:Button;
|
var playbarEnd:Button;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RENDER OBJECTS
|
* RENDER OBJECTS
|
||||||
*/
|
*/
|
||||||
|
@ -1682,7 +1855,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
isViewDownscroll = save.chartEditorDownscroll;
|
isViewDownscroll = save.chartEditorDownscroll;
|
||||||
playtestStartTime = save.chartEditorPlaytestStartTime;
|
playtestStartTime = save.chartEditorPlaytestStartTime;
|
||||||
currentTheme = save.chartEditorTheme;
|
currentTheme = save.chartEditorTheme;
|
||||||
isMetronomeEnabled = save.chartEditorMetronomeEnabled;
|
metronomeVolume = save.chartEditorMetronomeVolume;
|
||||||
|
hitsoundVolume = save.chartEditorHitsoundVolume;
|
||||||
hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer;
|
hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer;
|
||||||
hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent;
|
hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent;
|
||||||
|
|
||||||
|
@ -1710,7 +1884,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
save.chartEditorDownscroll = isViewDownscroll;
|
save.chartEditorDownscroll = isViewDownscroll;
|
||||||
save.chartEditorPlaytestStartTime = playtestStartTime;
|
save.chartEditorPlaytestStartTime = playtestStartTime;
|
||||||
save.chartEditorTheme = currentTheme;
|
save.chartEditorTheme = currentTheme;
|
||||||
save.chartEditorMetronomeEnabled = isMetronomeEnabled;
|
save.chartEditorMetronomeVolume = metronomeVolume;
|
||||||
|
save.chartEditorHitsoundVolume = hitsoundVolume;
|
||||||
save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer;
|
save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer;
|
||||||
save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent;
|
save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent;
|
||||||
|
|
||||||
|
@ -2282,8 +2457,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemLoadInstrumental.onClick = _ -> this.openUploadInstDialog(true);
|
menubarItemLoadInstrumental.onClick = _ -> this.openUploadInstDialog(true);
|
||||||
menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true);
|
menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true);
|
||||||
|
|
||||||
menubarItemMetronomeEnabled.onChange = event -> isMetronomeEnabled = event.value;
|
menubarItemVolumeMetronome.onChange = event -> {
|
||||||
menubarItemMetronomeEnabled.selected = isMetronomeEnabled;
|
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||||
|
metronomeVolume = volume;
|
||||||
|
menubarLabelVolumeMetronome.text = 'Metronome - ${Std.int(event.value)}%';
|
||||||
|
};
|
||||||
|
menubarItemVolumeMetronome.value = Std.int(metronomeVolume * 100);
|
||||||
|
|
||||||
menubarItemPlayerHitsounds.onChange = event -> hitsoundsEnabledPlayer = event.value;
|
menubarItemPlayerHitsounds.onChange = event -> hitsoundsEnabledPlayer = event.value;
|
||||||
menubarItemPlayerHitsounds.selected = hitsoundsEnabledPlayer;
|
menubarItemPlayerHitsounds.selected = hitsoundsEnabledPlayer;
|
||||||
|
@ -2291,6 +2470,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value;
|
menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value;
|
||||||
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
||||||
|
|
||||||
|
menubarItemVolumeHitsound.onChange = event -> {
|
||||||
|
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||||
|
hitsoundVolume = volume;
|
||||||
|
menubarLabelVolumeHitsound.text = 'Hitsound - ${Std.int(event.value)}%';
|
||||||
|
};
|
||||||
|
menubarItemVolumeHitsound.value = Std.int(hitsoundVolume * 100);
|
||||||
|
|
||||||
menubarItemVolumeInstrumental.onChange = event -> {
|
menubarItemVolumeInstrumental.onChange = event -> {
|
||||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||||
|
@ -2499,7 +2685,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// dispatchEvent gets called here.
|
// dispatchEvent gets called here.
|
||||||
if (!super.beatHit()) return false;
|
if (!super.beatHit()) return false;
|
||||||
|
|
||||||
if (isMetronomeEnabled && this.subState == null && (audioInstTrack != null && audioInstTrack.playing))
|
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.playing))
|
||||||
{
|
{
|
||||||
playMetronomeTick(Conductor.currentBeat % 4 == 0);
|
playMetronomeTick(Conductor.currentBeat % 4 == 0);
|
||||||
}
|
}
|
||||||
|
@ -2540,7 +2726,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
if (audioInstTrack != null && audioInstTrack.playing)
|
if (audioInstTrack != null && audioInstTrack.playing)
|
||||||
{
|
{
|
||||||
if (FlxG.mouse.pressedMiddle)
|
if (FlxG.keys.pressed.ALT)
|
||||||
{
|
{
|
||||||
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
|
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
|
||||||
|
|
||||||
|
@ -2940,6 +3126,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var shouldPause:Bool = false; // Whether to pause the song when scrolling.
|
var shouldPause:Bool = false; // Whether to pause the song when scrolling.
|
||||||
var shouldEase:Bool = false; // Whether to ease the scroll.
|
var shouldEase:Bool = false; // Whether to ease the scroll.
|
||||||
|
|
||||||
|
// Handle scroll anchor
|
||||||
|
if (scrollAnchorScreenPos != null)
|
||||||
|
{
|
||||||
|
var currentScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||||
|
var distance = currentScreenPos - scrollAnchorScreenPos;
|
||||||
|
|
||||||
|
var verticalDistance = distance.y;
|
||||||
|
|
||||||
|
// How much scrolling should be done based on the distance of the cursor from the anchor.
|
||||||
|
final ANCHOR_SCROLL_SPEED = 0.2;
|
||||||
|
|
||||||
|
scrollAmount = ANCHOR_SCROLL_SPEED * verticalDistance;
|
||||||
|
shouldPause = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Mouse Wheel = Scroll
|
// Mouse Wheel = Scroll
|
||||||
if (FlxG.mouse.wheel != 0 && !FlxG.keys.pressed.CONTROL)
|
if (FlxG.mouse.wheel != 0 && !FlxG.keys.pressed.CONTROL)
|
||||||
{
|
{
|
||||||
|
@ -3019,18 +3220,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middle Mouse + Drag = Scroll but move the playhead the same amount.
|
|
||||||
if (FlxG.mouse.pressedMiddle)
|
|
||||||
{
|
|
||||||
if (FlxG.mouse.deltaY != 0)
|
|
||||||
{
|
|
||||||
// Scroll down by the amount dragged.
|
|
||||||
scrollAmount += -FlxG.mouse.deltaY;
|
|
||||||
// Move the playhead by the same amount in the other direction so it is stationary.
|
|
||||||
playheadAmount += FlxG.mouse.deltaY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHIFT + Scroll = Scroll Fast
|
// SHIFT + Scroll = Scroll Fast
|
||||||
if (FlxG.keys.pressed.SHIFT)
|
if (FlxG.keys.pressed.SHIFT)
|
||||||
{
|
{
|
||||||
|
@ -3042,7 +3231,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
scrollAmount /= 10;
|
scrollAmount /= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ALT = Move playhead instead.
|
// Alt + Drag = Scroll but move the playhead the same amount.
|
||||||
if (FlxG.keys.pressed.ALT)
|
if (FlxG.keys.pressed.ALT)
|
||||||
{
|
{
|
||||||
playheadAmount = scrollAmount;
|
playheadAmount = scrollAmount;
|
||||||
|
@ -3164,9 +3353,26 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
var overlapsSelection:Bool = FlxG.mouse.overlaps(renderedSelectionSquares);
|
var overlapsSelection:Bool = FlxG.mouse.overlaps(renderedSelectionSquares);
|
||||||
|
|
||||||
|
if (FlxG.mouse.justPressedMiddle)
|
||||||
|
{
|
||||||
|
if (scrollAnchorScreenPos == null)
|
||||||
|
{
|
||||||
|
scrollAnchorScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||||
|
selectionBoxStartPos = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scrollAnchorScreenPos = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (FlxG.mouse.justPressed)
|
if (FlxG.mouse.justPressed)
|
||||||
{
|
{
|
||||||
if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
|
if (scrollAnchorScreenPos != null)
|
||||||
|
{
|
||||||
|
scrollAnchorScreenPos = null;
|
||||||
|
}
|
||||||
|
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
|
||||||
{
|
{
|
||||||
gridPlayheadScrollAreaPressed = true;
|
gridPlayheadScrollAreaPressed = true;
|
||||||
}
|
}
|
||||||
|
@ -3175,7 +3381,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Clicked note preview
|
// Clicked note preview
|
||||||
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||||
}
|
}
|
||||||
else if (!overlapsGrid || overlapsSelectionBorder)
|
else if (!isCursorOverHaxeUI && (!overlapsGrid || overlapsSelectionBorder))
|
||||||
{
|
{
|
||||||
selectionBoxStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
selectionBoxStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||||
// Drawing selection box.
|
// Drawing selection box.
|
||||||
|
@ -3458,6 +3664,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
scrollPositionInPixels = clickedPosInPixels;
|
scrollPositionInPixels = clickedPosInPixels;
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
}
|
}
|
||||||
|
else if (scrollAnchorScreenPos != null)
|
||||||
|
{
|
||||||
|
// Cursor should be a scroll anchor.
|
||||||
|
targetCursorMode = Scroll;
|
||||||
|
}
|
||||||
else if (dragTargetNote != null || dragTargetEvent != null)
|
else if (dragTargetNote != null || dragTargetEvent != null)
|
||||||
{
|
{
|
||||||
if (FlxG.mouse.justReleased)
|
if (FlxG.mouse.justReleased)
|
||||||
|
@ -4371,7 +4582,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var startTimestamp:Float = 0;
|
var startTimestamp:Float = 0;
|
||||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||||
|
|
||||||
var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
var targetSong:Song;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
this.error("Could Not Playtest", 'Got an error trying to playtest the song.\n${e}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Rework asset system so we can remove this.
|
// TODO: Rework asset system so we can remove this.
|
||||||
switch (currentSongStage)
|
switch (currentSongStage)
|
||||||
|
@ -4415,6 +4635,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Override music.
|
// Override music.
|
||||||
if (audioInstTrack != null) FlxG.sound.music = audioInstTrack;
|
if (audioInstTrack != null) FlxG.sound.music = audioInstTrack;
|
||||||
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
||||||
|
|
||||||
|
this.persistentUpdate = false;
|
||||||
|
this.persistentDraw = false;
|
||||||
stopWelcomeMusic();
|
stopWelcomeMusic();
|
||||||
openSubState(targetState);
|
openSubState(targetState);
|
||||||
}
|
}
|
||||||
|
@ -4530,7 +4753,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
function playMetronomeTick(high:Bool = false):Void
|
function playMetronomeTick(high:Bool = false):Void
|
||||||
{
|
{
|
||||||
this.playSound(Paths.sound('chartingSounds/metronome${high ? '1' : '2'}'));
|
this.playSound(Paths.sound('chartingSounds/metronome${high ? '1' : '2'}'), metronomeVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchToCurrentInstrumental():Void
|
function switchToCurrentInstrumental():Void
|
||||||
|
@ -4629,6 +4852,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var prevDifficulty = availableDifficulties[availableDifficulties.length - 1];
|
var prevDifficulty = availableDifficulties[availableDifficulties.length - 1];
|
||||||
selectedDifficulty = prevDifficulty;
|
selectedDifficulty = prevDifficulty;
|
||||||
|
|
||||||
|
Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges);
|
||||||
|
|
||||||
refreshDifficultyTreeSelection();
|
refreshDifficultyTreeSelection();
|
||||||
refreshMetadataToolbox();
|
refreshMetadataToolbox();
|
||||||
}
|
}
|
||||||
|
@ -4747,6 +4972,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
@:nullSafety(Off)
|
@:nullSafety(Off)
|
||||||
function resetConductorAfterTest(_:FlxSubState = null):Void
|
function resetConductorAfterTest(_:FlxSubState = null):Void
|
||||||
{
|
{
|
||||||
|
this.persistentUpdate = true;
|
||||||
|
this.persistentDraw = true;
|
||||||
|
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
|
|
||||||
// Reapply the volume.
|
// Reapply the volume.
|
||||||
|
@ -5026,9 +5254,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
switch (noteData.getStrumlineIndex())
|
switch (noteData.getStrumlineIndex())
|
||||||
{
|
{
|
||||||
case 0: // Player
|
case 0: // Player
|
||||||
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'));
|
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume);
|
||||||
case 1: // Opponent
|
case 1: // Opponent
|
||||||
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'));
|
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package funkin.ui.debug.charting.commands;
|
||||||
|
|
||||||
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command which changes the starting BPM of the song.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||||
|
class ChangeStartingBPMCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
var targetBPM:Float;
|
||||||
|
|
||||||
|
var previousBPM:Float = 100;
|
||||||
|
|
||||||
|
public function new(targetBPM:Float)
|
||||||
|
{
|
||||||
|
this.targetBPM = targetBPM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||||
|
if (timeChanges == null || timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
previousBPM = 100;
|
||||||
|
timeChanges = [new SongTimeChange(0, targetBPM)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
previousBPM = timeChanges[0].bpm;
|
||||||
|
timeChanges[0].bpm = targetBPM;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentSongMetadata.timeChanges = timeChanges;
|
||||||
|
|
||||||
|
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||||
|
if (timeChanges == null || timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
timeChanges = [new SongTimeChange(0, previousBPM)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timeChanges[0].bpm = previousBPM;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentSongMetadata.timeChanges = timeChanges;
|
||||||
|
|
||||||
|
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Change Starting BPM to ${targetBPM}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,10 +32,14 @@ class PasteItemsCommand implements ChartEditorCommand
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace(currentClipboard.notes);
|
var stepEndOfSong:Float = Conductor.getTimeInSteps(state.songLengthInMs);
|
||||||
|
var stepCutoff:Float = stepEndOfSong - 1.0;
|
||||||
|
var msCutoff:Float = Conductor.getStepTimeInMs(stepCutoff);
|
||||||
|
|
||||||
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
||||||
|
addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff);
|
||||||
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
||||||
|
addedEvents = SongDataUtils.clampSongEventData(addedEvents, 0.0, msCutoff);
|
||||||
|
|
||||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
|
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
|
||||||
|
|
|
@ -211,7 +211,7 @@ class ChartEditorAudioHandler
|
||||||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
||||||
* @param path The path to the sound effect. Use `Paths` to build this.
|
* @param path The path to the sound effect. Use `Paths` to build this.
|
||||||
*/
|
*/
|
||||||
public static function playSound(_state:ChartEditorState, path:String):Void
|
public static function playSound(_state:ChartEditorState, path:String, volume:Float = 1.0):Void
|
||||||
{
|
{
|
||||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||||
|
@ -223,6 +223,7 @@ class ChartEditorAudioHandler
|
||||||
snd.loadEmbedded(asset);
|
snd.loadEmbedded(asset);
|
||||||
snd.autoDestroy = true;
|
snd.autoDestroy = true;
|
||||||
FlxG.sound.list.add(snd);
|
FlxG.sound.list.add(snd);
|
||||||
|
snd.volume = volume;
|
||||||
snd.play();
|
snd.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,12 +110,12 @@ class ChartEditorDialogHandler
|
||||||
{
|
{
|
||||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT, true, true);
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT, true, true);
|
||||||
if (dialog == null) throw 'Could not locate Backup Available dialog';
|
if (dialog == null) throw 'Could not locate Backup Available dialog';
|
||||||
dialog.onDialogClosed = function(_event) {
|
dialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// User loaded the backup! Close the welcome dialog behind this.
|
// User loaded the backup! Close the welcome dialog behind this.
|
||||||
if (welcomeDialog != null) welcomeDialog.hideDialog(DialogButton.CANCEL);
|
if (welcomeDialog != null) welcomeDialog.hideDialog(DialogButton.APPLY);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -137,22 +137,22 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Backup Available dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Backup Available dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
// Don't hide the welcome dialog behind this.
|
// Don't hide the welcome dialog behind this.
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonGoToFolder:Null<Button> = dialog.findComponent('buttonGoToFolder', Button);
|
var buttonGoToFolder:Null<Button> = dialog.findComponent('buttonGoToFolder', Button);
|
||||||
if (buttonGoToFolder == null) throw 'Could not locate buttonGoToFolder button in Backup Available dialog';
|
if (buttonGoToFolder == null) throw 'Could not locate buttonGoToFolder button in Backup Available dialog';
|
||||||
buttonGoToFolder.onClick = function(_event) {
|
buttonGoToFolder.onClick = function(_) {
|
||||||
state.openBackupsFolder();
|
state.openBackupsFolder();
|
||||||
// Don't hide the welcome dialog behind this.
|
// Don't hide the welcome dialog behind this.
|
||||||
// dialog.hideDialog(DialogButton.CANCEL);
|
// Don't close this dialog.
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonOpenBackup:Null<Button> = dialog.findComponent('buttonOpenBackup', Button);
|
var buttonOpenBackup:Null<Button> = dialog.findComponent('buttonOpenBackup', Button);
|
||||||
if (buttonOpenBackup == null) throw 'Could not locate buttonOpenBackup button in Backup Available dialog';
|
if (buttonOpenBackup == null) throw 'Could not locate buttonOpenBackup button in Backup Available dialog';
|
||||||
buttonOpenBackup.onClick = function(_event) {
|
buttonOpenBackup.onClick = function(_) {
|
||||||
var latestBackupPath:Null<String> = ChartEditorImportExportHandler.getLatestBackupPath();
|
var latestBackupPath:Null<String> = ChartEditorImportExportHandler.getLatestBackupPath();
|
||||||
|
|
||||||
var result:Null<Array<String>> = (latestBackupPath != null) ? state.loadFromFNFCPath(latestBackupPath) : null;
|
var result:Null<Array<String>> = (latestBackupPath != null) ? state.loadFromFNFCPath(latestBackupPath) : null;
|
||||||
|
@ -210,20 +210,20 @@ class ChartEditorDialogHandler
|
||||||
// Open the "Open Chart" wizard
|
// Open the "Open Chart" wizard
|
||||||
// Step 1. Open Chart
|
// Step 1. Open Chart
|
||||||
var openChartDialog:Dialog = openChartDialog(state);
|
var openChartDialog:Dialog = openChartDialog(state);
|
||||||
openChartDialog.onDialogClosed = function(_event) {
|
openChartDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Upload instrumental
|
// Step 2. Upload instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
uploadVocalsDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
state.currentWorkingFilePath = null; // Built from parts, so no .fnfc to save to.
|
state.currentWorkingFilePath = null; // Built from parts, so no .fnfc to save to.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
@ -251,20 +251,20 @@ class ChartEditorDialogHandler
|
||||||
// Step 1. Open Chart
|
// Step 1. Open Chart
|
||||||
var openChartDialog:Null<Dialog> = openImportChartDialog(state, format);
|
var openChartDialog:Null<Dialog> = openImportChartDialog(state, format);
|
||||||
if (openChartDialog == null) throw 'Could not locate Import Chart dialog';
|
if (openChartDialog == null) throw 'Could not locate Import Chart dialog';
|
||||||
openChartDialog.onDialogClosed = function(_event) {
|
openChartDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Upload instrumental
|
// Step 2. Upload instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
state.currentWorkingFilePath = null; // New file, so no path.
|
state.currentWorkingFilePath = null; // New file, so no path.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
@ -289,21 +289,21 @@ class ChartEditorDialogHandler
|
||||||
public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void
|
public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void
|
||||||
{
|
{
|
||||||
// Step 1. Song Metadata
|
// Step 1. Song Metadata
|
||||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION);
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION, true);
|
||||||
songMetadataDialog.onDialogClosed = function(_event) {
|
songMetadataDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Upload Instrumental
|
// Step 2. Upload Instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
state.currentWorkingFilePath = null; // New file, so no path.
|
state.currentWorkingFilePath = null; // New file, so no path.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
@ -328,21 +328,21 @@ class ChartEditorDialogHandler
|
||||||
public static function openCreateSongWizardErectOnly(state:ChartEditorState, closable:Bool):Void
|
public static function openCreateSongWizardErectOnly(state:ChartEditorState, closable:Bool):Void
|
||||||
{
|
{
|
||||||
// Step 1. Song Metadata
|
// Step 1. Song Metadata
|
||||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state, true, Constants.DEFAULT_VARIATION);
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, true, Constants.DEFAULT_VARIATION, true);
|
||||||
songMetadataDialog.onDialogClosed = function(_event) {
|
songMetadataDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Upload Instrumental
|
// Step 2. Upload Instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
state.currentWorkingFilePath = null; // New file, so no path.
|
state.currentWorkingFilePath = null; // New file, so no path.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
@ -367,41 +367,41 @@ class ChartEditorDialogHandler
|
||||||
public static function openCreateSongWizardBasicErect(state:ChartEditorState, closable:Bool):Void
|
public static function openCreateSongWizardBasicErect(state:ChartEditorState, closable:Bool):Void
|
||||||
{
|
{
|
||||||
// Step 1. Song Metadata
|
// Step 1. Song Metadata
|
||||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION);
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION, true);
|
||||||
songMetadataDialog.onDialogClosed = function(_event) {
|
songMetadataDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Upload Instrumental
|
// Step 2. Upload Instrumental
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialog.onDialogClosed = function(_event) {
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
// Step 4. Song Metadata (Erect)
|
// Step 4. Song Metadata (Erect)
|
||||||
var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, true, 'erect');
|
var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, true, 'erect', false);
|
||||||
songMetadataDialogErect.onDialogClosed = function(_event) {
|
songMetadataDialogErect.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Switch to the Erect variation so uploading the instrumental applies properly.
|
// Switch to the Erect variation so uploading the instrumental applies properly.
|
||||||
state.selectedVariation = 'erect';
|
state.selectedVariation = 'erect';
|
||||||
|
|
||||||
// Step 5. Upload Instrumental (Erect)
|
// Step 5. Upload Instrumental (Erect)
|
||||||
var uploadInstDialogErect:Dialog = openUploadInstDialog(state, closable);
|
var uploadInstDialogErect:Dialog = openUploadInstDialog(state, closable);
|
||||||
uploadInstDialogErect.onDialogClosed = function(_event) {
|
uploadInstDialogErect.onDialogClosed = function(event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 6. Upload Vocals (Erect)
|
// Step 6. Upload Vocals (Erect)
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialogErect.onDialogClosed = function(_event) {
|
uploadVocalsDialogErect.onDialogClosed = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
state.currentWorkingFilePath = null; // New file, so no path.
|
state.currentWorkingFilePath = null; // New file, so no path.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
@ -453,19 +453,19 @@ class ChartEditorDialogHandler
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Instrumental dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Instrumental dialog';
|
||||||
|
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
var instrumentalBox:Null<Box> = dialog.findComponent('instrumentalBox', Box);
|
var instrumentalBox:Null<Box> = dialog.findComponent('instrumentalBox', Box);
|
||||||
if (instrumentalBox == null) throw 'Could not locate instrumentalBox in Upload Instrumental dialog';
|
if (instrumentalBox == null) throw 'Could not locate instrumentalBox in Upload Instrumental dialog';
|
||||||
|
|
||||||
instrumentalBox.onMouseOver = function(_event) {
|
instrumentalBox.onMouseOver = function(_) {
|
||||||
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
|
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
Cursor.cursorMode = Pointer;
|
Cursor.cursorMode = Pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
instrumentalBox.onMouseOut = function(_event) {
|
instrumentalBox.onMouseOut = function(_) {
|
||||||
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
|
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
@ -474,7 +474,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var dropHandler:DialogDropTarget = {component: instrumentalBox, handler: null};
|
var dropHandler:DialogDropTarget = {component: instrumentalBox, handler: null};
|
||||||
|
|
||||||
instrumentalBox.onClick = function(_event) {
|
instrumentalBox.onClick = function(_) {
|
||||||
Dialogs.openBinaryFile('Open Instrumental', [
|
Dialogs.openBinaryFile('Open Instrumental', [
|
||||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
|
@ -533,10 +533,13 @@ class ChartEditorDialogHandler
|
||||||
/**
|
/**
|
||||||
* Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM.
|
* Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM.
|
||||||
* @param state The ChartEditorState instance.
|
* @param state The ChartEditorState instance.
|
||||||
|
* @param erect Whether to create erect difficulties or normal ones.
|
||||||
|
* @param targetVariation The variation to create difficulties for.
|
||||||
|
* @param clearExistingMetadata Whether to clear existing metadata when confirming.
|
||||||
* @return The dialog to open.
|
* @return The dialog to open.
|
||||||
*/
|
*/
|
||||||
@:haxe.warning("-WVarInit")
|
@:haxe.warning("-WVarInit")
|
||||||
public static function openSongMetadataDialog(state:ChartEditorState, erect:Bool, targetVariation:String):Dialog
|
public static function openSongMetadataDialog(state:ChartEditorState, erect:Bool, targetVariation:String, clearExistingMetadata:Bool):Dialog
|
||||||
{
|
{
|
||||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||||
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
||||||
|
@ -549,7 +552,7 @@ class ChartEditorDialogHandler
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
||||||
state.isHaxeUIDialogOpen = true;
|
state.isHaxeUIDialogOpen = true;
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
@ -661,8 +664,12 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||||
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
||||||
dialogContinue.onClick = (_event) -> {
|
dialogContinue.onClick = (_) -> {
|
||||||
if (targetVariation == Constants.DEFAULT_VARIATION) state.songMetadata.clear();
|
if (clearExistingMetadata)
|
||||||
|
{
|
||||||
|
state.songMetadata.clear();
|
||||||
|
state.songChartData.clear();
|
||||||
|
}
|
||||||
|
|
||||||
state.songMetadata.set(targetVariation, newSongMetadata);
|
state.songMetadata.set(targetVariation, newSongMetadata);
|
||||||
|
|
||||||
|
@ -702,13 +709,13 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Vocals dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Vocals dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialogNoVocals:Null<Button> = dialog.findComponent('dialogNoVocals', Button);
|
var dialogNoVocals:Null<Button> = dialog.findComponent('dialogNoVocals', Button);
|
||||||
if (dialogNoVocals == null) throw 'Could not locate dialogNoVocals button in Upload Vocals dialog';
|
if (dialogNoVocals == null) throw 'Could not locate dialogNoVocals button in Upload Vocals dialog';
|
||||||
dialogNoVocals.onClick = function(_event) {
|
dialogNoVocals.onClick = function(_) {
|
||||||
// Dismiss
|
// Dismiss
|
||||||
state.wipeVocalData();
|
state.wipeVocalData();
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
@ -820,7 +827,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||||
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Upload Vocals dialog';
|
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Upload Vocals dialog';
|
||||||
dialogContinue.onClick = function(_event) {
|
dialogContinue.onClick = function(_) {
|
||||||
// Dismiss
|
// Dismiss
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
};
|
};
|
||||||
|
@ -842,7 +849,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Open Chart dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Open Chart dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,7 +863,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
var buttonContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||||
if (buttonContinue == null) throw 'Could not locate dialogContinue button in Open Chart dialog';
|
if (buttonContinue == null) throw 'Could not locate dialogContinue button in Open Chart dialog';
|
||||||
buttonContinue.onClick = function(_event) {
|
buttonContinue.onClick = function(_) {
|
||||||
state.loadSong(songMetadata, songChartData);
|
state.loadSong(songMetadata, songChartData);
|
||||||
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
@ -904,11 +911,11 @@ class ChartEditorDialogHandler
|
||||||
songVariationMetadataEntryLabel.text = 'Click to browse for <song>-metadata-${variation}.json file.';
|
songVariationMetadataEntryLabel.text = 'Click to browse for <song>-metadata-${variation}.json file.';
|
||||||
#end
|
#end
|
||||||
|
|
||||||
songVariationMetadataEntry.onMouseOver = function(_event) {
|
songVariationMetadataEntry.onMouseOver = function(_) {
|
||||||
songVariationMetadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
songVariationMetadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
Cursor.cursorMode = Pointer;
|
Cursor.cursorMode = Pointer;
|
||||||
}
|
}
|
||||||
songVariationMetadataEntry.onMouseOut = function(_event) {
|
songVariationMetadataEntry.onMouseOut = function(_) {
|
||||||
songVariationMetadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
songVariationMetadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
@ -928,11 +935,11 @@ class ChartEditorDialogHandler
|
||||||
songVariationChartDataEntryLabel.text = 'Click to browse for <song>-chart-${variation}.json file.';
|
songVariationChartDataEntryLabel.text = 'Click to browse for <song>-chart-${variation}.json file.';
|
||||||
#end
|
#end
|
||||||
|
|
||||||
songVariationChartDataEntry.onMouseOver = function(_event) {
|
songVariationChartDataEntry.onMouseOver = function(_) {
|
||||||
songVariationChartDataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
songVariationChartDataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
Cursor.cursorMode = Pointer;
|
Cursor.cursorMode = Pointer;
|
||||||
}
|
}
|
||||||
songVariationChartDataEntry.onMouseOut = function(_event) {
|
songVariationChartDataEntry.onMouseOut = function(_) {
|
||||||
songVariationChartDataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
songVariationChartDataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
@ -982,7 +989,7 @@ class ChartEditorDialogHandler
|
||||||
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickMetadataVariation = function(variation:String, label:Label, _event:UIEvent) {
|
onClickMetadataVariation = function(variation:String, label:Label, _:UIEvent) {
|
||||||
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
||||||
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
|
@ -1066,7 +1073,7 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickChartDataVariation = function(variation:String, label:Label, _event:UIEvent) {
|
onClickChartDataVariation = function(variation:String, label:Label, _:UIEvent) {
|
||||||
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
||||||
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
|
@ -1122,7 +1129,7 @@ class ChartEditorDialogHandler
|
||||||
metadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
metadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
Cursor.cursorMode = Pointer;
|
Cursor.cursorMode = Pointer;
|
||||||
}
|
}
|
||||||
metadataEntry.onMouseOut = function(_event) {
|
metadataEntry.onMouseOut = function(_) {
|
||||||
metadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
metadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
@ -1162,7 +1169,7 @@ class ChartEditorDialogHandler
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
||||||
|
|
||||||
state.isHaxeUIDialogOpen = true;
|
state.isHaxeUIDialogOpen = true;
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
@ -1170,18 +1177,18 @@ class ChartEditorDialogHandler
|
||||||
var importBox:Null<Box> = dialog.findComponent('importBox', Box);
|
var importBox:Null<Box> = dialog.findComponent('importBox', Box);
|
||||||
if (importBox == null) throw 'Could not locate importBox in Import Chart dialog';
|
if (importBox == null) throw 'Could not locate importBox in Import Chart dialog';
|
||||||
|
|
||||||
importBox.onMouseOver = function(_event) {
|
importBox.onMouseOver = function(_) {
|
||||||
importBox.swapClass('upload-bg', 'upload-bg-hover');
|
importBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||||
Cursor.cursorMode = Pointer;
|
Cursor.cursorMode = Pointer;
|
||||||
}
|
}
|
||||||
importBox.onMouseOut = function(_event) {
|
importBox.onMouseOut = function(_) {
|
||||||
importBox.swapClass('upload-bg-hover', 'upload-bg');
|
importBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
var onDropFile:String->Void;
|
var onDropFile:String->Void;
|
||||||
|
|
||||||
importBox.onClick = function(_event) {
|
importBox.onClick = function(_) {
|
||||||
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', fileFilter != null ? [fileFilter] : [], function(selectedFile:SelectedFileInfo) {
|
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', fileFilter != null ? [fileFilter] : [], function(selectedFile:SelectedFileInfo) {
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
{
|
{
|
||||||
|
@ -1251,13 +1258,13 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Variation dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Variation dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
||||||
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Variation dialog';
|
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Variation dialog';
|
||||||
buttonAdd.onClick = function(_event) {
|
buttonAdd.onClick = function(_) {
|
||||||
// This performs validation before the onSubmit callback is called.
|
// This performs validation before the onSubmit callback is called.
|
||||||
variationForm.submit();
|
variationForm.submit();
|
||||||
}
|
}
|
||||||
|
@ -1296,12 +1303,13 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper);
|
var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper);
|
||||||
if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Add Variation dialog';
|
if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Add Variation dialog';
|
||||||
dialogBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
var currentStartingBPM:Float = state.currentSongMetadata.timeChanges[0].bpm;
|
||||||
|
dialogBPM.value = currentStartingBPM;
|
||||||
|
|
||||||
// If all validators succeeded, this callback is called.
|
// If all validators succeeded, this callback is called.
|
||||||
|
|
||||||
state.isHaxeUIDialogOpen = true;
|
state.isHaxeUIDialogOpen = true;
|
||||||
variationForm.onSubmit = function(_event) {
|
variationForm.onSubmit = function(_) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
trace('Add Variation dialog submitted, validation succeeded!');
|
trace('Add Variation dialog submitted, validation succeeded!');
|
||||||
|
|
||||||
|
@ -1317,6 +1325,8 @@ class ChartEditorDialogHandler
|
||||||
state.songMetadata.set(pendingVariation.variation, pendingVariation);
|
state.songMetadata.set(pendingVariation.variation, pendingVariation);
|
||||||
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
||||||
|
|
||||||
|
// Don't update conductor since we haven't switched to the new variation yet.
|
||||||
|
|
||||||
state.success('Add Variation', 'Added new variation "${pendingVariation.variation}"');
|
state.success('Add Variation', 'Added new variation "${pendingVariation.variation}"');
|
||||||
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
@ -1341,13 +1351,13 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Difficulty dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Difficulty dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
||||||
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Difficulty dialog';
|
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Difficulty dialog';
|
||||||
buttonAdd.onClick = function(_event) {
|
buttonAdd.onClick = function(_) {
|
||||||
// This performs validation before the onSubmit callback is called.
|
// This performs validation before the onSubmit callback is called.
|
||||||
difficultyForm.submit();
|
difficultyForm.submit();
|
||||||
}
|
}
|
||||||
|
@ -1367,7 +1377,7 @@ class ChartEditorDialogHandler
|
||||||
inputScrollSpeed.value = state.currentSongChartScrollSpeed;
|
inputScrollSpeed.value = state.currentSongChartScrollSpeed;
|
||||||
labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x';
|
labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x';
|
||||||
|
|
||||||
difficultyForm.onSubmit = function(_event) {
|
difficultyForm.onSubmit = function(_) {
|
||||||
trace('Add Difficulty dialog submitted, validation succeeded!');
|
trace('Add Difficulty dialog submitted, validation succeeded!');
|
||||||
|
|
||||||
var dialogDifficultyName:Null<TextField> = dialog.findComponent('dialogDifficultyName', TextField);
|
var dialogDifficultyName:Null<TextField> = dialog.findComponent('dialogDifficultyName', TextField);
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ChartEditorNotificationHandler
|
||||||
*/
|
*/
|
||||||
public static function success(state:ChartEditorState, title:String, body:String):Notification
|
public static function success(state:ChartEditorState, title:String, body:String):Notification
|
||||||
{
|
{
|
||||||
return sendNotification(title, body, NotificationType.Success);
|
return sendNotification(state, title, body, NotificationType.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +30,7 @@ class ChartEditorNotificationHandler
|
||||||
*/
|
*/
|
||||||
public static function warning(state:ChartEditorState, title:String, body:String):Notification
|
public static function warning(state:ChartEditorState, title:String, body:String):Notification
|
||||||
{
|
{
|
||||||
return sendNotification(title, body, NotificationType.Warning);
|
return sendNotification(state, title, body, NotificationType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@ class ChartEditorNotificationHandler
|
||||||
*/
|
*/
|
||||||
public static function error(state:ChartEditorState, title:String, body:String):Notification
|
public static function error(state:ChartEditorState, title:String, body:String):Notification
|
||||||
{
|
{
|
||||||
return sendNotification(title, body, NotificationType.Error);
|
return sendNotification(state, title, body, NotificationType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,7 +66,7 @@ class ChartEditorNotificationHandler
|
||||||
*/
|
*/
|
||||||
public static function info(state:ChartEditorState, title:String, body:String):Notification
|
public static function info(state:ChartEditorState, title:String, body:String):Notification
|
||||||
{
|
{
|
||||||
return sendNotification(title, body, NotificationType.Info);
|
return sendNotification(state, title, body, NotificationType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +79,7 @@ class ChartEditorNotificationHandler
|
||||||
*/
|
*/
|
||||||
public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array<NotificationAction>):Notification
|
public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array<NotificationAction>):Notification
|
||||||
{
|
{
|
||||||
return sendNotification(title, body, NotificationType.Info, actions);
|
return sendNotification(state, title, body, NotificationType.Info, actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +101,7 @@ class ChartEditorNotificationHandler
|
||||||
NotificationManager.instance.removeNotification(notif);
|
NotificationManager.instance.removeNotification(notif);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function sendNotification(title:String, body:String, ?type:NotificationType, ?actions:Array<NotificationAction>):Notification
|
static function sendNotification(state:ChartEditorState, title:String, body:String, ?type:NotificationType, ?actions:Array<NotificationAction>):Notification
|
||||||
{
|
{
|
||||||
#if !mac
|
#if !mac
|
||||||
var actionNames:Array<String> = actions == null ? [] : actions.map(action -> action.text);
|
var actionNames:Array<String> = actions == null ? [] : actions.map(action -> action.text);
|
||||||
|
@ -127,6 +127,8 @@ class ChartEditorNotificationHandler
|
||||||
if (action != null && action.callback != null)
|
if (action != null && action.callback != null)
|
||||||
{
|
{
|
||||||
button.onClick = function(_) {
|
button.onClick = function(_) {
|
||||||
|
// Don't allow actions to be clicked while the playtest is open.
|
||||||
|
if (state.subState != null) return;
|
||||||
action.callback();
|
action.callback();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -137,6 +139,8 @@ class ChartEditorNotificationHandler
|
||||||
|
|
||||||
return notif;
|
return notif;
|
||||||
#else
|
#else
|
||||||
|
// TODO: Implement notifications on Mac OS OR... make sure the null is handled properly on mac?
|
||||||
|
return null;
|
||||||
trace('WARNING: Notifications are not supported on Mac OS.');
|
trace('WARNING: Notifications are not supported on Mac OS.');
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.play.event.SongEvent;
|
import funkin.play.event.SongEvent;
|
||||||
import funkin.data.event.SongEventData;
|
import funkin.data.event.SongEventData;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
|
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.play.character.CharacterData;
|
import funkin.play.character.CharacterData;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
@ -610,19 +611,12 @@ class ChartEditorToolboxHandler
|
||||||
inputBPM.onChange = function(event:UIEvent) {
|
inputBPM.onChange = function(event:UIEvent) {
|
||||||
if (event.value == null || event.value <= 0) return;
|
if (event.value == null || event.value <= 0) return;
|
||||||
|
|
||||||
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
// Use a command so we can undo/redo this action.
|
||||||
if (timeChanges == null || timeChanges.length == 0)
|
var startingBPM = state.currentSongMetadata.timeChanges[0].bpm;
|
||||||
|
if (event.value != startingBPM)
|
||||||
{
|
{
|
||||||
timeChanges = [new SongTimeChange(0, event.value)];
|
state.performCommand(new ChangeStartingBPMCommand(event.value));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
timeChanges[0].bpm = event.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.currentSongMetadata.timeChanges = timeChanges;
|
|
||||||
|
|
||||||
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
|
||||||
};
|
};
|
||||||
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
||||||
|
|
||||||
|
|
|
@ -192,15 +192,15 @@ class LatencyState extends MusicBeatSubState
|
||||||
|
|
||||||
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
|
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
|
||||||
|
|
||||||
Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset);
|
Conductor.update(swagSong.getTimeWithDiff() - Conductor.inputOffset);
|
||||||
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
|
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
|
||||||
|
|
||||||
songPosVis.x = songPosToX(Conductor.songPosition);
|
songPosVis.x = songPosToX(Conductor.songPosition);
|
||||||
songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.audioOffset);
|
songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.instrumentalOffset);
|
||||||
songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.visualOffset);
|
songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.inputOffset);
|
||||||
|
|
||||||
offsetText.text = "AUDIO Offset: " + Conductor.audioOffset + "ms";
|
offsetText.text = "INST Offset: " + Conductor.instrumentalOffset + "ms";
|
||||||
offsetText.text += "\nVIDOE Offset: " + Conductor.visualOffset + "ms";
|
offsetText.text += "\nINPUT Offset: " + Conductor.inputOffset + "ms";
|
||||||
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
|
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
|
||||||
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
|
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
|
||||||
|
|
||||||
|
@ -221,24 +221,24 @@ class LatencyState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (FlxG.keys.justPressed.RIGHT)
|
if (FlxG.keys.justPressed.RIGHT)
|
||||||
{
|
{
|
||||||
Conductor.audioOffset += 1 * multiply;
|
Conductor.instrumentalOffset += 1.0 * multiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT)
|
if (FlxG.keys.justPressed.LEFT)
|
||||||
{
|
{
|
||||||
Conductor.audioOffset -= 1 * multiply;
|
Conductor.instrumentalOffset -= 1.0 * multiply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FlxG.keys.justPressed.RIGHT)
|
if (FlxG.keys.justPressed.RIGHT)
|
||||||
{
|
{
|
||||||
Conductor.visualOffset += 1 * multiply;
|
Conductor.inputOffset += 1.0 * multiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT)
|
if (FlxG.keys.justPressed.LEFT)
|
||||||
{
|
{
|
||||||
Conductor.visualOffset -= 1 * multiply;
|
Conductor.inputOffset -= 1.0 * multiply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ class LatencyState extends MusicBeatSubState
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
noteGrp.forEach(function(daNote:NoteSprite) {
|
noteGrp.forEach(function(daNote:NoteSprite) {
|
||||||
daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.audioOffset) - daNote.noteData.time) * 0.45);
|
daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.instrumentalOffset) - daNote.noteData.time) * 0.45);
|
||||||
daNote.x = strumLine.x + 30;
|
daNote.x = strumLine.x + 30;
|
||||||
|
|
||||||
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
|
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
|
||||||
|
@ -271,7 +271,7 @@ class LatencyState extends MusicBeatSubState
|
||||||
|
|
||||||
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
|
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
|
||||||
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
|
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
|
||||||
getDiff -= Conductor.visualOffset;
|
getDiff -= Conductor.inputOffset;
|
||||||
|
|
||||||
// lil fix for end of song
|
// lil fix for end of song
|
||||||
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
|
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.util.macro;
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
#if !display
|
||||||
#if macro
|
#if macro
|
||||||
class FlxMacro
|
class FlxMacro
|
||||||
{
|
{
|
||||||
|
@ -33,3 +34,4 @@ class FlxMacro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.util.macro;
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
#if !display
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if (debug || FORCE_DEBUG_VERSION)
|
||||||
class GitCommit
|
class GitCommit
|
||||||
{
|
{
|
||||||
|
@ -65,3 +66,4 @@ class GitCommit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
#end
|
||||||
|
|
Loading…
Reference in a new issue