mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-21 09:29:41 +00:00
Merge branch 'feature/chart-editor-events'
This commit is contained in:
commit
e922c026a9
15
Project.xml
15
Project.xml
|
@ -151,16 +151,23 @@
|
||||||
|
|
||||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
||||||
<haxeflag name="-dce no" />
|
<haxeflag name="-dce no" />
|
||||||
|
|
||||||
|
<!-- Ensure all Funkin' classes are available at runtime. -->
|
||||||
<haxeflag name="--macro" value="include('funkin')" />
|
<haxeflag name="--macro" value="include('funkin')" />
|
||||||
|
|
||||||
<!-- Ensure all UI components are available at runtime. -->
|
<!-- Ensure all UI components are available at runtime. -->
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||||
|
|
||||||
<!-- Ensure all UI components are available at runtime. -->
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ensure additional class packages are available at runtime (some only really used by scripts).
|
||||||
|
Ignore packages we can't include.
|
||||||
|
-->
|
||||||
|
<haxeflag name="--macro" value="include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Necessary to provide stack traces for HScript. -->
|
<!-- Necessary to provide stack traces for HScript. -->
|
||||||
<haxedef name="hscriptPos" />
|
<haxedef name="hscriptPos" />
|
||||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
"lineEnds": {
|
"lineEnds": {
|
||||||
"leftCurly": "both",
|
"leftCurly": "both",
|
||||||
"rightCurly": "both",
|
"rightCurly": "both",
|
||||||
"emptyCurly": "break",
|
"emptyCurly": "noBreak",
|
||||||
"objectLiteralCurly": {
|
"objectLiteralCurly": {
|
||||||
"leftCurly": "after"
|
"leftCurly": "after"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"indentation": {
|
||||||
|
"character": " "
|
||||||
|
},
|
||||||
"sameLine": {
|
"sameLine": {
|
||||||
"ifElse": "next",
|
"ifElse": "next",
|
||||||
"doWhile": "next",
|
"doWhile": "next",
|
||||||
|
|
|
@ -7,297 +7,295 @@ import funkin.play.song.SongData.SongTimeChange;
|
||||||
|
|
||||||
typedef BPMChangeEvent =
|
typedef BPMChangeEvent =
|
||||||
{
|
{
|
||||||
var stepTime:Int;
|
var stepTime:Int;
|
||||||
var songTime:Float;
|
var songTime:Float;
|
||||||
var bpm:Float;
|
var bpm:Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Conductor
|
class Conductor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The list of time changes in the song.
|
* The list of time changes in the song.
|
||||||
* There should be at least one time change (at the beginning of the song) to define the BPM.
|
* There should be at least one time change (at the beginning of the song) to define the BPM.
|
||||||
*/
|
*/
|
||||||
private static var timeChanges:Array<SongTimeChange> = [];
|
private static var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current time change.
|
* The current time change.
|
||||||
*/
|
*/
|
||||||
private static var currentTimeChange:SongTimeChange;
|
private static var currentTimeChange:SongTimeChange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current position in the song in milliseconds.
|
* The current position in the song in milliseconds.
|
||||||
* Updated every frame based on the audio position.
|
* Updated every frame based on the audio position.
|
||||||
*/
|
*/
|
||||||
public static var songPosition:Float;
|
public static var songPosition:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beats per minute of the current song at the current time.
|
* Beats per minute of the current song at the current time.
|
||||||
*/
|
*/
|
||||||
public static var bpm(get, null):Float;
|
public static var bpm(get, null):Float;
|
||||||
|
|
||||||
static function get_bpm():Float
|
static function get_bpm():Float
|
||||||
{
|
{
|
||||||
if (bpmOverride != null)
|
if (bpmOverride != null)
|
||||||
return bpmOverride;
|
return bpmOverride;
|
||||||
|
|
||||||
if (currentTimeChange == null)
|
if (currentTimeChange == null)
|
||||||
return 100;
|
return 100;
|
||||||
|
|
||||||
return currentTimeChange.bpm;
|
return currentTimeChange.bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
static var bpmOverride:Null<Float> = null;
|
static var bpmOverride:Null<Float> = null;
|
||||||
|
|
||||||
// OLD, replaced with timeChanges.
|
// OLD, replaced with timeChanges.
|
||||||
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a beat in millisecond. Calculated based on bpm.
|
* Duration of a beat in millisecond. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var crochet(get, null):Float;
|
public static var crochet(get, null):Float;
|
||||||
|
|
||||||
static function get_crochet():Float
|
static function get_crochet():Float
|
||||||
{
|
{
|
||||||
return ((60 / bpm) * 1000);
|
return ((60 / bpm) * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
|
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var stepCrochet(get, null):Float;
|
public static var stepCrochet(get, null):Float;
|
||||||
|
|
||||||
static function get_stepCrochet():Float
|
static function get_stepCrochet():Float
|
||||||
{
|
{
|
||||||
return crochet / timeSignatureNumerator;
|
return crochet / timeSignatureNumerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var timeSignatureNumerator(get, null):Int;
|
public static var timeSignatureNumerator(get, null):Int;
|
||||||
|
|
||||||
static function get_timeSignatureNumerator():Int
|
static function get_timeSignatureNumerator():Int
|
||||||
{
|
{
|
||||||
if (currentTimeChange == null)
|
if (currentTimeChange == null)
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
return currentTimeChange.timeSignatureNum;
|
return currentTimeChange.timeSignatureNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var timeSignatureDenominator(get, null):Int;
|
public static var timeSignatureDenominator(get, null):Int;
|
||||||
|
|
||||||
static function get_timeSignatureDenominator():Int
|
static function get_timeSignatureDenominator():Int
|
||||||
{
|
{
|
||||||
if (currentTimeChange == null)
|
if (currentTimeChange == null)
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
return currentTimeChange.timeSignatureDen;
|
return currentTimeChange.timeSignatureDen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current position in the song, in beats.
|
* Current position in the song, in beats.
|
||||||
**/
|
**/
|
||||||
public static var currentBeat(default, null):Int;
|
public static var currentBeat(default, null):Int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current position in the song, in steps.
|
* Current position in the song, in steps.
|
||||||
*/
|
*/
|
||||||
public static var currentStep(default, null):Int;
|
public static var currentStep(default, null):Int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current position in the song, in steps and fractions of a step.
|
* Current position in the song, in steps and fractions of a step.
|
||||||
*/
|
*/
|
||||||
public static var currentStepTime(default, null):Float;
|
public static var currentStepTime(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 visualOffset:Float = 0;
|
||||||
public static var audioOffset:Float = 0;
|
public static var audioOffset:Float = 0;
|
||||||
public static var offset:Float = 0;
|
public static var offset:Float = 0;
|
||||||
|
|
||||||
// TODO: Add code to update this.
|
// TODO: Add code to update this.
|
||||||
public static var beatsPerMeasure(get, null):Int;
|
public static var beatsPerMeasure(get, null):Int;
|
||||||
|
|
||||||
static function get_beatsPerMeasure():Int
|
static function get_beatsPerMeasure():Int
|
||||||
{
|
{
|
||||||
return timeSignatureNumerator;
|
return timeSignatureNumerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var stepsPerMeasure(get, null):Int;
|
public static var stepsPerMeasure(get, null):Int;
|
||||||
|
|
||||||
static function get_stepsPerMeasure():Int
|
static function get_stepsPerMeasure():Int
|
||||||
{
|
{
|
||||||
// Is this always x4?
|
// Is this always x4?
|
||||||
return timeSignatureNumerator * 4;
|
return timeSignatureNumerator * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function new()
|
private function new() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getLastBPMChange()
|
public static function getLastBPMChange()
|
||||||
{
|
{
|
||||||
var lastChange:BPMChangeEvent = {
|
var lastChange:BPMChangeEvent = {
|
||||||
stepTime: 0,
|
stepTime: 0,
|
||||||
songTime: 0,
|
songTime: 0,
|
||||||
bpm: 0
|
bpm: 0
|
||||||
}
|
}
|
||||||
for (i in 0...Conductor.bpmChangeMap.length)
|
for (i in 0...Conductor.bpmChangeMap.length)
|
||||||
{
|
{
|
||||||
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
|
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
|
||||||
lastChange = Conductor.bpmChangeMap[i];
|
lastChange = Conductor.bpmChangeMap[i];
|
||||||
|
|
||||||
if (Conductor.songPosition < Conductor.bpmChangeMap[i].songTime)
|
if (Conductor.songPosition < Conductor.bpmChangeMap[i].songTime)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return lastChange;
|
return lastChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcibly defines the current BPM of the song.
|
* Forcibly defines the current BPM of the song.
|
||||||
* Useful for things like the chart editor that need to manipulate BPM in real time.
|
* Useful for things like the chart editor that need to manipulate BPM in real time.
|
||||||
*
|
*
|
||||||
* Set to null to reset to the BPM defined by the timeChanges.
|
* Set to null to reset to the BPM defined by the timeChanges.
|
||||||
*
|
*
|
||||||
* WARNING: Avoid this for things like setting the BPM of the title screen music,
|
* WARNING: Avoid this for things like setting the BPM of the title screen music,
|
||||||
* you should have a metadata file for it instead.
|
* you should have a metadata file for it instead.
|
||||||
*/
|
*/
|
||||||
public static function forceBPM(?bpm:Float = null)
|
public static function forceBPM(?bpm:Float = null)
|
||||||
{
|
{
|
||||||
if (bpm != null)
|
if (bpm != null)
|
||||||
trace('[CONDUCTOR] Forcing BPM to ' + bpm);
|
trace('[CONDUCTOR] Forcing BPM to ' + bpm);
|
||||||
else
|
else
|
||||||
trace('[CONDUCTOR] Resetting BPM to default');
|
trace('[CONDUCTOR] Resetting BPM to default');
|
||||||
Conductor.bpmOverride = bpm;
|
Conductor.bpmOverride = bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the conductor with the current song position.
|
* Update the conductor with the current song position.
|
||||||
* BPM, current step, etc. will be re-calculated based on the song position.
|
* BPM, current step, etc. will be re-calculated based on the song position.
|
||||||
*
|
*
|
||||||
* @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 = null)
|
||||||
{
|
{
|
||||||
if (songPosition == null)
|
if (songPosition == null)
|
||||||
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + Conductor.offset) : 0;
|
songPosition = (FlxG.sound.music != null) ? FlxG.sound.music.time + Conductor.offset : 0.0;
|
||||||
|
|
||||||
var oldBeat = currentBeat;
|
var oldBeat = currentBeat;
|
||||||
var oldStep = currentStep;
|
var oldStep = currentStep;
|
||||||
|
|
||||||
Conductor.songPosition = songPosition;
|
Conductor.songPosition = songPosition;
|
||||||
// Conductor.bpm = Conductor.getLastBPMChange().bpm;
|
// Conductor.bpm = Conductor.getLastBPMChange().bpm;
|
||||||
|
|
||||||
currentTimeChange = timeChanges[0];
|
currentTimeChange = timeChanges[0];
|
||||||
for (i in 0...timeChanges.length)
|
for (i in 0...timeChanges.length)
|
||||||
{
|
{
|
||||||
if (songPosition >= timeChanges[i].timeStamp)
|
if (songPosition >= timeChanges[i].timeStamp)
|
||||||
currentTimeChange = timeChanges[i];
|
currentTimeChange = timeChanges[i];
|
||||||
|
|
||||||
if (songPosition < timeChanges[i].timeStamp)
|
if (songPosition < timeChanges[i].timeStamp)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTimeChange == null && bpmOverride == null)
|
if (currentTimeChange == null && bpmOverride == null && FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
||||||
}
|
}
|
||||||
else if (currentTimeChange != null)
|
else if (currentTimeChange != null)
|
||||||
{
|
{
|
||||||
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet;
|
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet;
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentStep / 4);
|
currentBeat = Math.floor(currentStep / 4);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Assume a constant BPM equal to the forced value.
|
// Assume a constant BPM equal to the forced value.
|
||||||
currentStepTime = (songPosition / stepCrochet);
|
currentStepTime = (songPosition / stepCrochet);
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentStep / 4);
|
currentBeat = Math.floor(currentStep / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlxSignals are really cool.
|
// FlxSignals are really cool.
|
||||||
if (currentStep != oldStep)
|
if (currentStep != oldStep)
|
||||||
stepHit.dispatch();
|
stepHit.dispatch();
|
||||||
|
|
||||||
if (currentBeat != oldBeat)
|
if (currentBeat != oldBeat)
|
||||||
beatHit.dispatch();
|
beatHit.dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@:deprecated // Switch to TimeChanges instead.
|
@:deprecated // Switch to TimeChanges instead.
|
||||||
public static function mapBPMChanges(song:SwagSong)
|
public static function mapBPMChanges(song:SwagSong)
|
||||||
{
|
{
|
||||||
bpmChangeMap = [];
|
bpmChangeMap = [];
|
||||||
|
|
||||||
var curBPM:Float = song.bpm;
|
var curBPM:Float = song.bpm;
|
||||||
var totalSteps:Int = 0;
|
var totalSteps:Int = 0;
|
||||||
var totalPos:Float = 0;
|
var totalPos:Float = 0;
|
||||||
for (i in 0...SongLoad.getSong().length)
|
for (i in 0...SongLoad.getSong().length)
|
||||||
{
|
{
|
||||||
if (SongLoad.getSong()[i].changeBPM && SongLoad.getSong()[i].bpm != curBPM)
|
if (SongLoad.getSong()[i].changeBPM && SongLoad.getSong()[i].bpm != curBPM)
|
||||||
{
|
{
|
||||||
curBPM = SongLoad.getSong()[i].bpm;
|
curBPM = SongLoad.getSong()[i].bpm;
|
||||||
var event:BPMChangeEvent = {
|
var event:BPMChangeEvent = {
|
||||||
stepTime: totalSteps,
|
stepTime: totalSteps,
|
||||||
songTime: totalPos,
|
songTime: totalPos,
|
||||||
bpm: curBPM
|
bpm: curBPM
|
||||||
};
|
};
|
||||||
bpmChangeMap.push(event);
|
bpmChangeMap.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
var deltaSteps:Int = SongLoad.getSong()[i].lengthInSteps;
|
var deltaSteps:Int = SongLoad.getSong()[i].lengthInSteps;
|
||||||
totalSteps += deltaSteps;
|
totalSteps += deltaSteps;
|
||||||
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
|
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
|
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
|
||||||
{
|
{
|
||||||
timeChanges = [];
|
timeChanges = [];
|
||||||
|
|
||||||
for (currentTimeChange in songTimeChanges)
|
for (currentTimeChange in songTimeChanges)
|
||||||
{
|
{
|
||||||
timeChanges.push(currentTimeChange);
|
timeChanges.push(currentTimeChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
trace('Done mapping time changes: ' + timeChanges);
|
trace('Done mapping time changes: ' + timeChanges);
|
||||||
|
|
||||||
// Done.
|
// Done.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a time in milliseconds, return a time in steps.
|
* Given a time in milliseconds, return a time in steps.
|
||||||
*/
|
*/
|
||||||
public static function getTimeInSteps(ms:Float):Int
|
public static function getTimeInSteps(ms:Float):Int
|
||||||
{
|
{
|
||||||
if (timeChanges.length == 0)
|
if (timeChanges.length == 0)
|
||||||
{
|
{
|
||||||
// Assume a constant BPM equal to the forced value.
|
// Assume a constant BPM equal to the forced value.
|
||||||
return Math.floor(ms / stepCrochet);
|
return Math.floor(ms / stepCrochet);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var resultStep:Int = 0;
|
var resultStep:Int = 0;
|
||||||
|
|
||||||
var lastTimeChange:SongTimeChange = timeChanges[0];
|
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||||
for (timeChange in timeChanges)
|
for (timeChange in timeChanges)
|
||||||
{
|
{
|
||||||
if (ms >= timeChange.timeStamp)
|
if (ms >= timeChange.timeStamp)
|
||||||
{
|
{
|
||||||
lastTimeChange = timeChange;
|
lastTimeChange = timeChange;
|
||||||
resultStep = lastTimeChange.beatTime * 4;
|
resultStep = lastTimeChange.beatTime * 4;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This time change is after the requested time.
|
// This time change is after the requested time.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet);
|
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet);
|
||||||
|
|
||||||
return resultStep;
|
return resultStep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@ import flixel.util.FlxColor;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.event.SongEvent.SongEventHandler;
|
import funkin.play.event.SongEvent.SongEventParser;
|
||||||
import funkin.play.song.SongData.SongDataParser;
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import funkin.ui.PreferencesMenu;
|
import funkin.ui.PreferencesMenu;
|
||||||
|
@ -32,232 +32,235 @@ import Discord.DiscordClient;
|
||||||
*/
|
*/
|
||||||
class InitState extends FlxTransitionableState
|
class InitState extends FlxTransitionableState
|
||||||
{
|
{
|
||||||
override public function create():Void
|
override public function create():Void
|
||||||
{
|
{
|
||||||
trace('This is a debug build, loading InitState...');
|
trace('This is a debug build, loading InitState...');
|
||||||
#if android
|
#if android
|
||||||
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
|
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
|
||||||
#end
|
#end
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
NGio.init();
|
NGio.init();
|
||||||
#end
|
#end
|
||||||
#if discord_rpc
|
#if discord_rpc
|
||||||
DiscordClient.initialize();
|
DiscordClient.initialize();
|
||||||
|
|
||||||
Application.current.onExit.add(function(exitCode)
|
Application.current.onExit.add(function(exitCode)
|
||||||
{
|
{
|
||||||
DiscordClient.shutdown();
|
DiscordClient.shutdown();
|
||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// ==== flixel shit ==== //
|
// ==== flixel shit ==== //
|
||||||
|
|
||||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||||
// easily with your finger when debug bullshit pops up during testing lol!
|
// easily with your finger when debug bullshit pops up during testing lol!
|
||||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
|
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
|
||||||
{
|
{
|
||||||
FlxG.debugger.visible = false;
|
FlxG.debugger.visible = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function()
|
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function()
|
||||||
{
|
{
|
||||||
if (FlxG.vcr.paused)
|
if (FlxG.vcr.paused)
|
||||||
{
|
{
|
||||||
FlxG.vcr.resume();
|
FlxG.vcr.resume();
|
||||||
|
|
||||||
for (snd in FlxG.sound.list)
|
for (snd in FlxG.sound.list)
|
||||||
snd.resume();
|
snd.resume();
|
||||||
|
|
||||||
FlxG.sound.music.resume();
|
FlxG.sound.music.resume();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FlxG.vcr.pause();
|
FlxG.vcr.pause();
|
||||||
|
|
||||||
for (snd in FlxG.sound.list)
|
for (snd in FlxG.sound.list)
|
||||||
snd.pause();
|
snd.pause();
|
||||||
|
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function()
|
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function()
|
||||||
{
|
{
|
||||||
FlxG.game.debugger.vcr.onStep();
|
FlxG.game.debugger.vcr.onStep();
|
||||||
|
|
||||||
for (snd in FlxG.sound.list)
|
for (snd in FlxG.sound.list)
|
||||||
{
|
{
|
||||||
snd.pause();
|
snd.pause();
|
||||||
snd.time += FlxG.elapsed * 1000;
|
snd.time += FlxG.elapsed * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
FlxG.sound.muteKeys = [ZERO];
|
FlxG.sound.muteKeys = [ZERO];
|
||||||
FlxG.game.focusLostFramerate = 60;
|
FlxG.game.focusLostFramerate = 60;
|
||||||
|
|
||||||
// FlxG.stage.window.borderless = true;
|
// FlxG.stage.window.borderless = true;
|
||||||
// FlxG.stage.window.mouseLock = true;
|
// FlxG.stage.window.mouseLock = true;
|
||||||
|
|
||||||
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
|
var diamond:FlxGraphic = FlxGraphic.fromClass(GraphicTransTileDiamond);
|
||||||
diamond.persist = true;
|
diamond.persist = true;
|
||||||
diamond.destroyOnNoUse = false;
|
diamond.destroyOnNoUse = false;
|
||||||
|
|
||||||
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), {asset: diamond, width: 32, height: 32},
|
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), {asset: diamond, width: 32, height: 32},
|
||||||
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
||||||
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), {asset: diamond, width: 32, height: 32},
|
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), {asset: diamond, width: 32, height: 32},
|
||||||
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
||||||
|
|
||||||
// ===== save shit ===== //
|
// ===== save shit ===== //
|
||||||
|
|
||||||
FlxG.save.bind('funkin', 'ninjamuffin99');
|
FlxG.save.bind('funkin', 'ninjamuffin99');
|
||||||
|
|
||||||
// https://github.com/HaxeFlixel/flixel/pull/2396
|
// https://github.com/HaxeFlixel/flixel/pull/2396
|
||||||
// IF/WHEN MY PR GOES THRU AND IT GETS INTO MAIN FLIXEL, DELETE THIS CHUNKOF CODE, AND THEN UNCOMMENT THE LINE BELOW
|
// IF/WHEN MY PR GOES THRU AND IT GETS INTO MAIN FLIXEL, DELETE THIS CHUNKOF CODE, AND THEN UNCOMMENT THE LINE BELOW
|
||||||
// FlxG.sound.loadSavedPrefs();
|
// FlxG.sound.loadSavedPrefs();
|
||||||
|
|
||||||
if (FlxG.save.data.volume != null)
|
if (FlxG.save.data.volume != null)
|
||||||
FlxG.sound.volume = FlxG.save.data.volume;
|
FlxG.sound.volume = FlxG.save.data.volume;
|
||||||
if (FlxG.save.data.mute != null)
|
if (FlxG.save.data.mute != null)
|
||||||
FlxG.sound.muted = FlxG.save.data.mute;
|
FlxG.sound.muted = FlxG.save.data.mute;
|
||||||
|
|
||||||
// Make errors and warnings less annoying.
|
// Make errors and warnings less annoying.
|
||||||
LogStyle.ERROR.openConsole = false;
|
LogStyle.ERROR.openConsole = false;
|
||||||
LogStyle.ERROR.errorSound = null;
|
LogStyle.ERROR.errorSound = null;
|
||||||
LogStyle.WARNING.openConsole = false;
|
LogStyle.WARNING.openConsole = false;
|
||||||
LogStyle.WARNING.errorSound = null;
|
LogStyle.WARNING.errorSound = null;
|
||||||
|
|
||||||
// FlxG.save.close();
|
// FlxG.save.close();
|
||||||
// FlxG.sound.loadSavedPrefs();
|
// FlxG.sound.loadSavedPrefs();
|
||||||
WindowUtil.initWindowEvents();
|
WindowUtil.initWindowEvents();
|
||||||
|
WindowUtil.disableCrashHandler();
|
||||||
|
|
||||||
PreferencesMenu.initPrefs();
|
PreferencesMenu.initPrefs();
|
||||||
PlayerSettings.init();
|
PlayerSettings.init();
|
||||||
Highscore.load();
|
Highscore.load();
|
||||||
|
|
||||||
if (FlxG.save.data.weekUnlocked != null)
|
if (FlxG.save.data.weekUnlocked != null)
|
||||||
{
|
{
|
||||||
// FIX LATER!!!
|
// FIX LATER!!!
|
||||||
// WEEK UNLOCK PROGRESSION!!
|
// WEEK UNLOCK PROGRESSION!!
|
||||||
// StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
|
// StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
|
||||||
|
|
||||||
if (StoryMenuState.weekUnlocked.length < 4)
|
if (StoryMenuState.weekUnlocked.length < 4)
|
||||||
StoryMenuState.weekUnlocked.insert(0, true);
|
StoryMenuState.weekUnlocked.insert(0, true);
|
||||||
|
|
||||||
// QUICK PATCH OOPS!
|
// QUICK PATCH OOPS!
|
||||||
if (!StoryMenuState.weekUnlocked[0])
|
if (!StoryMenuState.weekUnlocked[0])
|
||||||
StoryMenuState.weekUnlocked[0] = true;
|
StoryMenuState.weekUnlocked[0] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.save.data.seenVideo != null)
|
if (FlxG.save.data.seenVideo != null)
|
||||||
VideoState.seenVideo = FlxG.save.data.seenVideo;
|
VideoState.seenVideo = FlxG.save.data.seenVideo;
|
||||||
|
|
||||||
// ===== fuck outta here ===== //
|
// ===== fuck outta here ===== //
|
||||||
|
|
||||||
// FlxTransitionableState.skipNextTransOut = true;
|
// FlxTransitionableState.skipNextTransOut = true;
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
|
||||||
SongEventHandler.registerBaseEventCallbacks();
|
// TODO: Register custom event callbacks here
|
||||||
// TODO: Register custom event callbacks here
|
|
||||||
|
|
||||||
SongDataParser.loadSongCache();
|
SongEventParser.loadEventCache();
|
||||||
StageDataParser.loadStageCache();
|
SongDataParser.loadSongCache();
|
||||||
CharacterDataParser.loadCharacterCache();
|
StageDataParser.loadStageCache();
|
||||||
ModuleHandler.buildModuleCallbacks();
|
CharacterDataParser.loadCharacterCache();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.buildModuleCallbacks();
|
||||||
|
ModuleHandler.loadModuleCache();
|
||||||
|
|
||||||
FlxG.debugger.toggleKeys = [F2];
|
FlxG.debugger.toggleKeys = [F2];
|
||||||
|
|
||||||
#if song
|
ModuleHandler.callOnCreate();
|
||||||
var song = getSong();
|
|
||||||
|
|
||||||
var weeks = [
|
#if song
|
||||||
['bopeebo', 'fresh', 'dadbattle'],
|
var song = getSong();
|
||||||
['spookeez', 'south', 'monster'],
|
|
||||||
['spooky', 'spooky', 'monster'],
|
|
||||||
['pico', 'philly', 'blammed'],
|
|
||||||
['satin-panties', 'high', 'milf'],
|
|
||||||
['cocoa', 'eggnog', 'winter-horrorland'],
|
|
||||||
['senpai', 'roses', 'thorns'],
|
|
||||||
['ugh', 'guns', 'stress']
|
|
||||||
];
|
|
||||||
|
|
||||||
var week = 0;
|
var weeks = [
|
||||||
for (i in 0...weeks.length)
|
['bopeebo', 'fresh', 'dadbattle'],
|
||||||
{
|
['spookeez', 'south', 'monster'],
|
||||||
if (weeks[i].contains(song))
|
['spooky', 'spooky', 'monster'],
|
||||||
{
|
['pico', 'philly', 'blammed'],
|
||||||
week = i + 1;
|
['satin-panties', 'high', 'milf'],
|
||||||
break;
|
['cocoa', 'eggnog', 'winter-horrorland'],
|
||||||
}
|
['senpai', 'roses', 'thorns'],
|
||||||
}
|
['ugh', 'guns', 'stress']
|
||||||
|
];
|
||||||
|
|
||||||
if (week == 0)
|
var week = 0;
|
||||||
throw 'Invalid -D song=$song';
|
for (i in 0...weeks.length)
|
||||||
|
{
|
||||||
|
if (weeks[i].contains(song))
|
||||||
|
{
|
||||||
|
week = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
startSong(week, song, false);
|
if (week == 0)
|
||||||
#elseif week
|
throw 'Invalid -D song=$song';
|
||||||
var week = getWeek();
|
|
||||||
|
|
||||||
var songs = [
|
startSong(week, song, false);
|
||||||
'bopeebo', 'spookeez', 'spooky', 'pico',
|
#elseif week
|
||||||
'satin-panties', 'cocoa', 'senpai', 'ugh'
|
var week = getWeek();
|
||||||
];
|
|
||||||
|
|
||||||
if (week <= 0 || week >= songs.length)
|
var songs = [
|
||||||
throw "invalid -D week=" + week;
|
'bopeebo', 'spookeez', 'spooky', 'pico',
|
||||||
|
'satin-panties', 'cocoa', 'senpai', 'ugh'
|
||||||
|
];
|
||||||
|
|
||||||
startSong(week, songs[week - 1], true);
|
if (week <= 0 || week >= songs.length)
|
||||||
#elseif FREEPLAY
|
throw "invalid -D week=" + week;
|
||||||
FlxG.switchState(new FreeplayState());
|
|
||||||
#elseif ANIMATE
|
|
||||||
FlxG.switchState(new funkin.animate.dotstuff.DotStuffTestStage());
|
|
||||||
#elseif CHARTING
|
|
||||||
FlxG.switchState(new ChartingState());
|
|
||||||
#elseif STAGEBUILD
|
|
||||||
FlxG.switchState(new StageBuilderState());
|
|
||||||
#elseif FIGHT
|
|
||||||
FlxG.switchState(new PicoFight());
|
|
||||||
#elseif ANIMDEBUG
|
|
||||||
FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
|
|
||||||
#elseif LATENCY
|
|
||||||
FlxG.switchState(new LatencyState());
|
|
||||||
#elseif NETTEST
|
|
||||||
FlxG.switchState(new netTest.NetTest());
|
|
||||||
#else
|
|
||||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
|
||||||
FlxG.switchState(new TitleState());
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
function startSong(week, song, isStoryMode)
|
startSong(week, songs[week - 1], true);
|
||||||
{
|
#elseif FREEPLAY
|
||||||
var dif = getDif();
|
FlxG.switchState(new FreeplayState());
|
||||||
|
#elseif ANIMATE
|
||||||
|
FlxG.switchState(new funkin.animate.dotstuff.DotStuffTestStage());
|
||||||
|
#elseif CHARTING
|
||||||
|
FlxG.switchState(new ChartingState());
|
||||||
|
#elseif STAGEBUILD
|
||||||
|
FlxG.switchState(new StageBuilderState());
|
||||||
|
#elseif FIGHT
|
||||||
|
FlxG.switchState(new PicoFight());
|
||||||
|
#elseif ANIMDEBUG
|
||||||
|
FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState());
|
||||||
|
#elseif LATENCY
|
||||||
|
FlxG.switchState(new LatencyState());
|
||||||
|
#elseif NETTEST
|
||||||
|
FlxG.switchState(new netTest.NetTest());
|
||||||
|
#else
|
||||||
|
FlxG.sound.cache(Paths.music('freakyMenu'));
|
||||||
|
FlxG.switchState(new TitleState());
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
function startSong(week, song, isStoryMode)
|
||||||
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
|
{
|
||||||
PlayState.isStoryMode = isStoryMode;
|
var dif = getDif();
|
||||||
PlayState.storyDifficulty = dif;
|
|
||||||
PlayState.storyDifficulty_NEW = switch (dif)
|
PlayState.currentSong = SongLoad.loadFromJson(song, song);
|
||||||
{
|
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
|
||||||
case 0: 'easy';
|
PlayState.isStoryMode = isStoryMode;
|
||||||
case 1: 'normal';
|
PlayState.storyDifficulty = dif;
|
||||||
case 2: 'hard';
|
PlayState.storyDifficulty_NEW = switch (dif)
|
||||||
default: 'normal';
|
{
|
||||||
};
|
case 0: 'easy';
|
||||||
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
|
case 1: 'normal';
|
||||||
PlayState.storyWeek = week;
|
case 2: 'hard';
|
||||||
LoadingState.loadAndSwitchState(new PlayState());
|
default: 'normal';
|
||||||
}
|
};
|
||||||
|
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
|
||||||
|
PlayState.storyWeek = week;
|
||||||
|
LoadingState.loadAndSwitchState(new PlayState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWeek()
|
function getWeek()
|
||||||
return Std.parseInt(MacroUtil.getDefine("week"));
|
return Std.parseInt(MacroUtil.getDefine("week"));
|
||||||
|
|
||||||
function getSong()
|
function getSong()
|
||||||
return MacroUtil.getDefine("song");
|
return MacroUtil.getDefine("song");
|
||||||
|
|
||||||
function getDif()
|
function getDif()
|
||||||
return Std.parseInt(MacroUtil.getDefine("dif", "1"));
|
return Std.parseInt(MacroUtil.getDefine("dif", "1"));
|
||||||
|
|
|
@ -6,178 +6,178 @@ import funkin.util.assets.FlxAnimationUtil;
|
||||||
|
|
||||||
class DJBoyfriend extends FlxSprite
|
class DJBoyfriend extends FlxSprite
|
||||||
{
|
{
|
||||||
// Represents the sprite's current status.
|
// Represents the sprite's current status.
|
||||||
// Without state machines I would have driven myself crazy years ago.
|
// Without state machines I would have driven myself crazy years ago.
|
||||||
public var currentState:DJBoyfriendState = Intro;
|
public var currentState:DJBoyfriendState = Intro;
|
||||||
|
|
||||||
// A callback activated when the intro animation finishes.
|
// A callback activated when the intro animation finishes.
|
||||||
public var onIntroDone:FlxSignal = new FlxSignal();
|
public var onIntroDone:FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
// A callback activated when Boyfriend gets spooked.
|
// A callback activated when Boyfriend gets spooked.
|
||||||
public var onSpook:FlxSignal = new FlxSignal();
|
public var onSpook:FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
// playAnim stolen from Character.hx, cuz im lazy lol!
|
// playAnim stolen from Character.hx, cuz im lazy lol!
|
||||||
// TODO: Switch this class to use SwagSprite instead.
|
// TODO: Switch this class to use SwagSprite instead.
|
||||||
public var animOffsets:Map<String, Array<Dynamic>>;
|
public var animOffsets:Map<String, Array<Dynamic>>;
|
||||||
|
|
||||||
static final SPOOK_PERIOD:Float = 180.0;
|
static final SPOOK_PERIOD:Float = 180.0;
|
||||||
|
|
||||||
// Time since dad last SPOOKED you.
|
// Time since dad last SPOOKED you.
|
||||||
var timeSinceSpook:Float = 0;
|
var timeSinceSpook:Float = 0;
|
||||||
|
|
||||||
public function new(x:Float, y:Float)
|
public function new(x:Float, y:Float)
|
||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
animOffsets = new Map<String, Array<Dynamic>>();
|
animOffsets = new Map<String, Array<Dynamic>>();
|
||||||
|
|
||||||
setupAnimations();
|
setupAnimations();
|
||||||
|
|
||||||
animation.finishCallback = onFinishAnim;
|
animation.finishCallback = onFinishAnim;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function update(elapsed:Float):Void
|
public override function update(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT)
|
if (FlxG.keys.justPressed.LEFT)
|
||||||
{
|
{
|
||||||
animOffsets["confirm"] = [animOffsets["confirm"][0] + 1, animOffsets["confirm"][1]];
|
animOffsets["confirm"] = [animOffsets["confirm"][0] + 1, animOffsets["confirm"][1]];
|
||||||
applyAnimOffset();
|
applyAnimOffset();
|
||||||
}
|
}
|
||||||
else if (FlxG.keys.justPressed.RIGHT)
|
else if (FlxG.keys.justPressed.RIGHT)
|
||||||
{
|
{
|
||||||
animOffsets["confirm"] = [animOffsets["confirm"][0] - 1, animOffsets["confirm"][1]];
|
animOffsets["confirm"] = [animOffsets["confirm"][0] - 1, animOffsets["confirm"][1]];
|
||||||
applyAnimOffset();
|
applyAnimOffset();
|
||||||
}
|
}
|
||||||
else if (FlxG.keys.justPressed.UP)
|
else if (FlxG.keys.justPressed.UP)
|
||||||
{
|
{
|
||||||
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] + 1];
|
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] + 1];
|
||||||
applyAnimOffset();
|
applyAnimOffset();
|
||||||
}
|
}
|
||||||
else if (FlxG.keys.justPressed.DOWN)
|
else if (FlxG.keys.justPressed.DOWN)
|
||||||
{
|
{
|
||||||
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] - 1];
|
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] - 1];
|
||||||
applyAnimOffset();
|
applyAnimOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (currentState)
|
switch (currentState)
|
||||||
{
|
{
|
||||||
case Intro:
|
case Intro:
|
||||||
// Play the intro animation then leave this state immediately.
|
// Play the intro animation then leave this state immediately.
|
||||||
if (getCurrentAnimation() != 'intro')
|
if (getCurrentAnimation() != 'intro')
|
||||||
playAnimation('intro', true);
|
playAnimation('intro', true);
|
||||||
timeSinceSpook = 0;
|
timeSinceSpook = 0;
|
||||||
case Idle:
|
case Idle:
|
||||||
// We are in this state the majority of the time.
|
// We are in this state the majority of the time.
|
||||||
if (getCurrentAnimation() != 'idle' || animation.finished)
|
if (getCurrentAnimation() != 'idle' || animation.finished)
|
||||||
{
|
{
|
||||||
if (timeSinceSpook > SPOOK_PERIOD)
|
if (timeSinceSpook > SPOOK_PERIOD)
|
||||||
{
|
{
|
||||||
currentState = Spook;
|
currentState = Spook;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
playAnimation('idle', false);
|
playAnimation('idle', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeSinceSpook += elapsed;
|
timeSinceSpook += elapsed;
|
||||||
case Confirm:
|
case Confirm:
|
||||||
if (getCurrentAnimation() != 'confirm')
|
if (getCurrentAnimation() != 'confirm')
|
||||||
playAnimation('confirm', false);
|
playAnimation('confirm', false);
|
||||||
timeSinceSpook = 0;
|
timeSinceSpook = 0;
|
||||||
case Spook:
|
case Spook:
|
||||||
if (getCurrentAnimation() != 'spook')
|
if (getCurrentAnimation() != 'spook')
|
||||||
{
|
{
|
||||||
onSpook.dispatch();
|
onSpook.dispatch();
|
||||||
playAnimation('spook', false);
|
playAnimation('spook', false);
|
||||||
}
|
}
|
||||||
timeSinceSpook = 0;
|
timeSinceSpook = 0;
|
||||||
default:
|
default:
|
||||||
// I shit myself.
|
// I shit myself.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFinishAnim(name:String):Void
|
function onFinishAnim(name:String):Void
|
||||||
{
|
{
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
case "intro":
|
case "intro":
|
||||||
trace('Finished intro');
|
// trace('Finished intro');
|
||||||
currentState = Idle;
|
currentState = Idle;
|
||||||
onIntroDone.dispatch();
|
onIntroDone.dispatch();
|
||||||
case "idle":
|
case "idle":
|
||||||
trace('Finished idle');
|
// trace('Finished idle');
|
||||||
case "spook":
|
case "spook":
|
||||||
trace('Finished spook');
|
// trace('Finished spook');
|
||||||
currentState = Idle;
|
currentState = Idle;
|
||||||
case "confirm":
|
case "confirm":
|
||||||
trace('Finished confirm');
|
// trace('Finished confirm');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetAFKTimer():Void
|
public function resetAFKTimer():Void
|
||||||
{
|
{
|
||||||
timeSinceSpook = 0;
|
timeSinceSpook = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAnimations():Void
|
function setupAnimations():Void
|
||||||
{
|
{
|
||||||
frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk'));
|
frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk'));
|
||||||
|
|
||||||
animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
|
animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
|
||||||
addOffset('intro', 0, 0);
|
addOffset('intro', 0, 0);
|
||||||
|
|
||||||
animation.addByPrefix('idle', "Boyfriend DJ0", 24, false);
|
animation.addByPrefix('idle', "Boyfriend DJ0", 24, false);
|
||||||
addOffset('idle', -4, -426);
|
addOffset('idle', -4, -426);
|
||||||
|
|
||||||
animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24, false);
|
animation.addByPrefix('confirm', "Boyfriend DJ confirm", 24, false);
|
||||||
addOffset('confirm', 40, -451);
|
addOffset('confirm', 40, -451);
|
||||||
|
|
||||||
animation.addByPrefix('spook', "bf dj afk0", 24, false);
|
animation.addByPrefix('spook', "bf dj afk0", 24, false);
|
||||||
addOffset('spook', -3, -272);
|
addOffset('spook', -3, -272);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function confirm():Void
|
public function confirm():Void
|
||||||
{
|
{
|
||||||
currentState = Confirm;
|
currentState = Confirm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function addOffset(name:String, x:Float = 0, y:Float = 0)
|
public inline function addOffset(name:String, x:Float = 0, y:Float = 0)
|
||||||
{
|
{
|
||||||
animOffsets[name] = [x, y];
|
animOffsets[name] = [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCurrentAnimation():String
|
public function getCurrentAnimation():String
|
||||||
{
|
{
|
||||||
if (this.animation == null || this.animation.curAnim == null)
|
if (this.animation == null || this.animation.curAnim == null)
|
||||||
return "";
|
return "";
|
||||||
return this.animation.curAnim.name;
|
return this.animation.curAnim.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playAnimation(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
public function playAnimation(AnimName:String, Force:Bool = false, Reversed:Bool = false, Frame:Int = 0):Void
|
||||||
{
|
{
|
||||||
animation.play(AnimName, Force, Reversed, Frame);
|
animation.play(AnimName, Force, Reversed, Frame);
|
||||||
applyAnimOffset();
|
applyAnimOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAnimOffset()
|
function applyAnimOffset()
|
||||||
{
|
{
|
||||||
var AnimName = getCurrentAnimation();
|
var AnimName = getCurrentAnimation();
|
||||||
var daOffset = animOffsets.get(AnimName);
|
var daOffset = animOffsets.get(AnimName);
|
||||||
if (animOffsets.exists(AnimName))
|
if (animOffsets.exists(AnimName))
|
||||||
{
|
{
|
||||||
offset.set(daOffset[0], daOffset[1]);
|
offset.set(daOffset[0], daOffset[1]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
offset.set(0, 0);
|
offset.set(0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DJBoyfriendState
|
enum DJBoyfriendState
|
||||||
{
|
{
|
||||||
Intro;
|
Intro;
|
||||||
Idle;
|
Idle;
|
||||||
Confirm;
|
Confirm;
|
||||||
Spook;
|
Spook;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@ import flixel.FlxG; // This one in particular causes a compile error if you're u
|
||||||
// These are great.
|
// These are great.
|
||||||
using Lambda;
|
using Lambda;
|
||||||
using StringTools;
|
using StringTools;
|
||||||
|
using funkin.util.tools.MapTools;
|
||||||
using funkin.util.tools.IteratorTools;
|
using funkin.util.tools.IteratorTools;
|
||||||
using funkin.util.tools.StringTools;
|
using funkin.util.tools.StringTools;
|
||||||
|
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -60,6 +60,7 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
* and can be cancelled by scripts.
|
* and can be cancelled by scripts.
|
||||||
*/
|
*/
|
||||||
public function onPause(event:PauseScriptEvent):Void;
|
public function onPause(event:PauseScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game is unpaused.
|
* Called when the game is unpaused.
|
||||||
*/
|
*/
|
||||||
|
@ -70,18 +71,22 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
* Use this to mutate the chart.
|
* Use this to mutate the chart.
|
||||||
*/
|
*/
|
||||||
public function onSongLoaded(event:SongLoadScriptEvent):Void;
|
public function onSongLoaded(event:SongLoadScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the song starts (conductor time is 0 seconds).
|
* Called when the song starts (conductor time is 0 seconds).
|
||||||
*/
|
*/
|
||||||
public function onSongStart(event:ScriptEvent):Void;
|
public function onSongStart(event:ScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the song ends and the song is about to be unloaded.
|
* Called when the song ends and the song is about to be unloaded.
|
||||||
*/
|
*/
|
||||||
public function onSongEnd(event:ScriptEvent):Void;
|
public function onSongEnd(event:ScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called as the player runs out of health just before the game over substate is entered.
|
* Called as the player runs out of health just before the game over substate is entered.
|
||||||
*/
|
*/
|
||||||
public function onGameOver(event:ScriptEvent):Void;
|
public function onGameOver(event:ScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
||||||
*/
|
*/
|
||||||
|
@ -92,19 +97,27 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
||||||
*/
|
*/
|
||||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when EITHER player (usually the player) misses a note.
|
* Called when EITHER player (usually the player) misses a note.
|
||||||
*/
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player presses a key when no note is on the strumline.
|
* Called when the player presses a key when no note is on the strumline.
|
||||||
*/
|
*/
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void;
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song reaches an event.
|
||||||
|
*/
|
||||||
|
public function onSongEvent(event:SongEventScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once every step of the song.
|
* Called once every step of the song.
|
||||||
*/
|
*/
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once every beat of the song.
|
* Called once every beat of the song.
|
||||||
*/
|
*/
|
||||||
|
@ -114,10 +127,12 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||||
* Called when the countdown of the song starts.
|
* Called when the countdown of the song starts.
|
||||||
*/
|
*/
|
||||||
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
public function onCountdownStart(event:CountdownScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the a part of the countdown happens.
|
* Called when the a part of the countdown happens.
|
||||||
*/
|
*/
|
||||||
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
public function onCountdownStep(event:CountdownScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the countdown of the song ends.
|
* Called when the countdown of the song ends.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,68 +4,88 @@ import polymod.Polymod;
|
||||||
|
|
||||||
class PolymodErrorHandler
|
class PolymodErrorHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Show a popup with the given text.
|
* Show a popup with the given text.
|
||||||
* This displays a system popup, it WILL interrupt the game.
|
* This displays a system popup, it WILL interrupt the game.
|
||||||
* Make sure to only use this when it's important, like when there's a script error.
|
* Make sure to only use this when it's important, like when there's a script error.
|
||||||
*
|
*
|
||||||
* @param name The name at the top of the popup.
|
* @param name The name at the top of the popup.
|
||||||
* @param desc The body text of the popup.
|
* @param desc The body text of the popup.
|
||||||
*/
|
*/
|
||||||
public static function showAlert(name:String, desc:String):Void
|
public static function showAlert(name:String, desc:String):Void
|
||||||
{
|
{
|
||||||
lime.app.Application.current.window.alert(desc, name);
|
lime.app.Application.current.window.alert(desc, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function onPolymodError(error:PolymodError):Void
|
public static function onPolymodError(error:PolymodError):Void
|
||||||
{
|
{
|
||||||
// Perform an action based on the error code.
|
// Perform an action based on the error code.
|
||||||
switch (error.code)
|
switch (error.code)
|
||||||
{
|
{
|
||||||
case MOD_LOAD_PREPARE:
|
case FRAMEWORK_INIT, FRAMEWORK_AUTODETECT, SCRIPT_PARSING:
|
||||||
logInfo('[POLYMOD]: ${error.message}');
|
// Unimportant.
|
||||||
case MOD_LOAD_DONE:
|
return;
|
||||||
logInfo('[POLYMOD]: ${error.message}');
|
|
||||||
case MISSING_ICON:
|
|
||||||
logWarn('[POLYMOD]: A mod is missing an icon. Please add one.');
|
|
||||||
case SCRIPT_PARSE_ERROR:
|
|
||||||
// A syntax error when parsing a script.
|
|
||||||
logError('[POLYMOD]: ${error.message}');
|
|
||||||
showAlert('Polymod Script Parsing Error', error.message);
|
|
||||||
case SCRIPT_EXCEPTION:
|
|
||||||
// A runtime error when running a script.
|
|
||||||
logError('[POLYMOD]: ${error.message}');
|
|
||||||
showAlert('Polymod Script Execution Error', error.message);
|
|
||||||
case SCRIPT_CLASS_NOT_FOUND:
|
|
||||||
// A scripted class tried to reference an unknown superclass.
|
|
||||||
logError('[POLYMOD]: ${error.message}');
|
|
||||||
showAlert('Polymod Script Parsing Error', error.message);
|
|
||||||
default:
|
|
||||||
// Log the message based on its severity.
|
|
||||||
switch (error.severity)
|
|
||||||
{
|
|
||||||
case NOTICE:
|
|
||||||
logInfo('[POLYMOD]: ${error.message}');
|
|
||||||
case WARNING:
|
|
||||||
logWarn('[POLYMOD]: ${error.message}');
|
|
||||||
case ERROR:
|
|
||||||
logError('[POLYMOD]: ${error.message}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static function logInfo(message:String):Void
|
case MOD_LOAD_PREPARE, MOD_LOAD_DONE:
|
||||||
{
|
logInfo('LOADING MOD - ${error.message}');
|
||||||
trace('[INFO ] ${message}');
|
|
||||||
}
|
|
||||||
|
|
||||||
static function logError(message:String):Void
|
case MISSING_ICON:
|
||||||
{
|
logWarn('A mod is missing an icon. Please add one.');
|
||||||
trace('[ERROR] ${message}');
|
|
||||||
}
|
|
||||||
|
|
||||||
static function logWarn(message:String):Void
|
case SCRIPT_PARSE_ERROR:
|
||||||
{
|
// A syntax error when parsing a script.
|
||||||
trace('[WARN ] ${message}');
|
logError(error.message);
|
||||||
}
|
// Notify the user via popup.
|
||||||
|
showAlert('Polymod Script Parsing Error', error.message);
|
||||||
|
case SCRIPT_RUNTIME_EXCEPTION:
|
||||||
|
// A runtime error when running a script.
|
||||||
|
logError(error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
showAlert('Polymod Script Exception', error.message);
|
||||||
|
case SCRIPT_CLASS_MODULE_NOT_FOUND:
|
||||||
|
// A scripted class tried to reference an unknown class or module.
|
||||||
|
logError(error.message);
|
||||||
|
|
||||||
|
// Last word is the class name.
|
||||||
|
var className:String = error.message.split(' ').pop();
|
||||||
|
var msg:String = 'Import error in ${error.origin}';
|
||||||
|
msg += '\nCould not import unknown class ${className}';
|
||||||
|
msg += '\nCheck to ensure the class exists and is spelled correctly.';
|
||||||
|
|
||||||
|
// Notify the user via popup.
|
||||||
|
showAlert('Polymod Script Import Error', msg);
|
||||||
|
case SCRIPT_CLASS_MODULE_BLACKLISTED:
|
||||||
|
// A scripted class tried to reference a blacklisted class or module.
|
||||||
|
logError(error.message);
|
||||||
|
// Notify the user via popup.
|
||||||
|
showAlert('Polymod Script Blacklist Violation', error.message);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Log the message based on its severity.
|
||||||
|
switch (error.severity)
|
||||||
|
{
|
||||||
|
case NOTICE:
|
||||||
|
logInfo(error.message);
|
||||||
|
case WARNING:
|
||||||
|
logWarn(error.message);
|
||||||
|
case ERROR:
|
||||||
|
logError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function logInfo(message:String):Void
|
||||||
|
{
|
||||||
|
trace('[INFO-] ${message}');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function logError(message:String):Void
|
||||||
|
{
|
||||||
|
trace('[ERROR] ${message}');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function logWarn(message:String):Void
|
||||||
|
{
|
||||||
|
trace('[WARN-] ${message}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.modding;
|
package funkin.modding;
|
||||||
|
|
||||||
|
import funkin.util.macro.ClassMacro;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.song.SongData;
|
import funkin.play.song.SongData;
|
||||||
|
@ -11,251 +12,271 @@ import funkin.util.FileUtil;
|
||||||
|
|
||||||
class PolymodHandler
|
class PolymodHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The API version that mods should comply with.
|
* The API version that mods should comply with.
|
||||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
||||||
* Bug fixes increment the patch version, new features increment the minor version.
|
* Bug fixes increment the patch version, new features increment the minor version.
|
||||||
* Changes that break old mods increment the major version.
|
* Changes that break old mods increment the major version.
|
||||||
*/
|
*/
|
||||||
static final API_VERSION = "0.1.0";
|
static final API_VERSION = "0.1.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
*/
|
*/
|
||||||
static final MOD_FOLDER = "mods";
|
static final MOD_FOLDER = "mods";
|
||||||
|
|
||||||
public static function createModRoot()
|
public static function createModRoot()
|
||||||
{
|
{
|
||||||
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game with ALL mods enabled with Polymod.
|
* Loads the game with ALL mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadAllMods()
|
public static function loadAllMods()
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
trace("Initializing Polymod (using all mods)...");
|
trace("Initializing Polymod (using all mods)...");
|
||||||
loadModsById(getAllModIds());
|
loadModsById(getAllModIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game with configured mods enabled with Polymod.
|
* Loads the game with configured mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadEnabledMods()
|
public static function loadEnabledMods()
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
|
||||||
trace("Initializing Polymod (using configured mods)...");
|
trace("Initializing Polymod (using configured mods)...");
|
||||||
loadModsById(getEnabledModIds());
|
loadModsById(getEnabledModIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the game without any mods enabled with Polymod.
|
* Loads the game without any mods enabled with Polymod.
|
||||||
*/
|
*/
|
||||||
public static function loadNoMods()
|
public static function loadNoMods()
|
||||||
{
|
{
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
|
||||||
// We still need to configure the debug print calls etc.
|
// We still need to configure the debug print calls etc.
|
||||||
trace("Initializing Polymod (using no mods)...");
|
trace("Initializing Polymod (using no mods)...");
|
||||||
loadModsById([]);
|
loadModsById([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function loadModsById(ids:Array<String>)
|
public static function loadModsById(ids:Array<String>)
|
||||||
{
|
{
|
||||||
if (ids.length == 0)
|
if (ids.length == 0)
|
||||||
{
|
{
|
||||||
trace('You attempted to load zero mods.');
|
trace('You attempted to load zero mods.');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Attempting to load ${ids.length} mods...');
|
trace('Attempting to load ${ids.length} mods...');
|
||||||
}
|
}
|
||||||
var loadedModList = polymod.Polymod.init({
|
|
||||||
// Root directory for all mods.
|
|
||||||
modRoot: MOD_FOLDER,
|
|
||||||
// The directories for one or more mods to load.
|
|
||||||
dirs: ids,
|
|
||||||
// Framework being used to load assets.
|
|
||||||
framework: OPENFL,
|
|
||||||
// The current version of our API.
|
|
||||||
apiVersionRule: API_VERSION,
|
|
||||||
// Call this function any time an error occurs.
|
|
||||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
|
||||||
// Enforce semantic version patterns for each mod.
|
|
||||||
// modVersions: null,
|
|
||||||
// A map telling Polymod what the asset type is for unfamiliar file extensions.
|
|
||||||
// extensionMap: [],
|
|
||||||
|
|
||||||
frameworkParams: buildFrameworkParams(),
|
buildImports();
|
||||||
|
|
||||||
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
var loadedModList = polymod.Polymod.init({
|
||||||
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
// Root directory for all mods.
|
||||||
|
modRoot: MOD_FOLDER,
|
||||||
|
// The directories for one or more mods to load.
|
||||||
|
dirs: ids,
|
||||||
|
// Framework being used to load assets.
|
||||||
|
framework: OPENFL,
|
||||||
|
// The current version of our API.
|
||||||
|
apiVersionRule: API_VERSION,
|
||||||
|
// Call this function any time an error occurs.
|
||||||
|
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||||
|
// Enforce semantic version patterns for each mod.
|
||||||
|
// modVersions: null,
|
||||||
|
// A map telling Polymod what the asset type is for unfamiliar file extensions.
|
||||||
|
// extensionMap: [],
|
||||||
|
|
||||||
// Parsing rules for various data formats.
|
frameworkParams: buildFrameworkParams(),
|
||||||
parseRules: buildParseRules(),
|
|
||||||
|
|
||||||
// Parse hxc files and register the scripted classes in them.
|
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
||||||
useScriptedClasses: true,
|
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
||||||
});
|
|
||||||
|
|
||||||
if (loadedModList == null)
|
// Parsing rules for various data formats.
|
||||||
{
|
parseRules: buildParseRules(),
|
||||||
trace('[POLYMOD] An error occurred! Failed when loading mods!');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (loadedModList.length == 0)
|
|
||||||
{
|
|
||||||
trace('[POLYMOD] Mod loading complete. We loaded no mods / ${ids.length} mods.');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
trace('[POLYMOD] Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (mod in loadedModList)
|
// Parse hxc files and register the scripted classes in them.
|
||||||
{
|
useScriptedClasses: true,
|
||||||
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
});
|
||||||
}
|
|
||||||
|
|
||||||
#if debug
|
if (loadedModList == null)
|
||||||
var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
{
|
||||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} images.');
|
trace('An error occurred! Failed when loading mods!');
|
||||||
for (item in fileList)
|
}
|
||||||
trace(' * $item');
|
else
|
||||||
|
{
|
||||||
|
if (loadedModList.length == 0)
|
||||||
|
{
|
||||||
|
trace('Mod loading complete. We loaded no mods / ${ids.length} mods.');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
for (mod in loadedModList)
|
||||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} text files.');
|
{
|
||||||
for (item in fileList)
|
trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]');
|
||||||
trace(' * $item');
|
}
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
#if debug
|
||||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} music files.');
|
var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||||
for (item in fileList)
|
trace('Installed mods have replaced ${fileList.length} images.');
|
||||||
trace(' * $item');
|
for (item in fileList)
|
||||||
|
trace(' * $item');
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
||||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} sound files.');
|
trace('Installed mods have added/replaced ${fileList.length} text files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
|
|
||||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
||||||
trace('[POLYMOD] Installed mods have replaced ${fileList.length} generic audio files.');
|
trace('Installed mods have replaced ${fileList.length} music files.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
trace(' * $item');
|
trace(' * $item');
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
static function buildParseRules():polymod.format.ParseRules
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
||||||
{
|
trace('Installed mods have replaced ${fileList.length} sound files.');
|
||||||
var output = polymod.format.ParseRules.getDefault();
|
for (item in fileList)
|
||||||
// Ensure TXT files have merge support.
|
trace(' * $item');
|
||||||
output.addType("txt", TextFileFormat.LINES);
|
|
||||||
// Ensure script files have merge support.
|
|
||||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType("hxc", TextFileFormat.PLAINTEXT);
|
|
||||||
output.addType("hx", TextFileFormat.PLAINTEXT);
|
|
||||||
|
|
||||||
// You can specify the format of a specific file, with file extension.
|
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
||||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
trace('Installed mods have replaced ${fileList.length} generic audio files.');
|
||||||
return output;
|
for (item in fileList)
|
||||||
}
|
trace(' * $item');
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
static inline function buildFrameworkParams():polymod.Polymod.FrameworkParams
|
static function buildImports():Void
|
||||||
{
|
{
|
||||||
return {
|
// Add default imports for common classes.
|
||||||
assetLibraryPaths: [
|
|
||||||
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
|
||||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getAllMods():Array<ModMetadata>
|
// Add import aliases for certain classes.
|
||||||
{
|
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||||
trace('Scanning the mods folder...');
|
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
||||||
var modMetadata = Polymod.scan({
|
|
||||||
modRoot: MOD_FOLDER,
|
|
||||||
apiVersionRule: API_VERSION,
|
|
||||||
errorCallback: PolymodErrorHandler.onPolymodError
|
|
||||||
});
|
|
||||||
trace('Found ${modMetadata.length} mods when scanning.');
|
|
||||||
return modMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getAllModIds():Array<String>
|
// Add blacklisting for prohibited classes and packages.
|
||||||
{
|
// `polymod.*`
|
||||||
var modIds = [for (i in getAllMods()) i.id];
|
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||||
return modIds;
|
{
|
||||||
}
|
var className = Type.getClassName(cls);
|
||||||
|
Polymod.blacklistImport(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function setEnabledMods(newModList:Array<String>):Void
|
static function buildParseRules():polymod.format.ParseRules
|
||||||
{
|
{
|
||||||
FlxG.save.data.enabledMods = newModList;
|
var output = polymod.format.ParseRules.getDefault();
|
||||||
// Make sure to COMMIT the changes.
|
// Ensure TXT files have merge support.
|
||||||
FlxG.save.flush();
|
output.addType("txt", TextFileFormat.LINES);
|
||||||
}
|
// Ensure script files have merge support.
|
||||||
|
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
||||||
|
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
||||||
|
output.addType("hxc", TextFileFormat.PLAINTEXT);
|
||||||
|
output.addType("hx", TextFileFormat.PLAINTEXT);
|
||||||
|
|
||||||
/**
|
// You can specify the format of a specific file, with file extension.
|
||||||
* Returns the list of enabled mods.
|
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||||
* @return Array<String>
|
return output;
|
||||||
*/
|
}
|
||||||
public static function getEnabledModIds():Array<String>
|
|
||||||
{
|
|
||||||
if (FlxG.save.data.enabledMods == null)
|
|
||||||
{
|
|
||||||
// NOTE: If the value is null, the enabled mod list is unconfigured.
|
|
||||||
// Currently, we default to disabling newly installed mods.
|
|
||||||
// If we want to auto-enable new mods, but otherwise leave the configured list in place,
|
|
||||||
// we will need some custom logic.
|
|
||||||
FlxG.save.data.enabledMods = [];
|
|
||||||
}
|
|
||||||
return FlxG.save.data.enabledMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getEnabledMods():Array<ModMetadata>
|
static inline function buildFrameworkParams():polymod.Polymod.FrameworkParams
|
||||||
{
|
{
|
||||||
var modIds = getEnabledModIds();
|
return {
|
||||||
var modMetadata = getAllMods();
|
assetLibraryPaths: [
|
||||||
var enabledMods = [];
|
"songs" => "songs", "shared" => "", "tutorial" => "tutorial", "scripts" => "scripts", "week1" => "week1", "week2" => "week2",
|
||||||
for (item in modMetadata)
|
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||||
{
|
]
|
||||||
if (modIds.indexOf(item.id) != -1)
|
}
|
||||||
{
|
}
|
||||||
enabledMods.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return enabledMods;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function forceReloadAssets()
|
public static function getAllMods():Array<ModMetadata>
|
||||||
{
|
{
|
||||||
// Forcibly clear scripts so that scripts can be edited.
|
trace('Scanning the mods folder...');
|
||||||
ModuleHandler.clearModuleCache();
|
var modMetadata = Polymod.scan({
|
||||||
Polymod.clearScripts();
|
modRoot: MOD_FOLDER,
|
||||||
|
apiVersionRule: API_VERSION,
|
||||||
|
errorCallback: PolymodErrorHandler.onPolymodError
|
||||||
|
});
|
||||||
|
trace('Found ${modMetadata.length} mods when scanning.');
|
||||||
|
return modMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
// Forcibly reload Polymod so it finds any new files.
|
public static function getAllModIds():Array<String>
|
||||||
// TODO: Replace this with loadEnabledMods().
|
{
|
||||||
funkin.modding.PolymodHandler.loadAllMods();
|
var modIds = [for (i in getAllMods()) i.id];
|
||||||
|
return modIds;
|
||||||
|
}
|
||||||
|
|
||||||
// Reload scripted classes so stages and modules will update.
|
public static function setEnabledMods(newModList:Array<String>):Void
|
||||||
Polymod.registerAllScriptClasses();
|
{
|
||||||
|
FlxG.save.data.enabledMods = newModList;
|
||||||
|
// Make sure to COMMIT the changes.
|
||||||
|
FlxG.save.flush();
|
||||||
|
}
|
||||||
|
|
||||||
// Reload everything that is cached.
|
/**
|
||||||
// Currently this freezes the game for a second but I guess that's tolerable?
|
* Returns the list of enabled mods.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
|
public static function getEnabledModIds():Array<String>
|
||||||
|
{
|
||||||
|
if (FlxG.save.data.enabledMods == null)
|
||||||
|
{
|
||||||
|
// NOTE: If the value is null, the enabled mod list is unconfigured.
|
||||||
|
// Currently, we default to disabling newly installed mods.
|
||||||
|
// If we want to auto-enable new mods, but otherwise leave the configured list in place,
|
||||||
|
// we will need some custom logic.
|
||||||
|
FlxG.save.data.enabledMods = [];
|
||||||
|
}
|
||||||
|
return FlxG.save.data.enabledMods;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Reload event callbacks
|
public static function getEnabledMods():Array<ModMetadata>
|
||||||
|
{
|
||||||
|
var modIds = getEnabledModIds();
|
||||||
|
var modMetadata = getAllMods();
|
||||||
|
var enabledMods = [];
|
||||||
|
for (item in modMetadata)
|
||||||
|
{
|
||||||
|
if (modIds.indexOf(item.id) != -1)
|
||||||
|
{
|
||||||
|
enabledMods.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enabledMods;
|
||||||
|
}
|
||||||
|
|
||||||
SongDataParser.loadSongCache();
|
public static function forceReloadAssets()
|
||||||
StageDataParser.loadStageCache();
|
{
|
||||||
CharacterDataParser.loadCharacterCache();
|
// Forcibly clear scripts so that scripts can be edited.
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.clearModuleCache();
|
||||||
}
|
Polymod.clearScripts();
|
||||||
|
|
||||||
|
// Forcibly reload Polymod so it finds any new files.
|
||||||
|
// TODO: Replace this with loadEnabledMods().
|
||||||
|
funkin.modding.PolymodHandler.loadAllMods();
|
||||||
|
|
||||||
|
// Reload scripted classes so stages and modules will update.
|
||||||
|
Polymod.registerAllScriptClasses();
|
||||||
|
|
||||||
|
// Reload everything that is cached.
|
||||||
|
// Currently this freezes the game for a second but I guess that's tolerable?
|
||||||
|
|
||||||
|
// TODO: Reload event callbacks
|
||||||
|
|
||||||
|
SongDataParser.loadSongCache();
|
||||||
|
StageDataParser.loadStageCache();
|
||||||
|
CharacterDataParser.loadCharacterCache();
|
||||||
|
ModuleHandler.loadModuleCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,269 +15,277 @@ typedef ScriptEventType = EventType<ScriptEvent>;
|
||||||
*/
|
*/
|
||||||
class ScriptEvent
|
class ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the relevant object is created.
|
* Called when the relevant object is created.
|
||||||
* Keep in mind that the constructor may be called before the object is needed,
|
* Keep in mind that the constructor may be called before the object is needed,
|
||||||
* for the purposes of caching data or otherwise.
|
* for the purposes of caching data or otherwise.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final CREATE:ScriptEventType = "CREATE";
|
public static inline final CREATE:ScriptEventType = "CREATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the relevant object is destroyed.
|
* Called when the relevant object is destroyed.
|
||||||
* This should perform relevant cleanup to ensure good performance.
|
* This should perform relevant cleanup to ensure good performance.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final DESTROY:ScriptEventType = "DESTROY";
|
public static inline final DESTROY:ScriptEventType = "DESTROY";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called during the update function.
|
* Called during the update function.
|
||||||
* This is called every frame, so be careful!
|
* This is called every frame, so be careful!
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final UPDATE:ScriptEventType = "UPDATE";
|
public static inline final UPDATE:ScriptEventType = "UPDATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player moves to pause the game.
|
* Called when the player moves to pause the game.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
* This event IS cancelable! Canceling the event will prevent the game from pausing.
|
||||||
*/
|
*/
|
||||||
public static inline final PAUSE:ScriptEventType = "PAUSE";
|
public static inline final PAUSE:ScriptEventType = "PAUSE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player moves to unpause the game while paused.
|
* Called when the player moves to unpause the game while paused.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
* This event IS cancelable! Canceling the event will prevent the game from resuming.
|
||||||
*/
|
*/
|
||||||
public static inline final RESUME:ScriptEventType = "RESUME";
|
public static inline final RESUME:ScriptEventType = "RESUME";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once per step in the song. This happens 4 times per measure.
|
* Called once per step in the song. This happens 4 times per measure.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT";
|
public static inline final SONG_BEAT_HIT:ScriptEventType = "BEAT_HIT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once per step in the song. This happens 16 times per measure.
|
* Called once per step in the song. This happens 16 times per measure.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT";
|
public static inline final SONG_STEP_HIT:ScriptEventType = "STEP_HIT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character hits a note.
|
* Called when a character hits a note.
|
||||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
* This event IS cancelable! Canceling this event prevents the note from being hit,
|
||||||
* and will likely result in a miss later.
|
* and will likely result in a miss later.
|
||||||
*/
|
*/
|
||||||
public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT";
|
public static inline final NOTE_HIT:ScriptEventType = "NOTE_HIT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character misses a note.
|
* Called when a character misses a note.
|
||||||
* Important information such as note data, player/opponent, etc. are all provided.
|
* Important information such as note data, player/opponent, etc. are all provided.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||||
* avoiding a combo break and lost health.
|
* avoiding a combo break and lost health.
|
||||||
*/
|
*/
|
||||||
public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS";
|
public static inline final NOTE_MISS:ScriptEventType = "NOTE_MISS";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character presses a note when there was none there, causing them to lose health.
|
* Called when a character presses a note when there was none there, causing them to lose health.
|
||||||
* Important information such as direction pressed, etc. are all provided.
|
* Important information such as direction pressed, etc. are all provided.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
* This event IS cancelable! Canceling this event prevents the note from being considered missed,
|
||||||
* avoiding lost health/score and preventing the miss animation.
|
* avoiding lost health/score and preventing the miss animation.
|
||||||
*/
|
*/
|
||||||
public static inline final NOTE_GHOST_MISS:ScriptEventType = "NOTE_GHOST_MISS";
|
public static inline final NOTE_GHOST_MISS:ScriptEventType = "NOTE_GHOST_MISS";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
* Called when a song event is reached in the chart.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event IS cancelable! Cancelling this event prevents the event from being triggered,
|
||||||
*/
|
* thus blocking its normal functionality.
|
||||||
public static inline final SONG_START:ScriptEventType = "SONG_START";
|
*/
|
||||||
|
public static inline final SONG_EVENT:ScriptEventType = "SONG_EVENT";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the song ends. This happens as the instrumental and vocals end.
|
* Called when the song starts. This occurs as the countdown ends and the instrumental and vocals begin.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SONG_END:ScriptEventType = "SONG_END";
|
public static inline final SONG_START:ScriptEventType = "SONG_START";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the countdown begins. This occurs before the song starts.
|
* Called when the song ends. This happens as the instrumental and vocals end.
|
||||||
*
|
*
|
||||||
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
|
* This event is not cancelable.
|
||||||
* - The song will not start until you call Countdown.performCountdown() later.
|
*/
|
||||||
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
|
public static inline final SONG_END:ScriptEventType = "SONG_END";
|
||||||
*/
|
|
||||||
public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a step of the countdown happens.
|
* Called when the countdown begins. This occurs before the song starts.
|
||||||
* Includes information about what step of the countdown was hit.
|
*
|
||||||
*
|
* This event IS cancelable! Canceling this event will prevent the countdown from starting.
|
||||||
* This event IS cancelable! Canceling this event will pause the countdown.
|
* - The song will not start until you call Countdown.performCountdown() later.
|
||||||
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
* - Note that calling performCountdown() will trigger this event again, so be sure to add logic to ignore it.
|
||||||
*/
|
*/
|
||||||
public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP";
|
public static inline final COUNTDOWN_START:ScriptEventType = "COUNTDOWN_START";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the countdown is done but just before the song starts.
|
* Called when a step of the countdown happens.
|
||||||
*
|
* Includes information about what step of the countdown was hit.
|
||||||
* This event is not cancelable.
|
*
|
||||||
*/
|
* This event IS cancelable! Canceling this event will pause the countdown.
|
||||||
public static inline final COUNTDOWN_END:ScriptEventType = "COUNTDOWN_END";
|
* - The countdown will not resume until you call PlayState.resumeCountdown().
|
||||||
|
*/
|
||||||
|
public static inline final COUNTDOWN_STEP:ScriptEventType = "COUNTDOWN_STEP";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the game over screen triggers and the death animation plays.
|
* Called when the countdown is done but just before the song starts.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
public static inline final COUNTDOWN_END:ScriptEventType = "COUNTDOWN_END";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the player presses a key to restart the game.
|
* Called before the game over screen triggers and the death animation plays.
|
||||||
* This can happen from the pause menu or the game over screen.
|
*
|
||||||
*
|
* This event is not cancelable.
|
||||||
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
*/
|
||||||
*/
|
public static inline final GAME_OVER:ScriptEventType = "GAME_OVER";
|
||||||
public static inline final SONG_RETRY:ScriptEventType = "SONG_RETRY";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player pushes down any key on the keyboard.
|
* Called after the player presses a key to restart the game.
|
||||||
*
|
* This can happen from the pause menu or the game over screen.
|
||||||
* This event is not cancelable.
|
*
|
||||||
*/
|
* This event IS cancelable! Canceling this event will prevent the game from restarting.
|
||||||
public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN";
|
*/
|
||||||
|
public static inline final SONG_RETRY:ScriptEventType = "SONG_RETRY";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player releases a key on the keyboard.
|
* Called when the player pushes down any key on the keyboard.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final KEY_UP:ScriptEventType = "KEY_UP";
|
public static inline final KEY_DOWN:ScriptEventType = "KEY_DOWN";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game has finished loading the notes from JSON.
|
* Called when the player releases a key on the keyboard.
|
||||||
* This allows modders to mutate the notes before they are used in the song.
|
*
|
||||||
*
|
* This event is not cancelable.
|
||||||
* This event is not cancelable.
|
*/
|
||||||
*/
|
public static inline final KEY_UP:ScriptEventType = "KEY_UP";
|
||||||
public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game is about to switch the current FlxState.
|
* Called when the game has finished loading the notes from JSON.
|
||||||
*
|
* This allows modders to mutate the notes before they are used in the song.
|
||||||
* This event is not cancelable.
|
*
|
||||||
*/
|
* This event is not cancelable.
|
||||||
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = "STATE_CHANGE_BEGIN";
|
*/
|
||||||
|
public static inline final SONG_LOADED:ScriptEventType = "SONG_LOADED";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game has finished switching the current FlxState.
|
* Called when the game is about to switch the current FlxState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final STATE_CHANGE_END:ScriptEventType = "STATE_CHANGE_END";
|
public static inline final STATE_CHANGE_BEGIN:ScriptEventType = "STATE_CHANGE_BEGIN";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game is about to open a new FlxSubState.
|
* Called when the game has finished switching the current FlxState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = "SUBSTATE_OPEN_BEGIN";
|
public static inline final STATE_CHANGE_END:ScriptEventType = "STATE_CHANGE_END";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game has finished opening a new FlxSubState.
|
* Called when the game is about to open a new FlxSubState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SUBSTATE_OPEN_END:ScriptEventType = "SUBSTATE_OPEN_END";
|
public static inline final SUBSTATE_OPEN_BEGIN:ScriptEventType = "SUBSTATE_OPEN_BEGIN";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game is about to close the current FlxSubState.
|
* Called when the game has finished opening a new FlxSubState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = "SUBSTATE_CLOSE_BEGIN";
|
public static inline final SUBSTATE_OPEN_END:ScriptEventType = "SUBSTATE_OPEN_END";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game has finished closing the current FlxSubState.
|
* Called when the game is about to close the current FlxSubState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = "SUBSTATE_CLOSE_END";
|
public static inline final SUBSTATE_CLOSE_BEGIN:ScriptEventType = "SUBSTATE_CLOSE_BEGIN";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the game is exiting the current FlxState.
|
* Called when the game has finished closing the current FlxSubState.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
*/
|
*/
|
||||||
/**
|
public static inline final SUBSTATE_CLOSE_END:ScriptEventType = "SUBSTATE_CLOSE_END";
|
||||||
* If true, the behavior associated with this event can be prevented.
|
|
||||||
* For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
|
|
||||||
* until another script restarts it, or cancelling NOTE_HIT should cause the note to be missed.
|
|
||||||
*/
|
|
||||||
public var cancelable(default, null):Bool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type associated with the event.
|
* Called when the game is exiting the current FlxState.
|
||||||
*/
|
*
|
||||||
public var type(default, null):ScriptEventType;
|
* This event is not cancelable.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* If true, the behavior associated with this event can be prevented.
|
||||||
|
* For example, cancelling COUNTDOWN_START should prevent the countdown from starting,
|
||||||
|
* until another script restarts it, or cancelling NOTE_HIT should cause the note to be missed.
|
||||||
|
*/
|
||||||
|
public var cancelable(default, null):Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the event should continue to be triggered on additional targets.
|
* The type associated with the event.
|
||||||
*/
|
*/
|
||||||
public var shouldPropagate(default, null):Bool;
|
public var type(default, null):ScriptEventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the event has been canceled by one of the scripts that received it.
|
* Whether the event should continue to be triggered on additional targets.
|
||||||
*/
|
*/
|
||||||
public var eventCanceled(default, null):Bool;
|
public var shouldPropagate(default, null):Bool;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, cancelable:Bool = false):Void
|
/**
|
||||||
{
|
* Whether the event has been canceled by one of the scripts that received it.
|
||||||
this.type = type;
|
*/
|
||||||
this.cancelable = cancelable;
|
public var eventCanceled(default, null):Bool;
|
||||||
this.eventCanceled = false;
|
|
||||||
this.shouldPropagate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public function new(type:ScriptEventType, cancelable:Bool = false):Void
|
||||||
* Call this function on a cancelable event to cancel the associated behavior.
|
{
|
||||||
* For example, cancelling COUNTDOWN_START will prevent the countdown from starting.
|
this.type = type;
|
||||||
*/
|
this.cancelable = cancelable;
|
||||||
public function cancelEvent():Void
|
this.eventCanceled = false;
|
||||||
{
|
this.shouldPropagate = true;
|
||||||
if (cancelable)
|
}
|
||||||
{
|
|
||||||
eventCanceled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cancel():Void
|
/**
|
||||||
{
|
* Call this function on a cancelable event to cancel the associated behavior.
|
||||||
// This typo happens enough that I just added this.
|
* For example, cancelling COUNTDOWN_START will prevent the countdown from starting.
|
||||||
cancelEvent();
|
*/
|
||||||
}
|
public function cancelEvent():Void
|
||||||
|
{
|
||||||
|
if (cancelable)
|
||||||
|
{
|
||||||
|
eventCanceled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public function cancel():Void
|
||||||
* Call this function to stop any other Scripteds from receiving the event.
|
{
|
||||||
*/
|
// This typo happens enough that I just added this.
|
||||||
public function stopPropagation():Void
|
cancelEvent();
|
||||||
{
|
}
|
||||||
shouldPropagate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toString():String
|
/**
|
||||||
{
|
* Call this function to stop any other Scripteds from receiving the event.
|
||||||
return 'ScriptEvent(type=$type, cancelable=$cancelable)';
|
*/
|
||||||
}
|
public function stopPropagation():Void
|
||||||
|
{
|
||||||
|
shouldPropagate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'ScriptEvent(type=$type, cancelable=$cancelable)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,29 +296,29 @@ class ScriptEvent
|
||||||
*/
|
*/
|
||||||
class NoteScriptEvent extends ScriptEvent
|
class NoteScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The note associated with this event.
|
* The note associated with this event.
|
||||||
* You cannot replace it, but you can edit it.
|
* You cannot replace it, but you can edit it.
|
||||||
*/
|
*/
|
||||||
public var note(default, null):Note;
|
public var note(default, null):Note;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The combo count as it is with this event.
|
* The combo count as it is with this event.
|
||||||
* Will be (combo) on miss events and (combo + 1) on hit events (the stored combo count won't update if the event is cancelled).
|
* Will be (combo) on miss events and (combo + 1) on hit events (the stored combo count won't update if the event is cancelled).
|
||||||
*/
|
*/
|
||||||
public var comboCount(default, null):Int;
|
public var comboCount(default, null):Int;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void
|
public function new(type:ScriptEventType, note:Note, comboCount:Int = 0, cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, cancelable);
|
super(type, cancelable);
|
||||||
this.note = note;
|
this.note = note;
|
||||||
this.comboCount = comboCount;
|
this.comboCount = comboCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ', comboCount=' + comboCount + ')';
|
return 'NoteScriptEvent(type=' + type + ', cancelable=' + cancelable + ', note=' + note + ', comboCount=' + comboCount + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -318,52 +326,81 @@ class NoteScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class GhostMissNoteScriptEvent extends ScriptEvent
|
class GhostMissNoteScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The direction that was mistakenly pressed.
|
* The direction that was mistakenly pressed.
|
||||||
*/
|
*/
|
||||||
public var dir(default, null):NoteDir;
|
public var dir(default, null):NoteDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether there was a note within judgement range when this ghost note was pressed.
|
* Whether there was a note within judgement range when this ghost note was pressed.
|
||||||
*/
|
*/
|
||||||
public var hasPossibleNotes(default, null):Bool;
|
public var hasPossibleNotes(default, null):Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much health should be lost when this ghost note is pressed.
|
* How much health should be lost when this ghost note is pressed.
|
||||||
* Remember that max health is 2.00.
|
* Remember that max health is 2.00.
|
||||||
*/
|
*/
|
||||||
public var healthChange(default, default):Float;
|
public var healthChange(default, default):Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much score should be lost when this ghost note is pressed.
|
* How much score should be lost when this ghost note is pressed.
|
||||||
*/
|
*/
|
||||||
public var scoreChange(default, default):Int;
|
public var scoreChange(default, default):Int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to play the record scratch sound.
|
* Whether to play the record scratch sound.
|
||||||
*/
|
*/
|
||||||
public var playSound(default, default):Bool;
|
public var playSound(default, default):Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to play the miss animation on the player.
|
* Whether to play the miss animation on the player.
|
||||||
*/
|
*/
|
||||||
public var playAnim(default, default):Bool;
|
public var playAnim(default, default):Bool;
|
||||||
|
|
||||||
public function new(dir:NoteDir, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
|
public function new(dir:NoteDir, hasPossibleNotes:Bool, healthChange:Float, scoreChange:Int):Void
|
||||||
{
|
{
|
||||||
super(ScriptEvent.NOTE_GHOST_MISS, true);
|
super(ScriptEvent.NOTE_GHOST_MISS, true);
|
||||||
this.dir = dir;
|
this.dir = dir;
|
||||||
this.hasPossibleNotes = hasPossibleNotes;
|
this.hasPossibleNotes = hasPossibleNotes;
|
||||||
this.healthChange = healthChange;
|
this.healthChange = healthChange;
|
||||||
this.scoreChange = scoreChange;
|
this.scoreChange = scoreChange;
|
||||||
this.playSound = true;
|
this.playSound = true;
|
||||||
this.playAnim = true;
|
this.playAnim = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'GhostMissNoteScriptEvent(dir=' + dir + ', hasPossibleNotes=' + hasPossibleNotes + ')';
|
return 'GhostMissNoteScriptEvent(dir=' + dir + ', hasPossibleNotes=' + hasPossibleNotes + ')';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is fired when the song reaches an event.
|
||||||
|
*/
|
||||||
|
class SongEventScriptEvent extends ScriptEvent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The note associated with this event.
|
||||||
|
* You cannot replace it, but you can edit it.
|
||||||
|
*/
|
||||||
|
public var event(default, null):funkin.play.song.SongData.SongEventData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The combo count as it is with this event.
|
||||||
|
* Will be (combo) on miss events and (combo + 1) on hit events (the stored combo count won't update if the event is cancelled).
|
||||||
|
*/
|
||||||
|
public var comboCount(default, null):Int;
|
||||||
|
|
||||||
|
public function new(event:funkin.play.song.SongData.SongEventData):Void
|
||||||
|
{
|
||||||
|
super(ScriptEvent.SONG_EVENT, true);
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function toString():String
|
||||||
|
{
|
||||||
|
return 'SongEventScriptEvent(event=' + event + ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -371,22 +408,22 @@ class GhostMissNoteScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class UpdateScriptEvent extends ScriptEvent
|
class UpdateScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The note associated with this event.
|
* The note associated with this event.
|
||||||
* You cannot replace it, but you can edit it.
|
* You cannot replace it, but you can edit it.
|
||||||
*/
|
*/
|
||||||
public var elapsed(default, null):Float;
|
public var elapsed(default, null):Float;
|
||||||
|
|
||||||
public function new(elapsed:Float):Void
|
public function new(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
super(ScriptEvent.UPDATE, false);
|
super(ScriptEvent.UPDATE, false);
|
||||||
this.elapsed = elapsed;
|
this.elapsed = elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'UpdateScriptEvent(elapsed=$elapsed)';
|
return 'UpdateScriptEvent(elapsed=$elapsed)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -395,27 +432,27 @@ class UpdateScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class SongTimeScriptEvent extends ScriptEvent
|
class SongTimeScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current beat of the song.
|
* The current beat of the song.
|
||||||
*/
|
*/
|
||||||
public var beat(default, null):Int;
|
public var beat(default, null):Int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current step of the song.
|
* The current step of the song.
|
||||||
*/
|
*/
|
||||||
public var step(default, null):Int;
|
public var step(default, null):Int;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, beat:Int, step:Int):Void
|
public function new(type:ScriptEventType, beat:Int, step:Int):Void
|
||||||
{
|
{
|
||||||
super(type, true);
|
super(type, true);
|
||||||
this.beat = beat;
|
this.beat = beat;
|
||||||
this.step = step;
|
this.step = step;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'SongTimeScriptEvent(type=' + type + ', beat=' + beat + ', step=' + step + ')';
|
return 'SongTimeScriptEvent(type=' + type + ', beat=' + beat + ', step=' + step + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -424,21 +461,21 @@ class SongTimeScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class CountdownScriptEvent extends ScriptEvent
|
class CountdownScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current step of the countdown.
|
* The current step of the countdown.
|
||||||
*/
|
*/
|
||||||
public var step(default, null):CountdownStep;
|
public var step(default, null):CountdownStep;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void
|
public function new(type:ScriptEventType, step:CountdownStep, cancelable = true):Void
|
||||||
{
|
{
|
||||||
super(type, cancelable);
|
super(type, cancelable);
|
||||||
this.step = step;
|
this.step = step;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'CountdownScriptEvent(type=' + type + ', step=' + step + ')';
|
return 'CountdownScriptEvent(type=' + type + ', step=' + step + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -446,21 +483,21 @@ class CountdownScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class KeyboardInputScriptEvent extends ScriptEvent
|
class KeyboardInputScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The associated keyboard event.
|
* The associated keyboard event.
|
||||||
*/
|
*/
|
||||||
public var event(default, null):KeyboardEvent;
|
public var event(default, null):KeyboardEvent;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, event:KeyboardEvent):Void
|
public function new(type:ScriptEventType, event:KeyboardEvent):Void
|
||||||
{
|
{
|
||||||
super(type, false);
|
super(type, false);
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'KeyboardInputScriptEvent(type=' + type + ', event=' + event + ')';
|
return 'KeyboardInputScriptEvent(type=' + type + ', event=' + event + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -468,35 +505,35 @@ class KeyboardInputScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class SongLoadScriptEvent extends ScriptEvent
|
class SongLoadScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The note associated with this event.
|
* The note associated with this event.
|
||||||
* You cannot replace it, but you can edit it.
|
* You cannot replace it, but you can edit it.
|
||||||
*/
|
*/
|
||||||
public var notes(default, set):Array<Note>;
|
public var notes(default, set):Array<Note>;
|
||||||
|
|
||||||
public var id(default, null):String;
|
public var id(default, null):String;
|
||||||
|
|
||||||
public var difficulty(default, null):String;
|
public var difficulty(default, null):String;
|
||||||
|
|
||||||
function set_notes(notes:Array<Note>):Array<Note>
|
function set_notes(notes:Array<Note>):Array<Note>
|
||||||
{
|
{
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
return this.notes;
|
return this.notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function new(id:String, difficulty:String, notes:Array<Note>):Void
|
public function new(id:String, difficulty:String, notes:Array<Note>):Void
|
||||||
{
|
{
|
||||||
super(ScriptEvent.SONG_LOADED, false);
|
super(ScriptEvent.SONG_LOADED, false);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
var noteStr = notes == null ? 'null' : 'Array(' + notes.length + ')';
|
var noteStr = notes == null ? 'null' : 'Array(' + notes.length + ')';
|
||||||
return 'SongLoadScriptEvent(notes=$noteStr, id=$id, difficulty=$difficulty)';
|
return 'SongLoadScriptEvent(notes=$noteStr, id=$id, difficulty=$difficulty)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -504,21 +541,21 @@ class SongLoadScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class StateChangeScriptEvent extends ScriptEvent
|
class StateChangeScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The state the game is moving into.
|
* The state the game is moving into.
|
||||||
*/
|
*/
|
||||||
public var targetState(default, null):FlxState;
|
public var targetState(default, null):FlxState;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, targetState:FlxState, cancelable:Bool = false):Void
|
public function new(type:ScriptEventType, targetState:FlxState, cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, cancelable);
|
super(type, cancelable);
|
||||||
this.targetState = targetState;
|
this.targetState = targetState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'StateChangeScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
return 'StateChangeScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -526,21 +563,21 @@ class StateChangeScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class SubStateScriptEvent extends ScriptEvent
|
class SubStateScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The state the game is moving into.
|
* The state the game is moving into.
|
||||||
*/
|
*/
|
||||||
public var targetState(default, null):FlxSubState;
|
public var targetState(default, null):FlxSubState;
|
||||||
|
|
||||||
public function new(type:ScriptEventType, targetState:FlxSubState, cancelable:Bool = false):Void
|
public function new(type:ScriptEventType, targetState:FlxSubState, cancelable:Bool = false):Void
|
||||||
{
|
{
|
||||||
super(type, cancelable);
|
super(type, cancelable);
|
||||||
this.targetState = targetState;
|
this.targetState = targetState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
public override function toString():String
|
||||||
{
|
{
|
||||||
return 'SubStateScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
return 'SubStateScriptEvent(type=' + type + ', targetState=' + targetState + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -548,14 +585,14 @@ class SubStateScriptEvent extends ScriptEvent
|
||||||
*/
|
*/
|
||||||
class PauseScriptEvent extends ScriptEvent
|
class PauseScriptEvent extends ScriptEvent
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Whether to use the Gitaroo Man pause.
|
* Whether to use the Gitaroo Man pause.
|
||||||
*/
|
*/
|
||||||
public var gitaroo(default, default):Bool;
|
public var gitaroo(default, default):Bool;
|
||||||
|
|
||||||
public function new(gitaroo:Bool):Void
|
public function new(gitaroo:Bool):Void
|
||||||
{
|
{
|
||||||
super(ScriptEvent.PAUSE, true);
|
super(ScriptEvent.PAUSE, true);
|
||||||
this.gitaroo = gitaroo;
|
this.gitaroo = gitaroo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,109 +10,111 @@ import funkin.modding.events.ScriptEvent;
|
||||||
*/
|
*/
|
||||||
class Module implements IPlayStateScriptedClass implements IStateChangingScriptedClass
|
class Module implements IPlayStateScriptedClass implements IStateChangingScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Whether the module is currently active.
|
* Whether the module is currently active.
|
||||||
*/
|
*/
|
||||||
public var active(default, set):Bool = true;
|
public var active(default, set):Bool = true;
|
||||||
|
|
||||||
function set_active(value:Bool):Bool
|
function set_active(value:Bool):Bool
|
||||||
{
|
{
|
||||||
this.active = value;
|
this.active = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var moduleId(default, null):String = 'UNKNOWN';
|
public var moduleId(default, null):String = 'UNKNOWN';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the order in which modules receive events.
|
* Determines the order in which modules receive events.
|
||||||
* You can modify this to change the order in which a given module receives events.
|
* You can modify this to change the order in which a given module receives events.
|
||||||
*
|
*
|
||||||
* Priority 1 is processed before Priority 1000, etc.
|
* Priority 1 is processed before Priority 1000, etc.
|
||||||
*/
|
*/
|
||||||
public var priority(default, set):Int;
|
public var priority(default, set):Int;
|
||||||
|
|
||||||
function set_priority(value:Int):Int
|
function set_priority(value:Int):Int
|
||||||
{
|
{
|
||||||
this.priority = value;
|
this.priority = value;
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
ModuleHandler.reorderModuleCache();
|
ModuleHandler.reorderModuleCache();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the module is initialized.
|
* Called when the module is initialized.
|
||||||
* It may not be safe to reference other modules here since they may not be loaded yet.
|
* It may not be safe to reference other modules here since they may not be loaded yet.
|
||||||
*
|
*
|
||||||
* NOTE: To make the module start inactive, call `this.active = false` in the constructor.
|
* NOTE: To make the module start inactive, call `this.active = false` in the constructor.
|
||||||
*/
|
*/
|
||||||
public function new(moduleId:String, priority:Int = 1000):Void
|
public function new(moduleId:String, priority:Int = 1000):Void
|
||||||
{
|
{
|
||||||
this.moduleId = moduleId;
|
this.moduleId = moduleId;
|
||||||
this.priority = priority;
|
this.priority = priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString()
|
public function toString()
|
||||||
{
|
{
|
||||||
return 'Module(' + this.moduleId + ')';
|
return 'Module(' + this.moduleId + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Half of these aren't actually being called!!!!!!!
|
// TODO: Half of these aren't actually being called!!!!!!!
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent) {}
|
public function onScriptEvent(event:ScriptEvent) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the module is first created.
|
* Called when the module is first created.
|
||||||
* This happens before the title screen appears!
|
* This happens before the title screen appears!
|
||||||
*/
|
*/
|
||||||
public function onCreate(event:ScriptEvent) {}
|
public function onCreate(event:ScriptEvent) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a module is destroyed.
|
* Called when a module is destroyed.
|
||||||
* This currently only happens when reloading modules with F5.
|
* This currently only happens when reloading modules with F5.
|
||||||
*/
|
*/
|
||||||
public function onDestroy(event:ScriptEvent) {}
|
public function onDestroy(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onUpdate(event:UpdateScriptEvent) {}
|
public function onUpdate(event:UpdateScriptEvent) {}
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent) {}
|
public function onPause(event:PauseScriptEvent) {}
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent) {}
|
public function onResume(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onSongStart(event:ScriptEvent) {}
|
public function onSongStart(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onSongEnd(event:ScriptEvent) {}
|
public function onSongEnd(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onGameOver(event:ScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteHit(event:NoteScriptEvent) {}
|
public function onNoteHit(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||||
|
|
||||||
public function onBeatHit(event:SongTimeScriptEvent) {}
|
public function onBeatHit(event:SongTimeScriptEvent) {}
|
||||||
|
|
||||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||||
|
|
||||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
||||||
|
|
||||||
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
||||||
|
|
||||||
public function onSubstateOpenBegin(event:SubStateScriptEvent) {}
|
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
||||||
|
|
||||||
public function onSubstateOpenEnd(event:SubStateScriptEvent) {}
|
public function onSubstateOpenBegin(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
public function onSubstateCloseBegin(event:SubStateScriptEvent) {}
|
public function onSubstateOpenEnd(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
public function onSubstateCloseEnd(event:SubStateScriptEvent) {}
|
public function onSubstateCloseBegin(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
public function onSongRetry(event:ScriptEvent) {}
|
public function onSubstateCloseEnd(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
|
public function onSongRetry(event:ScriptEvent) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,132 +11,137 @@ import funkin.modding.module.ScriptedModule;
|
||||||
*/
|
*/
|
||||||
class ModuleHandler
|
class ModuleHandler
|
||||||
{
|
{
|
||||||
static final moduleCache:Map<String, Module> = new Map<String, Module>();
|
static final moduleCache:Map<String, Module> = new Map<String, Module>();
|
||||||
static var modulePriorityOrder:Array<String> = [];
|
static var modulePriorityOrder:Array<String> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||||
*
|
*
|
||||||
* If you want to force stages to be reloaded, you can just call this function again.
|
* If you want to force stages to be reloaded, you can just call this function again.
|
||||||
*/
|
*/
|
||||||
public static function loadModuleCache():Void
|
public static function loadModuleCache():Void
|
||||||
{
|
{
|
||||||
// Clear any stages that are cached if there were any.
|
// Clear any stages that are cached if there were any.
|
||||||
clearModuleCache();
|
clearModuleCache();
|
||||||
trace("[MODULEHANDLER] Loading module cache...");
|
trace("[MODULEHANDLER] Loading module cache...");
|
||||||
|
|
||||||
var scriptedModuleClassNames:Array<String> = ScriptedModule.listScriptClasses();
|
var scriptedModuleClassNames:Array<String> = ScriptedModule.listScriptClasses();
|
||||||
trace(' Instantiating ${scriptedModuleClassNames.length} modules...');
|
trace(' Instantiating ${scriptedModuleClassNames.length} modules...');
|
||||||
for (moduleCls in scriptedModuleClassNames)
|
for (moduleCls in scriptedModuleClassNames)
|
||||||
{
|
{
|
||||||
var module:Module = ScriptedModule.init(moduleCls, moduleCls);
|
var module:Module = ScriptedModule.init(moduleCls, moduleCls);
|
||||||
if (module != null)
|
if (module != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded module: ${moduleCls}');
|
trace(' Loaded module: ${moduleCls}');
|
||||||
|
|
||||||
// Then store it.
|
// Then store it.
|
||||||
addToModuleCache(module);
|
addToModuleCache(module);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace(' Failed to instantiate module: ${moduleCls}');
|
trace(' Failed to instantiate module: ${moduleCls}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reorderModuleCache();
|
reorderModuleCache();
|
||||||
|
|
||||||
trace("[MODULEHANDLER] Module cache loaded.");
|
trace("[MODULEHANDLER] Module cache loaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildModuleCallbacks():Void
|
public static function buildModuleCallbacks():Void
|
||||||
{
|
{
|
||||||
FlxG.signals.postStateSwitch.add(onStateSwitchComplete);
|
FlxG.signals.postStateSwitch.add(onStateSwitchComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function onStateSwitchComplete():Void
|
static function onStateSwitchComplete():Void
|
||||||
{
|
{
|
||||||
callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true));
|
callEvent(new StateChangeScriptEvent(ScriptEvent.STATE_CHANGE_END, FlxG.state, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
static function addToModuleCache(module:Module):Void
|
static function addToModuleCache(module:Module):Void
|
||||||
{
|
{
|
||||||
moduleCache.set(module.moduleId, module);
|
moduleCache.set(module.moduleId, module);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function reorderModuleCache():Void
|
static function reorderModuleCache():Void
|
||||||
{
|
{
|
||||||
modulePriorityOrder = moduleCache.keys().array();
|
modulePriorityOrder = moduleCache.keys().array();
|
||||||
|
|
||||||
modulePriorityOrder.sort(function(a:String, b:String):Int
|
modulePriorityOrder.sort(function(a:String, b:String):Int
|
||||||
{
|
{
|
||||||
var aModule:Module = moduleCache.get(a);
|
var aModule:Module = moduleCache.get(a);
|
||||||
var bModule:Module = moduleCache.get(b);
|
var bModule:Module = moduleCache.get(b);
|
||||||
|
|
||||||
if (aModule.priority != bModule.priority)
|
if (aModule.priority != bModule.priority)
|
||||||
{
|
{
|
||||||
return aModule.priority - bModule.priority;
|
return aModule.priority - bModule.priority;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Sort alphabetically. Yes that's how this works.
|
// Sort alphabetically. Yes that's how this works.
|
||||||
return a > b ? 1 : -1;
|
return a > b ? 1 : -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getModule(moduleId:String):Module
|
public static function getModule(moduleId:String):Module
|
||||||
{
|
{
|
||||||
return moduleCache.get(moduleId);
|
return moduleCache.get(moduleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function activateModule(moduleId:String):Void
|
public static function activateModule(moduleId:String):Void
|
||||||
{
|
{
|
||||||
var module:Module = getModule(moduleId);
|
var module:Module = getModule(moduleId);
|
||||||
if (module != null)
|
if (module != null)
|
||||||
{
|
{
|
||||||
module.active = true;
|
module.active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function deactivateModule(moduleId:String):Void
|
public static function deactivateModule(moduleId:String):Void
|
||||||
{
|
{
|
||||||
var module:Module = getModule(moduleId);
|
var module:Module = getModule(moduleId);
|
||||||
if (module != null)
|
if (module != null)
|
||||||
{
|
{
|
||||||
module.active = false;
|
module.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the module cache, forcing all modules to call shutdown events.
|
* Clear the module cache, forcing all modules to call shutdown events.
|
||||||
*/
|
*/
|
||||||
public static function clearModuleCache():Void
|
public static function clearModuleCache():Void
|
||||||
{
|
{
|
||||||
if (moduleCache != null)
|
if (moduleCache != null)
|
||||||
{
|
{
|
||||||
var event = new ScriptEvent(ScriptEvent.DESTROY, false);
|
var event = new ScriptEvent(ScriptEvent.DESTROY, false);
|
||||||
|
|
||||||
// Note: Ignore stopPropagation()
|
// Note: Ignore stopPropagation()
|
||||||
for (key => value in moduleCache)
|
for (key => value in moduleCache)
|
||||||
{
|
{
|
||||||
ScriptEventDispatcher.callEvent(value, event);
|
ScriptEventDispatcher.callEvent(value, event);
|
||||||
moduleCache.remove(key);
|
moduleCache.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleCache.clear();
|
moduleCache.clear();
|
||||||
modulePriorityOrder = [];
|
modulePriorityOrder = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function callEvent(event:ScriptEvent):Void
|
public static function callEvent(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
for (moduleId in modulePriorityOrder)
|
for (moduleId in modulePriorityOrder)
|
||||||
{
|
{
|
||||||
var module:Module = moduleCache.get(moduleId);
|
var module:Module = moduleCache.get(moduleId);
|
||||||
// The module needs to be active to receive events.
|
// The module needs to be active to receive events.
|
||||||
if (module != null && module.active)
|
if (module != null && module.active)
|
||||||
{
|
{
|
||||||
ScriptEventDispatcher.callEvent(module, event);
|
ScriptEventDispatcher.callEvent(module, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static inline function callOnCreate():Void
|
||||||
|
{
|
||||||
|
callEvent(new ScriptEvent(ScriptEvent.CREATE, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,87 +13,87 @@ import openfl.Assets;
|
||||||
*/
|
*/
|
||||||
class NoteUtil
|
class NoteUtil
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* IDK THING FOR BOTH LOL! DIS SHIT HACK-Y
|
* IDK THING FOR BOTH LOL! DIS SHIT HACK-Y
|
||||||
* @param jsonPath
|
* @param jsonPath
|
||||||
* @return Map<Int, Array<SongEventInfo>>
|
* @return Map<Int, Array<SongEventInfo>>
|
||||||
*/
|
*/
|
||||||
public static function loadSongEvents(jsonPath:String):Map<Int, Array<SongEventInfo>>
|
public static function loadSongEvents(jsonPath:String):Map<Int, Array<SongEventInfo>>
|
||||||
{
|
{
|
||||||
return parseSongEvents(loadSongEventFromJson(jsonPath));
|
return parseSongEvents(loadSongEventFromJson(jsonPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function loadSongEventFromJson(jsonPath:String):Array<SongEvent>
|
public static function loadSongEventFromJson(jsonPath:String):Array<SongEvent>
|
||||||
{
|
{
|
||||||
var daEvents:Array<SongEvent>;
|
var daEvents:Array<SongEvent>;
|
||||||
daEvents = cast Json.parse(Assets.getText(jsonPath)).events; // DUMB LIL DETAIL HERE: MAKE SURE THAT .events IS THERE??
|
daEvents = cast Json.parse(Assets.getText(jsonPath)).events; // DUMB LIL DETAIL HERE: MAKE SURE THAT .events IS THERE??
|
||||||
trace('GET JSON SONG EVENTS:');
|
trace('GET JSON SONG EVENTS:');
|
||||||
trace(daEvents);
|
trace(daEvents);
|
||||||
return daEvents;
|
return daEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses song event json stuff into a neater lil map grouping?
|
* Parses song event json stuff into a neater lil map grouping?
|
||||||
* @param songEvents
|
* @param songEvents
|
||||||
*/
|
*/
|
||||||
public static function parseSongEvents(songEvents:Array<SongEvent>):Map<Int, Array<SongEventInfo>>
|
public static function parseSongEvents(songEvents:Array<SongEvent>):Map<Int, Array<SongEventInfo>>
|
||||||
{
|
{
|
||||||
var songData:Map<Int, Array<SongEventInfo>> = new Map();
|
var songData:Map<Int, Array<SongEventInfo>> = new Map();
|
||||||
|
|
||||||
for (songEvent in songEvents)
|
for (songEvent in songEvents)
|
||||||
{
|
{
|
||||||
trace(songEvent);
|
trace(songEvent);
|
||||||
if (songData[songEvent.t] == null)
|
if (songData[songEvent.t] == null)
|
||||||
songData[songEvent.t] = [];
|
songData[songEvent.t] = [];
|
||||||
|
|
||||||
songData[songEvent.t].push({songEventType: songEvent.e, value: songEvent.v, activated: false});
|
songData[songEvent.t].push({songEventType: songEvent.e, value: songEvent.v, activated: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
trace("FINISH SONG EVENTS!");
|
trace("FINISH SONG EVENTS!");
|
||||||
trace(songData);
|
trace(songData);
|
||||||
|
|
||||||
return songData;
|
return songData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function checkSongEvents(songData:Map<Int, Array<SongEventInfo>>, time:Float)
|
public static function checkSongEvents(songData:Map<Int, Array<SongEventInfo>>, time:Float)
|
||||||
{
|
{
|
||||||
for (eventGrp in songData.keys())
|
for (eventGrp in songData.keys())
|
||||||
{
|
{
|
||||||
if (time >= eventGrp)
|
if (time >= eventGrp)
|
||||||
{
|
{
|
||||||
for (events in songData[eventGrp])
|
for (events in songData[eventGrp])
|
||||||
{
|
{
|
||||||
if (!events.activated)
|
if (!events.activated)
|
||||||
{
|
{
|
||||||
// TURN TO NICER SWITCH STATEMENT CHECKER OF EVENT TYPES!!
|
// TURN TO NICER SWITCH STATEMENT CHECKER OF EVENT TYPES!!
|
||||||
trace(events.value);
|
trace(events.value);
|
||||||
trace(eventGrp);
|
trace(eventGrp);
|
||||||
trace(Conductor.songPosition);
|
trace(Conductor.songPosition);
|
||||||
events.activated = true;
|
events.activated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef SongEventInfo =
|
typedef SongEventInfo =
|
||||||
{
|
{
|
||||||
var songEventType:SongEventType;
|
var songEventType:SongEventType;
|
||||||
var value:Dynamic;
|
var value:Dynamic;
|
||||||
var activated:Bool;
|
var activated:Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef SongEvent =
|
typedef SongEvent =
|
||||||
{
|
{
|
||||||
var t:Int;
|
var t:Int;
|
||||||
var e:SongEventType;
|
var e:SongEventType;
|
||||||
var v:Dynamic;
|
var v:Dynamic;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum abstract SongEventType(String)
|
enum abstract SongEventType(String)
|
||||||
{
|
{
|
||||||
var FocusCamera;
|
var FocusCamera;
|
||||||
var PlayCharAnim;
|
var PlayCharAnim;
|
||||||
var Trace;
|
var Trace;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -20,199 +20,199 @@ import funkin.util.assets.FlxAnimationUtil;
|
||||||
*/
|
*/
|
||||||
class MultiSparrowCharacter extends BaseCharacter
|
class MultiSparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The actual group which holds all spritesheets this character uses.
|
* The actual group which holds all spritesheets this character uses.
|
||||||
*/
|
*/
|
||||||
private var members:Map<String, FlxFramesCollection> = new Map<String, FlxFramesCollection>();
|
private var members:Map<String, FlxFramesCollection> = new Map<String, FlxFramesCollection>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map between animation names and what frame collection the animation should use.
|
* A map between animation names and what frame collection the animation should use.
|
||||||
*/
|
*/
|
||||||
private var animAssetPath:Map<String, String> = new Map<String, String>();
|
private var animAssetPath:Map<String, String> = new Map<String, String>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current frame collection being used.
|
* The current frame collection being used.
|
||||||
*/
|
*/
|
||||||
private var activeMember:String;
|
private var activeMember:String;
|
||||||
|
|
||||||
public function new(id:String)
|
public function new(id:String)
|
||||||
{
|
{
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function onCreate(event:ScriptEvent):Void
|
override function onCreate(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
trace('Creating MULTI SPARROW CHARACTER: ' + this.characterId);
|
trace('Creating Multi-Sparrow character: ' + this.characterId);
|
||||||
|
|
||||||
buildSprites();
|
buildSprites();
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSprites()
|
function buildSprites()
|
||||||
{
|
{
|
||||||
buildSpritesheets();
|
buildSpritesheets();
|
||||||
buildAnimations();
|
buildAnimations();
|
||||||
|
|
||||||
if (_data.isPixel)
|
if (_data.isPixel)
|
||||||
{
|
{
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.antialiasing = true;
|
this.antialiasing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSpritesheets()
|
function buildSpritesheets()
|
||||||
{
|
{
|
||||||
// Build the list of asset paths to use.
|
// Build the list of asset paths to use.
|
||||||
// Ignore nulls and duplicates.
|
// Ignore nulls and duplicates.
|
||||||
var assetList = [_data.assetPath];
|
var assetList = [_data.assetPath];
|
||||||
for (anim in _data.animations)
|
for (anim in _data.animations)
|
||||||
{
|
{
|
||||||
if (anim.assetPath != null && !assetList.contains(anim.assetPath))
|
if (anim.assetPath != null && !assetList.contains(anim.assetPath))
|
||||||
{
|
{
|
||||||
assetList.push(anim.assetPath);
|
assetList.push(anim.assetPath);
|
||||||
}
|
}
|
||||||
animAssetPath.set(anim.name, anim.assetPath);
|
animAssetPath.set(anim.name, anim.assetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the Sparrow atlas for each path and store them in the members map.
|
// Load the Sparrow atlas for each path and store them in the members map.
|
||||||
for (asset in assetList)
|
for (asset in assetList)
|
||||||
{
|
{
|
||||||
var texture:FlxFramesCollection = Paths.getSparrowAtlas(asset, 'shared');
|
var texture:FlxFramesCollection = Paths.getSparrowAtlas(asset, 'shared');
|
||||||
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
trace('Multi-Sparrow atlas could not load texture: ${asset}');
|
trace('Multi-Sparrow atlas could not load texture: ${asset}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Adding multi-sparrow atlas: ${asset}');
|
trace('Adding multi-sparrow atlas: ${asset}');
|
||||||
texture.parent.destroyOnNoUse = false;
|
texture.parent.destroyOnNoUse = false;
|
||||||
members.set(asset, texture);
|
members.set(asset, texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the default frame collection to start.
|
// Use the default frame collection to start.
|
||||||
loadFramesByAssetPath(_data.assetPath);
|
loadFramesByAssetPath(_data.assetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace this sprite's animation frames with the ones at this asset path.
|
* Replace this sprite's animation frames with the ones at this asset path.
|
||||||
*/
|
*/
|
||||||
function loadFramesByAssetPath(assetPath:String):Void
|
function loadFramesByAssetPath(assetPath:String):Void
|
||||||
{
|
{
|
||||||
if (_data.assetPath == null)
|
if (_data.assetPath == null)
|
||||||
{
|
{
|
||||||
trace('[ERROR] Multi-Sparrow character has no default asset path!');
|
trace('[ERROR] Multi-Sparrow character has no default asset path!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (assetPath == null)
|
if (assetPath == null)
|
||||||
{
|
{
|
||||||
// trace('Asset path is null, falling back to default. This is normal!');
|
// trace('Asset path is null, falling back to default. This is normal!');
|
||||||
loadFramesByAssetPath(_data.assetPath);
|
loadFramesByAssetPath(_data.assetPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activeMember == assetPath)
|
if (this.activeMember == assetPath)
|
||||||
{
|
{
|
||||||
// trace('Already using this asset path: ${assetPath}');
|
// trace('Already using this asset path: ${assetPath}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (members.exists(assetPath))
|
if (members.exists(assetPath))
|
||||||
{
|
{
|
||||||
// Switch to a new set of sprites.
|
// Switch to a new set of sprites.
|
||||||
// trace('Loading frames from asset path: ${assetPath}');
|
// trace('Loading frames from asset path: ${assetPath}');
|
||||||
this.frames = members.get(assetPath);
|
this.frames = members.get(assetPath);
|
||||||
this.activeMember = assetPath;
|
this.activeMember = assetPath;
|
||||||
this.setScale(_data.scale);
|
this.setScale(_data.scale);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[WARN] MultiSparrow character ${characterId} could not find asset path: ${assetPath}');
|
trace('[WARN] MultiSparrow character ${characterId} could not find asset path: ${assetPath}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace this sprite's animation frames with the ones needed to play this animation.
|
* Replace this sprite's animation frames with the ones needed to play this animation.
|
||||||
*/
|
*/
|
||||||
function loadFramesByAnimName(animName)
|
function loadFramesByAnimName(animName)
|
||||||
{
|
{
|
||||||
if (animAssetPath.exists(animName))
|
if (animAssetPath.exists(animName))
|
||||||
{
|
{
|
||||||
loadFramesByAssetPath(animAssetPath.get(animName));
|
loadFramesByAssetPath(animAssetPath.get(animName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[WARN] MultiSparrow character ${characterId} could not find animation: ${animName}');
|
trace('[WARN] MultiSparrow character ${characterId} could not find animation: ${animName}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAnimations()
|
function buildAnimations()
|
||||||
{
|
{
|
||||||
trace('[MULTISPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
trace('[MULTISPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||||
|
|
||||||
// We need to swap to the proper frame collection before adding the animations, I think?
|
// We need to swap to the proper frame collection before adding the animations, I think?
|
||||||
for (anim in _data.animations)
|
for (anim in _data.animations)
|
||||||
{
|
{
|
||||||
loadFramesByAnimName(anim.name);
|
loadFramesByAnimName(anim.name);
|
||||||
FlxAnimationUtil.addAtlasAnimation(this, anim);
|
FlxAnimationUtil.addAtlasAnimation(this, anim);
|
||||||
|
|
||||||
if (anim.offsets == null)
|
if (anim.offsets == null)
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, 0, 0);
|
setAnimationOffsets(anim.name, 0, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var animNames = this.animation.getNameList();
|
var animNames = this.animation.getNameList();
|
||||||
trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
trace('[MULTISPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||||
{
|
{
|
||||||
// Make sure we ignore other animations if we're currently playing a forced one,
|
// Make sure we ignore other animations if we're currently playing a forced one,
|
||||||
// unless we're forcing a new animation.
|
// unless we're forcing a new animation.
|
||||||
if (!this.canPlayOtherAnims && !ignoreOther)
|
if (!this.canPlayOtherAnims && !ignoreOther)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
loadFramesByAnimName(name);
|
loadFramesByAnimName(name);
|
||||||
super.playAnimation(name, restart, ignoreOther);
|
super.playAnimation(name, restart, ignoreOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function set_frames(value:FlxFramesCollection):FlxFramesCollection
|
override function set_frames(value:FlxFramesCollection):FlxFramesCollection
|
||||||
{
|
{
|
||||||
// DISABLE THIS SO WE DON'T DESTROY OUR HARD WORK
|
// DISABLE THIS SO WE DON'T DESTROY OUR HARD WORK
|
||||||
// WE WILL MAKE SURE TO LOAD THE PROPER SPRITESHEET BEFORE PLAYING AN ANIM
|
// WE WILL MAKE SURE TO LOAD THE PROPER SPRITESHEET BEFORE PLAYING AN ANIM
|
||||||
// if (animation != null)
|
// if (animation != null)
|
||||||
// {
|
// {
|
||||||
// animation.destroyAnimations();
|
// animation.destroyAnimations();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
graphic = value.parent;
|
graphic = value.parent;
|
||||||
this.frames = value;
|
this.frames = value;
|
||||||
this.frame = value.getByIndex(0);
|
this.frame = value.getByIndex(0);
|
||||||
this.numFrames = value.numFrames;
|
this.numFrames = value.numFrames;
|
||||||
resetHelpers();
|
resetHelpers();
|
||||||
this.bakedRotationAngle = 0;
|
this.bakedRotationAngle = 0;
|
||||||
this.animation.frameIndex = 0;
|
this.animation.frameIndex = 0;
|
||||||
graphicLoaded();
|
graphicLoaded();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.frames = null;
|
this.frames = null;
|
||||||
this.frame = null;
|
this.frame = null;
|
||||||
this.graphic = null;
|
this.graphic = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.frames;
|
return this.frames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,65 +11,65 @@ import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
*/
|
*/
|
||||||
class PackerCharacter extends BaseCharacter
|
class PackerCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
public function new(id:String)
|
public function new(id:String)
|
||||||
{
|
{
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function onCreate(event:ScriptEvent):Void
|
override function onCreate(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
trace('Creating PACKER CHARACTER: ' + this.characterId);
|
trace('Creating Packer character: ' + this.characterId);
|
||||||
|
|
||||||
loadSpritesheet();
|
loadSpritesheet();
|
||||||
loadAnimations();
|
loadAnimations();
|
||||||
|
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSpritesheet()
|
function loadSpritesheet()
|
||||||
{
|
{
|
||||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||||
|
|
||||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
|
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
|
||||||
if (tex == null)
|
if (tex == null)
|
||||||
{
|
{
|
||||||
trace('Could not load Packer sprite: ${_data.assetPath}');
|
trace('Could not load Packer sprite: ${_data.assetPath}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frames = tex;
|
this.frames = tex;
|
||||||
|
|
||||||
if (_data.isPixel)
|
if (_data.isPixel)
|
||||||
{
|
{
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.antialiasing = true;
|
this.antialiasing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setScale(_data.scale);
|
this.setScale(_data.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAnimations()
|
function loadAnimations()
|
||||||
{
|
{
|
||||||
trace('[PACKERCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
trace('[PACKERCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||||
|
|
||||||
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
||||||
|
|
||||||
for (anim in _data.animations)
|
for (anim in _data.animations)
|
||||||
{
|
{
|
||||||
if (anim.offsets == null)
|
if (anim.offsets == null)
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, 0, 0);
|
setAnimationOffsets(anim.name, 0, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var animNames = this.animation.getNameList();
|
var animNames = this.animation.getNameList();
|
||||||
trace('[PACKERCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
trace('[PACKERCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,65 +13,65 @@ import flixel.graphics.frames.FlxFramesCollection;
|
||||||
*/
|
*/
|
||||||
class SparrowCharacter extends BaseCharacter
|
class SparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
public function new(id:String)
|
public function new(id:String)
|
||||||
{
|
{
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function onCreate(event:ScriptEvent):Void
|
override function onCreate(event:ScriptEvent):Void
|
||||||
{
|
{
|
||||||
trace('Creating SPARROW CHARACTER: ' + this.characterId);
|
trace('Creating Sparrow character: ' + this.characterId);
|
||||||
|
|
||||||
loadSpritesheet();
|
loadSpritesheet();
|
||||||
loadAnimations();
|
loadAnimations();
|
||||||
|
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSpritesheet()
|
function loadSpritesheet()
|
||||||
{
|
{
|
||||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||||
|
|
||||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
||||||
if (tex == null)
|
if (tex == null)
|
||||||
{
|
{
|
||||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frames = tex;
|
this.frames = tex;
|
||||||
|
|
||||||
if (_data.isPixel)
|
if (_data.isPixel)
|
||||||
{
|
{
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.antialiasing = true;
|
this.antialiasing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setScale(_data.scale);
|
this.setScale(_data.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAnimations()
|
function loadAnimations()
|
||||||
{
|
{
|
||||||
trace('[SPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
trace('[SPARROWCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||||
|
|
||||||
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
FlxAnimationUtil.addAtlasAnimations(this, _data.animations);
|
||||||
|
|
||||||
for (anim in _data.animations)
|
for (anim in _data.animations)
|
||||||
{
|
{
|
||||||
if (anim.offsets == null)
|
if (anim.offsets == null)
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, 0, 0);
|
setAnimationOffsets(anim.name, 0, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
setAnimationOffsets(anim.name, anim.offsets[0], anim.offsets[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var animNames = this.animation.getNameList();
|
var animNames = this.animation.getNameList();
|
||||||
trace('[SPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
trace('[SPARROWCHAR] Successfully loaded ${animNames.length} animations for ${characterId}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
142
source/funkin/play/event/FocusCameraSongEvent.hx
Normal file
142
source/funkin/play/event/FocusCameraSongEvent.hx
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package funkin.play.event;
|
||||||
|
|
||||||
|
import funkin.play.event.SongEvent;
|
||||||
|
import funkin.play.song.SongData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a handler for a type of song event.
|
||||||
|
* It is used by the ScriptedSongEvent class to handle user-defined events.
|
||||||
|
*
|
||||||
|
* Example: Focus on Boyfriend:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "e": "FocusCamera",
|
||||||
|
* "v": {
|
||||||
|
* "char": 0,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Example: Focus on 10px above Girlfriend:
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "e": "FocusCamera",
|
||||||
|
* "v": {
|
||||||
|
* "char": 2,
|
||||||
|
* "y": -10,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Example: Focus on (100, 100):
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "e": "FocusCamera",
|
||||||
|
* "v": {
|
||||||
|
* "char": -1,
|
||||||
|
* "x": 100,
|
||||||
|
* "y": 100,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class FocusCameraSongEvent extends SongEvent
|
||||||
|
{
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('FocusCamera');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function handleEvent(data:SongEventData)
|
||||||
|
{
|
||||||
|
// Does nothing if there is no PlayState camera or stage.
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var posX = data.getFloat('x');
|
||||||
|
if (posX == null)
|
||||||
|
posX = 0.0;
|
||||||
|
var posY = data.getFloat('y');
|
||||||
|
if (posY == null)
|
||||||
|
posY = 0.0;
|
||||||
|
|
||||||
|
var char = data.getInt('char');
|
||||||
|
|
||||||
|
if (char == null)
|
||||||
|
char = cast data.value;
|
||||||
|
|
||||||
|
switch (char)
|
||||||
|
{
|
||||||
|
case -1: // Position
|
||||||
|
trace('Focusing camera on static position.');
|
||||||
|
var xTarget = posX;
|
||||||
|
var yTarget = posY;
|
||||||
|
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
|
||||||
|
case 0: // Boyfriend
|
||||||
|
// Focus the camera on the player.
|
||||||
|
trace('Focusing camera on player.');
|
||||||
|
var xTarget = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x + posX;
|
||||||
|
var yTarget = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y + posY;
|
||||||
|
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
|
||||||
|
case 1: // Dad
|
||||||
|
// Focus the camera on the dad.
|
||||||
|
trace('Focusing camera on dad.');
|
||||||
|
var xTarget = PlayState.instance.currentStage.getDad().cameraFocusPoint.x + posX;
|
||||||
|
var yTarget = PlayState.instance.currentStage.getDad().cameraFocusPoint.y + posY;
|
||||||
|
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
|
||||||
|
case 2: // Girlfriend
|
||||||
|
// Focus the camera on the girlfriend.
|
||||||
|
trace('Focusing camera on girlfriend.');
|
||||||
|
var xTarget = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x + posX;
|
||||||
|
var yTarget = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y + posY;
|
||||||
|
|
||||||
|
PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
|
||||||
|
default:
|
||||||
|
trace('Unknown camera focus: ' + data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function getTitle():String
|
||||||
|
{
|
||||||
|
return "Focus Camera";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "char": ENUM, // Which character to point to
|
||||||
|
* "x": FLOAT, // Optional x offset
|
||||||
|
* "y": FLOAT, // Optional y offset
|
||||||
|
* }
|
||||||
|
* @return SongEventSchema
|
||||||
|
*/
|
||||||
|
public override function getEventSchema():SongEventSchema
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "char",
|
||||||
|
title: "Character",
|
||||||
|
defaultValue: 0,
|
||||||
|
type: SongEventFieldType.ENUM,
|
||||||
|
keys: ["Position" => -1, "Boyfriend" => 0, "Dad" => 1, "Girlfriend" => 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "x",
|
||||||
|
title: "X Position",
|
||||||
|
defaultValue: 0,
|
||||||
|
step: 10.0,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "y",
|
||||||
|
title: "Y Position",
|
||||||
|
defaultValue: 0,
|
||||||
|
step: 10.0,
|
||||||
|
type: SongEventFieldType.FLOAT,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
111
source/funkin/play/event/PlayAnimationSongEvent.hx
Normal file
111
source/funkin/play/event/PlayAnimationSongEvent.hx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package funkin.play.event;
|
||||||
|
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import funkin.play.character.BaseCharacter;
|
||||||
|
import funkin.play.event.SongEvent;
|
||||||
|
import funkin.play.song.SongData;
|
||||||
|
|
||||||
|
class PlayAnimationSongEvent extends SongEvent
|
||||||
|
{
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('PlayAnimation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function handleEvent(data:SongEventData)
|
||||||
|
{
|
||||||
|
// Does nothing if there is no PlayState camera or stage.
|
||||||
|
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var targetName = data.getString('target');
|
||||||
|
var anim = data.getString('anim');
|
||||||
|
var force = data.getBool('force');
|
||||||
|
if (force == null)
|
||||||
|
force = false;
|
||||||
|
|
||||||
|
var target:FlxSprite = null;
|
||||||
|
|
||||||
|
switch (targetName)
|
||||||
|
{
|
||||||
|
case 'boyfriend':
|
||||||
|
trace('Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'bf':
|
||||||
|
trace('Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'player':
|
||||||
|
trace('Playing animation $anim on boyfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getBoyfriend();
|
||||||
|
case 'dad':
|
||||||
|
trace('Playing animation $anim on dad.');
|
||||||
|
target = PlayState.instance.currentStage.getDad();
|
||||||
|
case 'opponent':
|
||||||
|
trace('Playing animation $anim on dad.');
|
||||||
|
target = PlayState.instance.currentStage.getDad();
|
||||||
|
case 'girlfriend':
|
||||||
|
trace('Playing animation $anim on girlfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getGirlfriend();
|
||||||
|
case 'gf':
|
||||||
|
trace('Playing animation $anim on girlfriend.');
|
||||||
|
target = PlayState.instance.currentStage.getGirlfriend();
|
||||||
|
default:
|
||||||
|
target = PlayState.instance.currentStage.getNamedProp(targetName);
|
||||||
|
if (target == null)
|
||||||
|
trace('Unknown animation target: $targetName');
|
||||||
|
else
|
||||||
|
trace('Fetched animation target $targetName from stage.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
if (Std.isOfType(target, BaseCharacter))
|
||||||
|
{
|
||||||
|
var targetChar:BaseCharacter = cast target;
|
||||||
|
targetChar.playAnimation(anim, force, force);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.animation.play(anim, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function getTitle():String
|
||||||
|
{
|
||||||
|
return "Play Animation";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* "target": STRING, // Name of character or prop to point to.
|
||||||
|
* "anim": STRING, // Name of animation to play.
|
||||||
|
* "force": BOOL, // Whether to force the animation to play.
|
||||||
|
* }
|
||||||
|
* @return SongEventSchema
|
||||||
|
*/
|
||||||
|
public override function getEventSchema():SongEventSchema
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'target',
|
||||||
|
title: 'Target',
|
||||||
|
type: SongEventFieldType.STRING,
|
||||||
|
defaultValue: 'boyfriend',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'anim',
|
||||||
|
title: 'Animation',
|
||||||
|
type: SongEventFieldType.STRING,
|
||||||
|
defaultValue: 'idle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'force',
|
||||||
|
title: 'Force',
|
||||||
|
type: SongEventFieldType.BOOL,
|
||||||
|
defaultValue: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
9
source/funkin/play/event/ScriptedSongEvent.hx
Normal file
9
source/funkin/play/event/ScriptedSongEvent.hx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package funkin.play.event;
|
||||||
|
|
||||||
|
import funkin.play.song.Song;
|
||||||
|
import polymod.hscript.HScriptedClass;
|
||||||
|
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedSongEvent extends SongEvent implements HScriptedClass
|
||||||
|
{
|
||||||
|
}
|
|
@ -1,303 +1,270 @@
|
||||||
package funkin.play.event;
|
package funkin.play.event;
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
import funkin.util.macro.ClassMacro;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.song.SongData.SongEventData;
|
||||||
import funkin.play.character.BaseCharacter;
|
|
||||||
import funkin.play.song.SongData.RawSongEventData;
|
|
||||||
import haxe.DynamicAccess;
|
|
||||||
|
|
||||||
typedef RawSongEvent =
|
/**
|
||||||
|
* This class represents a handler for a type of song event.
|
||||||
|
* It is used by the ScriptedSongEvent class to handle user-defined events.
|
||||||
|
*/
|
||||||
|
class SongEvent
|
||||||
{
|
{
|
||||||
> RawSongEventData,
|
public var id:String;
|
||||||
|
|
||||||
/**
|
public function new(id:String)
|
||||||
* Whether the event has been activated or not.
|
{
|
||||||
*/
|
this.id = id;
|
||||||
var a:Bool;
|
}
|
||||||
|
|
||||||
|
public function handleEvent(data:SongEventData)
|
||||||
|
{
|
||||||
|
throw 'SongEvent.handleEvent() must be overridden!';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEventSchema():SongEventSchema
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle():String
|
||||||
|
{
|
||||||
|
return this.id.toTitleCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'SongEvent(${this.id})';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@:forward
|
class SongEventParser
|
||||||
abstract SongEvent(RawSongEvent)
|
|
||||||
{
|
{
|
||||||
public function new(time:Float, event:String, value:Dynamic = null)
|
/**
|
||||||
{
|
* Every built-in event class must be added to this list.
|
||||||
this = {
|
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
|
||||||
t: time,
|
*/
|
||||||
e: event,
|
private static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
|
||||||
v: value,
|
|
||||||
a: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public var time(get, set):Float;
|
/**
|
||||||
|
* Map of internal handlers for song events.
|
||||||
|
* These may be either `ScriptedSongEvents` or built-in classes extending `SongEvent`.
|
||||||
|
*/
|
||||||
|
static final eventCache:Map<String, SongEvent> = new Map<String, SongEvent>();
|
||||||
|
|
||||||
public function get_time():Float
|
public static function loadEventCache():Void
|
||||||
{
|
{
|
||||||
return this.t;
|
clearEventCache();
|
||||||
}
|
|
||||||
|
|
||||||
public function set_time(value:Float):Float
|
//
|
||||||
{
|
// BASE GAME EVENTS
|
||||||
return this.t = value;
|
//
|
||||||
}
|
registerBaseEvents();
|
||||||
|
registerScriptedEvents();
|
||||||
|
}
|
||||||
|
|
||||||
public var event(get, set):String;
|
static function registerBaseEvents()
|
||||||
|
{
|
||||||
|
trace('Instantiating ${BUILTIN_EVENTS.length} built-in song events...');
|
||||||
|
for (eventCls in BUILTIN_EVENTS)
|
||||||
|
{
|
||||||
|
var eventClsName:String = Type.getClassName(eventCls);
|
||||||
|
if (eventClsName == 'funkin.play.event.SongEvent' || eventClsName == 'funkin.play.event.ScriptedSongEvent')
|
||||||
|
continue;
|
||||||
|
|
||||||
public function get_event():String
|
var event:SongEvent = Type.createInstance(eventCls, ["UNKNOWN"]);
|
||||||
{
|
|
||||||
return this.e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function set_event(value:String):String
|
if (event != null)
|
||||||
{
|
{
|
||||||
return this.e = value;
|
trace(' Loaded built-in song event: (${event.id})');
|
||||||
}
|
eventCache.set(event.id, event);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace(' Failed to load built-in song event: ${Type.getClassName(eventCls)}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var value(get, set):Dynamic;
|
static function registerScriptedEvents()
|
||||||
|
{
|
||||||
|
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||||
|
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
public function get_value():Dynamic
|
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||||
{
|
for (eventCls in scriptedEventClassNames)
|
||||||
return this.v;
|
{
|
||||||
}
|
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||||
|
|
||||||
public function set_value(value:Dynamic):Dynamic
|
if (event != null)
|
||||||
{
|
{
|
||||||
return this.v = value;
|
trace(' Loaded scripted song event: ${event.id}');
|
||||||
}
|
eventCache.set(event.id, event);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace(' Failed to instantiate scripted song event class: ${eventCls}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public inline function getBool():Bool
|
public static function listEventIds():Array<String>
|
||||||
{
|
{
|
||||||
return cast this.v;
|
return eventCache.keys().array();
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getInt():Int
|
public static function listEvents():Array<SongEvent>
|
||||||
{
|
{
|
||||||
return cast this.v;
|
return eventCache.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getFloat():Float
|
public static function getEvent(id:String):SongEvent
|
||||||
{
|
{
|
||||||
return cast this.v;
|
return eventCache.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getString():String
|
public static function getEventSchema(id:String):SongEventSchema
|
||||||
{
|
{
|
||||||
return cast this.v;
|
var event:SongEvent = getEvent(id);
|
||||||
}
|
if (event == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
public inline function getArray():Array<Dynamic>
|
return event.getEventSchema();
|
||||||
{
|
}
|
||||||
return cast this.v;
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline function getMap():DynamicAccess<Dynamic>
|
static function clearEventCache()
|
||||||
{
|
{
|
||||||
return cast this.v;
|
eventCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getBoolArray():Array<Bool>
|
public static function handleEvent(data:SongEventData):Void
|
||||||
{
|
{
|
||||||
return cast this.v;
|
var eventType:String = data.event;
|
||||||
}
|
var eventHandler:SongEvent = eventCache.get(eventType);
|
||||||
|
|
||||||
|
if (eventHandler != null)
|
||||||
|
{
|
||||||
|
eventHandler.handleEvent(data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('WARNING: No event handler for event with id: ${eventType}');
|
||||||
|
}
|
||||||
|
|
||||||
|
data.activated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function handleEvents(events:Array<SongEventData>):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of song events and the current timestamp,
|
||||||
|
* return a list of events that should be handled.
|
||||||
|
*/
|
||||||
|
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return events.filter(function(event:SongEventData):Bool
|
||||||
|
{
|
||||||
|
// If the event is already activated, don't activate it again.
|
||||||
|
if (event.activated)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the event is in the future, don't activate it.
|
||||||
|
if (event.time > currentTime)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset activation of all the provided events.
|
||||||
|
*/
|
||||||
|
public static function resetEvents(events:Array<SongEventData>):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
event.activated = false;
|
||||||
|
// TODO: Add an onReset() method to SongEvent?
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef SongEventCallback = SongEvent->Void;
|
enum abstract SongEventFieldType(String) from String to String
|
||||||
|
|
||||||
class SongEventHandler
|
|
||||||
{
|
{
|
||||||
private static final eventCallbacks:Map<String, SongEventCallback> = new Map<String, SongEventCallback>();
|
/**
|
||||||
|
* The STRING type will display as a text field.
|
||||||
|
*/
|
||||||
|
var STRING = "string";
|
||||||
|
|
||||||
public static function registerCallback(event:String, callback:SongEventCallback):Void
|
/**
|
||||||
{
|
* The INTEGER type will display as a text field that only accepts numbers.
|
||||||
eventCallbacks.set(event, callback);
|
*/
|
||||||
}
|
var INTEGER = "integer";
|
||||||
|
|
||||||
public static function unregisterCallback(event:String):Void
|
/**
|
||||||
{
|
* The FLOAT type will display as a text field that only accepts numbers.
|
||||||
eventCallbacks.remove(event);
|
*/
|
||||||
}
|
var FLOAT = "float";
|
||||||
|
|
||||||
public static function clearCallbacks():Void
|
/**
|
||||||
{
|
* The BOOL type will display as a checkbox.
|
||||||
eventCallbacks.clear();
|
*/
|
||||||
}
|
var BOOL = "bool";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register each of the event callbacks provided by the base game.
|
* The ENUM type will display as a dropdown.
|
||||||
*/
|
* Make sure to specify the `keys` field in the schema.
|
||||||
public static function registerBaseEventCallbacks():Void
|
*/
|
||||||
{
|
var ENUM = "enum";
|
||||||
// TODO: Add a system for mods to easily add their own event callbacks.
|
|
||||||
// Should be easy as creating character or stage scripts.
|
|
||||||
registerCallback('FocusCamera', VanillaEventCallbacks.focusCamera);
|
|
||||||
registerCallback('PlayAnimation', VanillaEventCallbacks.playAnimation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a list of song events and the current timestamp,
|
|
||||||
* return a list of events that should be activated.
|
|
||||||
*/
|
|
||||||
public static function queryEvents(events:Array<SongEvent>, currentTime:Float):Array<SongEvent>
|
|
||||||
{
|
|
||||||
return events.filter(function(event:SongEvent):Bool
|
|
||||||
{
|
|
||||||
// If the event is already activated, don't activate it again.
|
|
||||||
if (event.a)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// If the event is in the future, don't activate it.
|
|
||||||
if (event.time > currentTime)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function activateEvents(events:Array<SongEvent>):Void
|
|
||||||
{
|
|
||||||
for (event in events)
|
|
||||||
{
|
|
||||||
activateEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function activateEvent(event:SongEvent):Void
|
|
||||||
{
|
|
||||||
if (event.a)
|
|
||||||
{
|
|
||||||
trace('Event already activated: ' + event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent the event from being activated again.
|
|
||||||
event.a = true;
|
|
||||||
|
|
||||||
// Perform the action.
|
|
||||||
if (eventCallbacks.exists(event.event))
|
|
||||||
{
|
|
||||||
eventCallbacks.get(event.event)(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function resetEvents(events:Array<SongEvent>):Void
|
|
||||||
{
|
|
||||||
for (event in events)
|
|
||||||
{
|
|
||||||
resetEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function resetEvent(event:SongEvent):Void
|
|
||||||
{
|
|
||||||
// TODO: Add a system for mods to easily add their reset callbacks.
|
|
||||||
event.a = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class VanillaEventCallbacks
|
typedef SongEventSchemaField =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Event Name: "FocusCamera"
|
* The name of the property as it should be saved in the event data.
|
||||||
* Event Value: Int
|
*/
|
||||||
* 0: Focus on the player.
|
name:String,
|
||||||
* 1: Focus on the opponent.
|
|
||||||
* 2: Focus on the girlfriend.
|
|
||||||
*/
|
|
||||||
public static function focusCamera(event:SongEvent):Void
|
|
||||||
{
|
|
||||||
// Does nothing if there is no PlayState camera or stage.
|
|
||||||
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (event.getInt())
|
/**
|
||||||
{
|
* The title of the field to display in the UI.
|
||||||
case 0: // Boyfriend
|
*/
|
||||||
// Focus the camera on the player.
|
title:String,
|
||||||
trace('[EVENT] Focusing camera on player.');
|
|
||||||
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x,
|
|
||||||
PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y);
|
|
||||||
case 1: // Dad
|
|
||||||
// Focus the camera on the dad.
|
|
||||||
trace('[EVENT] Focusing camera on dad.');
|
|
||||||
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getDad().cameraFocusPoint.x,
|
|
||||||
PlayState.instance.currentStage.getDad().cameraFocusPoint.y);
|
|
||||||
case 2: // Girlfriend
|
|
||||||
// Focus the camera on the girlfriend.
|
|
||||||
trace('[EVENT] Focusing camera on girlfriend.');
|
|
||||||
PlayState.instance.cameraFollowPoint.setPosition(PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x,
|
|
||||||
PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y);
|
|
||||||
default:
|
|
||||||
trace('[EVENT] Unknown camera focus: ' + event.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event Name: "playAnimation"
|
* The type of the field.
|
||||||
* Event Value: Object
|
*/
|
||||||
* {
|
type:SongEventFieldType,
|
||||||
* target: String, // "player", "dad", "girlfriend", or <stage prop id>
|
|
||||||
* animation: String,
|
|
||||||
* force: Bool // optional
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
public static function playAnimation(event:SongEvent):Void
|
|
||||||
{
|
|
||||||
// Does nothing if there is no PlayState camera or stage.
|
|
||||||
if (PlayState.instance == null || PlayState.instance.currentStage == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data:Dynamic = event.value;
|
/**
|
||||||
|
* Used for ENUM values.
|
||||||
var targetName:String = Reflect.field(data, 'target');
|
* The key is the display name and the value is the actual value.
|
||||||
var anim:String = Reflect.field(data, 'anim');
|
*/
|
||||||
var force:Null<Bool> = Reflect.field(data, 'force');
|
?keys:Map<String, Dynamic>,
|
||||||
if (force == null)
|
/**
|
||||||
force = false;
|
* Used for INTEGER and FLOAT values.
|
||||||
|
* The minimum value that can be entered.
|
||||||
var target:FlxSprite = null;
|
*/
|
||||||
|
?min:Float,
|
||||||
switch (targetName)
|
/**
|
||||||
{
|
* Used for INTEGER and FLOAT values.
|
||||||
case 'boyfriend':
|
* The maximum value that can be entered.
|
||||||
trace('[EVENT] Playing animation $anim on boyfriend.');
|
*/
|
||||||
target = PlayState.instance.currentStage.getBoyfriend();
|
?max:Float,
|
||||||
case 'bf':
|
/**
|
||||||
trace('[EVENT] Playing animation $anim on boyfriend.');
|
* Used for INTEGER and FLOAT values.
|
||||||
target = PlayState.instance.currentStage.getBoyfriend();
|
* The step value that will be used when incrementing/decrementing the value.
|
||||||
case 'player':
|
*/
|
||||||
trace('[EVENT] Playing animation $anim on boyfriend.');
|
?step:Float,
|
||||||
target = PlayState.instance.currentStage.getBoyfriend();
|
/**
|
||||||
case 'dad':
|
* An optional default value for the field.
|
||||||
trace('[EVENT] Playing animation $anim on dad.');
|
*/
|
||||||
target = PlayState.instance.currentStage.getDad();
|
?defaultValue:Dynamic,
|
||||||
case 'opponent':
|
|
||||||
trace('[EVENT] Playing animation $anim on dad.');
|
|
||||||
target = PlayState.instance.currentStage.getDad();
|
|
||||||
case 'girlfriend':
|
|
||||||
trace('[EVENT] Playing animation $anim on girlfriend.');
|
|
||||||
target = PlayState.instance.currentStage.getGirlfriend();
|
|
||||||
case 'gf':
|
|
||||||
trace('[EVENT] Playing animation $anim on girlfriend.');
|
|
||||||
target = PlayState.instance.currentStage.getGirlfriend();
|
|
||||||
default:
|
|
||||||
target = PlayState.instance.currentStage.getNamedProp(targetName);
|
|
||||||
if (target == null)
|
|
||||||
trace('[EVENT] Unknown animation target: $targetName');
|
|
||||||
else
|
|
||||||
trace('[EVENT] Fetched animation target $targetName from stage.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target != null)
|
|
||||||
{
|
|
||||||
if (Std.isOfType(target, BaseCharacter))
|
|
||||||
{
|
|
||||||
var targetChar:BaseCharacter = cast target;
|
|
||||||
targetChar.playAnimation(anim, force, force);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
target.animation.play(anim, force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef SongEventSchema = Array<SongEventSchemaField>;
|
||||||
|
|
|
@ -22,239 +22,239 @@ import funkin.play.song.SongData.SongTimeFormat;
|
||||||
*/
|
*/
|
||||||
class Song // implements IPlayStateScriptedClass
|
class Song // implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
public final songId:String;
|
public final songId:String;
|
||||||
|
|
||||||
final _metadata:Array<SongMetadata>;
|
final _metadata:Array<SongMetadata>;
|
||||||
|
|
||||||
final variations:Array<String>;
|
final variations:Array<String>;
|
||||||
final difficulties:Map<String, SongDifficulty>;
|
final difficulties:Map<String, SongDifficulty>;
|
||||||
|
|
||||||
public function new(id:String)
|
public function new(id:String)
|
||||||
{
|
{
|
||||||
this.songId = id;
|
this.songId = id;
|
||||||
|
|
||||||
variations = [];
|
variations = [];
|
||||||
difficulties = new Map<String, SongDifficulty>();
|
difficulties = new Map<String, SongDifficulty>();
|
||||||
|
|
||||||
_metadata = SongDataParser.parseSongMetadata(songId);
|
_metadata = SongDataParser.parseSongMetadata(songId);
|
||||||
if (_metadata == null || _metadata.length == 0)
|
if (_metadata == null || _metadata.length == 0)
|
||||||
{
|
{
|
||||||
throw 'Could not find song data for songId: $songId';
|
throw 'Could not find song data for songId: $songId';
|
||||||
}
|
}
|
||||||
|
|
||||||
populateFromMetadata();
|
populateFromMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRawMetadata():Array<SongMetadata>
|
public function getRawMetadata():Array<SongMetadata>
|
||||||
{
|
{
|
||||||
return _metadata;
|
return _metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the song data from the provided metadata,
|
* Populate the song data from the provided metadata,
|
||||||
* including data from individual difficulties. Does not load chart data.
|
* including data from individual difficulties. Does not load chart data.
|
||||||
*/
|
*/
|
||||||
function populateFromMetadata():Void
|
function populateFromMetadata():Void
|
||||||
{
|
{
|
||||||
// Variations may have different artist, time format, generatedBy, etc.
|
// Variations may have different artist, time format, generatedBy, etc.
|
||||||
for (metadata in _metadata)
|
for (metadata in _metadata)
|
||||||
{
|
{
|
||||||
for (diffId in metadata.playData.difficulties)
|
for (diffId in metadata.playData.difficulties)
|
||||||
{
|
{
|
||||||
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
|
var difficulty:SongDifficulty = new SongDifficulty(this, diffId, metadata.variation);
|
||||||
|
|
||||||
variations.push(metadata.variation);
|
variations.push(metadata.variation);
|
||||||
|
|
||||||
difficulty.songName = metadata.songName;
|
difficulty.songName = metadata.songName;
|
||||||
difficulty.songArtist = metadata.artist;
|
difficulty.songArtist = metadata.artist;
|
||||||
difficulty.timeFormat = metadata.timeFormat;
|
difficulty.timeFormat = metadata.timeFormat;
|
||||||
difficulty.divisions = metadata.divisions;
|
difficulty.divisions = metadata.divisions;
|
||||||
difficulty.timeChanges = metadata.timeChanges;
|
difficulty.timeChanges = metadata.timeChanges;
|
||||||
difficulty.loop = metadata.loop;
|
difficulty.loop = metadata.loop;
|
||||||
difficulty.generatedBy = metadata.generatedBy;
|
difficulty.generatedBy = metadata.generatedBy;
|
||||||
|
|
||||||
difficulty.stage = metadata.playData.stage;
|
difficulty.stage = metadata.playData.stage;
|
||||||
// difficulty.noteSkin = metadata.playData.noteSkin;
|
// difficulty.noteSkin = metadata.playData.noteSkin;
|
||||||
|
|
||||||
difficulty.chars = new Map<String, SongPlayableChar>();
|
difficulty.chars = new Map<String, SongPlayableChar>();
|
||||||
for (charId in metadata.playData.playableChars.keys())
|
for (charId in metadata.playData.playableChars.keys())
|
||||||
{
|
{
|
||||||
var char = metadata.playData.playableChars.get(charId);
|
var char = metadata.playData.playableChars.get(charId);
|
||||||
|
|
||||||
difficulty.chars.set(charId, char);
|
difficulty.chars.set(charId, char);
|
||||||
}
|
}
|
||||||
|
|
||||||
difficulties.set(diffId, difficulty);
|
difficulties.set(diffId, difficulty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and cache the chart for all difficulties of this song.
|
* Parse and cache the chart for all difficulties of this song.
|
||||||
*/
|
*/
|
||||||
public function cacheCharts(?force:Bool = false):Void
|
public function cacheCharts(?force:Bool = false):Void
|
||||||
{
|
{
|
||||||
if (force)
|
if (force)
|
||||||
{
|
{
|
||||||
clearCharts();
|
clearCharts();
|
||||||
}
|
}
|
||||||
|
|
||||||
trace('Caching ${variations.length} chart files for song $songId');
|
trace('Caching ${variations.length} chart files for song $songId');
|
||||||
for (variation in variations)
|
for (variation in variations)
|
||||||
{
|
{
|
||||||
var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
|
var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation);
|
||||||
var chartNotes = chartData.notes;
|
var chartNotes = chartData.notes;
|
||||||
|
|
||||||
for (diffId in chartNotes.keys())
|
for (diffId in chartNotes.keys())
|
||||||
{
|
{
|
||||||
// Retrieve the cached difficulty data.
|
// Retrieve the cached difficulty data.
|
||||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||||
if (difficulty == null)
|
if (difficulty == null)
|
||||||
{
|
{
|
||||||
trace('Could not find difficulty $diffId for song $songId');
|
trace('Could not find difficulty $diffId for song $songId');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Add the chart data to the difficulty.
|
// Add the chart data to the difficulty.
|
||||||
difficulty.notes = chartData.notes.get(diffId);
|
difficulty.notes = chartData.notes.get(diffId);
|
||||||
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
difficulty.scrollSpeed = chartData.getScrollSpeed(diffId);
|
||||||
|
|
||||||
difficulty.events = chartData.events;
|
difficulty.events = chartData.events;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trace('Done caching charts.');
|
trace('Done caching charts.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||||
*/
|
*/
|
||||||
public inline function getDifficulty(?diffId:String):SongDifficulty
|
public inline function getDifficulty(?diffId:String):SongDifficulty
|
||||||
{
|
{
|
||||||
if (diffId == null)
|
if (diffId == null)
|
||||||
diffId = difficulties.keys().array()[0];
|
diffId = difficulties.keys().array()[0];
|
||||||
|
|
||||||
return difficulties.get(diffId);
|
return difficulties.get(diffId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purge the cached chart data for each difficulty of this song.
|
* Purge the cached chart data for each difficulty of this song.
|
||||||
*/
|
*/
|
||||||
public function clearCharts():Void
|
public function clearCharts():Void
|
||||||
{
|
{
|
||||||
for (diff in difficulties)
|
for (diff in difficulties)
|
||||||
{
|
{
|
||||||
diff.clearChart();
|
diff.clearChart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Song($songId)';
|
return 'Song($songId)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SongDifficulty
|
class SongDifficulty
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The parent song for this difficulty.
|
* The parent song for this difficulty.
|
||||||
*/
|
*/
|
||||||
public final song:Song;
|
public final song:Song;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The difficulty ID, such as `easy` or `hard`.
|
* The difficulty ID, such as `easy` or `hard`.
|
||||||
*/
|
*/
|
||||||
public final difficulty:String;
|
public final difficulty:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata file that contains this difficulty.
|
* The metadata file that contains this difficulty.
|
||||||
*/
|
*/
|
||||||
public final variation:String;
|
public final variation:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The note chart for this difficulty.
|
* The note chart for this difficulty.
|
||||||
*/
|
*/
|
||||||
public var notes:Array<SongNoteData>;
|
public var notes:Array<SongNoteData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The event chart for this difficulty.
|
* The event chart for this difficulty.
|
||||||
*/
|
*/
|
||||||
public var events:Array<SongEventData>;
|
public var events:Array<SongEventData>;
|
||||||
|
|
||||||
public var songName:String = SongValidator.DEFAULT_SONGNAME;
|
public var songName:String = SongValidator.DEFAULT_SONGNAME;
|
||||||
public var songArtist:String = SongValidator.DEFAULT_ARTIST;
|
public var songArtist:String = SongValidator.DEFAULT_ARTIST;
|
||||||
public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT;
|
public var timeFormat:SongTimeFormat = SongValidator.DEFAULT_TIMEFORMAT;
|
||||||
public var divisions:Int = SongValidator.DEFAULT_DIVISIONS;
|
public var divisions:Int = SongValidator.DEFAULT_DIVISIONS;
|
||||||
public var loop:Bool = SongValidator.DEFAULT_LOOP;
|
public var loop:Bool = SongValidator.DEFAULT_LOOP;
|
||||||
public var generatedBy:String = SongValidator.DEFAULT_GENERATEDBY;
|
public var generatedBy:String = SongValidator.DEFAULT_GENERATEDBY;
|
||||||
|
|
||||||
public var timeChanges:Array<SongTimeChange> = [];
|
public var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
|
||||||
public var stage:String = SongValidator.DEFAULT_STAGE;
|
public var stage:String = SongValidator.DEFAULT_STAGE;
|
||||||
public var chars:Map<String, SongPlayableChar> = null;
|
public var chars:Map<String, SongPlayableChar> = null;
|
||||||
|
|
||||||
public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED;
|
public var scrollSpeed:Float = SongValidator.DEFAULT_SCROLLSPEED;
|
||||||
|
|
||||||
public function new(song:Song, diffId:String, variation:String)
|
public function new(song:Song, diffId:String, variation:String)
|
||||||
{
|
{
|
||||||
this.song = song;
|
this.song = song;
|
||||||
this.difficulty = diffId;
|
this.difficulty = diffId;
|
||||||
this.variation = variation;
|
this.variation = variation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearChart():Void
|
public function clearChart():Void
|
||||||
{
|
{
|
||||||
notes = null;
|
notes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStartingBPM():Float
|
public function getStartingBPM():Float
|
||||||
{
|
{
|
||||||
if (timeChanges.length == 0)
|
if (timeChanges.length == 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return timeChanges[0].bpm;
|
return timeChanges[0].bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlayableChar(id:String):SongPlayableChar
|
public function getPlayableChar(id:String):SongPlayableChar
|
||||||
{
|
{
|
||||||
return chars.get(id);
|
return chars.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlayableChars():Array<String>
|
public function getPlayableChars():Array<String>
|
||||||
{
|
{
|
||||||
return chars.keys().array();
|
return chars.keys().array();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEvents():Array<SongEvent>
|
public function getEvents():Array<SongEventData>
|
||||||
{
|
{
|
||||||
return cast events;
|
return cast events;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function cacheInst()
|
public inline function cacheInst()
|
||||||
{
|
{
|
||||||
FlxG.sound.cache(Paths.inst(this.song.songId));
|
FlxG.sound.cache(Paths.inst(this.song.songId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function playInst(volume:Float = 1.0, looped:Bool = false)
|
public inline function playInst(volume:Float = 1.0, looped:Bool = false)
|
||||||
{
|
{
|
||||||
FlxG.sound.playMusic(Paths.inst(this.song.songId), volume, looped);
|
FlxG.sound.playMusic(Paths.inst(this.song.songId), volume, looped);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function cacheVocals()
|
public inline function cacheVocals()
|
||||||
{
|
{
|
||||||
FlxG.sound.cache(Paths.voices(this.song.songId));
|
FlxG.sound.cache(Paths.voices(this.song.songId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildVoiceList():Array<String>
|
public function buildVoiceList():Array<String>
|
||||||
{
|
{
|
||||||
// TODO: Implement.
|
// TODO: Implement.
|
||||||
|
|
||||||
return [""];
|
return [""];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildVocals(charId:String = "bf"):VoicesGroup
|
public function buildVocals(charId:String = "bf"):VoicesGroup
|
||||||
{
|
{
|
||||||
var result:VoicesGroup = VoicesGroup.build(this.song.songId, this.buildVoiceList());
|
var result:VoicesGroup = VoicesGroup.build(this.song.songId, this.buildVoiceList());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,6 +26,22 @@ class SongDataUtils
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of SongEventData objects, return a new array of SongEventData objects
|
||||||
|
* whose timestamps are shifted by the given amount.
|
||||||
|
* Does not mutate the original array.
|
||||||
|
*
|
||||||
|
* @param events The events to modify.
|
||||||
|
* @param offset The time difference to apply in milliseconds.
|
||||||
|
*/
|
||||||
|
public static function offsetSongEventData(events:Array<SongEventData>, offset:Int):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return events.map(function(event:SongEventData):SongEventData
|
||||||
|
{
|
||||||
|
return new SongEventData(event.time + offset, event.event, event.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -94,11 +110,21 @@ class SongDataUtils
|
||||||
*
|
*
|
||||||
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||||
*/
|
*/
|
||||||
public static function buildClipboard(notes:Array<SongNoteData>):Array<SongNoteData>
|
public static function buildNoteClipboard(notes:Array<SongNoteData>):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
|
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare an array of events to be used as the clipboard data.
|
||||||
|
*
|
||||||
|
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
||||||
|
*/
|
||||||
|
public static function buildEventClipboard(events:Array<SongEventData>):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return offsetSongEventData(sortEvents(events), -Std.int(events[0].time));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort an array of notes by strum time.
|
* Sort an array of notes by strum time.
|
||||||
*/
|
*/
|
||||||
|
@ -113,39 +139,55 @@ class SongDataUtils
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize an array of note data and write it to the clipboard.
|
* Sort an array of events by strum time.
|
||||||
*/
|
*/
|
||||||
public static function writeNotesToClipboard(notes:Array<SongNoteData>):Void
|
public static function sortEvents(events:Array<SongEventData>, ?desc:Bool = false):Array<SongEventData>
|
||||||
{
|
{
|
||||||
var notesString = SerializerUtil.toJSON(notes);
|
// TODO: Modifies the array in place. Is this okay?
|
||||||
|
events.sort(function(a:SongEventData, b:SongEventData):Int
|
||||||
|
{
|
||||||
|
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||||
|
});
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
ClipboardUtil.setClipboard(notesString);
|
/**
|
||||||
|
* Serialize note and event data and write it to the clipboard.
|
||||||
|
*/
|
||||||
|
public static function writeItemsToClipboard(data:SongClipboardItems):Void
|
||||||
|
{
|
||||||
|
var dataString = SerializerUtil.toJSON(data);
|
||||||
|
|
||||||
trace('Wrote ' + notes.length + ' notes to clipboard.');
|
ClipboardUtil.setClipboard(dataString);
|
||||||
|
|
||||||
trace(notesString);
|
trace('Wrote ' + data.notes.length + ' notes and ' + data.events.length + ' events to clipboard.');
|
||||||
|
|
||||||
|
trace(dataString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an array of note data from the clipboard and deserialize it.
|
* Read an array of note data from the clipboard and deserialize it.
|
||||||
*/
|
*/
|
||||||
public static function readNotesFromClipboard():Array<SongNoteData>
|
public static function readItemsFromClipboard():SongClipboardItems
|
||||||
{
|
{
|
||||||
var notesString = ClipboardUtil.getClipboard();
|
var notesString = ClipboardUtil.getClipboard();
|
||||||
|
|
||||||
trace('Read ' + notesString.length + ' characters from clipboard.');
|
trace('Read ${notesString.length} characters from clipboard.');
|
||||||
|
|
||||||
var notes:Array<SongNoteData> = notesString.parseJSON();
|
var data:SongClipboardItems = notesString.parseJSON();
|
||||||
|
|
||||||
if (notes == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
trace('Failed to parse notes from clipboard.');
|
trace('Failed to parse notes from clipboard.');
|
||||||
return [];
|
return {
|
||||||
|
notes: [],
|
||||||
|
events: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Parsed ' + notes.length + ' notes from clipboard.');
|
trace('Parsed ' + data.notes.length + ' notes and ' + data.events.length + ' from clipboard.');
|
||||||
return notes;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,6 +202,17 @@ class SongDataUtils
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a list of events to only include events that are within the given time range.
|
||||||
|
*/
|
||||||
|
public static function getEventsInTimeRange(events:Array<SongEventData>, start:Float, end:Float):Array<SongEventData>
|
||||||
|
{
|
||||||
|
return events.filter(function(event:SongEventData):Bool
|
||||||
|
{
|
||||||
|
return event.time >= start && event.time <= end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter a list of notes to only include notes whose data is within the given range.
|
* Filter a list of notes to only include notes whose data is within the given range.
|
||||||
*/
|
*/
|
||||||
|
@ -182,3 +235,9 @@ class SongDataUtils
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef SongClipboardItems =
|
||||||
|
{
|
||||||
|
notes:Array<SongNoteData>,
|
||||||
|
events:Array<SongEventData>
|
||||||
|
}
|
||||||
|
|
|
@ -6,74 +6,74 @@ import funkin.util.VersionUtil;
|
||||||
|
|
||||||
class SongMigrator
|
class SongMigrator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current latest version string for the song data format.
|
* The current latest version string for the song data format.
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the SongMigrator class.
|
* and adding migration to the SongMigrator class.
|
||||||
*/
|
*/
|
||||||
public static final CHART_VERSION:String = "2.0.0";
|
public static final CHART_VERSION:String = "2.0.0";
|
||||||
|
|
||||||
public static final CHART_VERSION_RULE:String = "2.0.x";
|
public static final CHART_VERSION_RULE:String = "2.0.x";
|
||||||
|
|
||||||
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
||||||
{
|
{
|
||||||
if (jsonData.version)
|
if (jsonData.version)
|
||||||
{
|
{
|
||||||
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.');
|
trace('Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.');
|
||||||
|
|
||||||
var songMetadata:SongMetadata = cast jsonData;
|
var songMetadata:SongMetadata = cast jsonData;
|
||||||
|
|
||||||
return songMetadata;
|
return songMetadata;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
||||||
switch (jsonData.version)
|
switch (jsonData.version)
|
||||||
{
|
{
|
||||||
// TODO: Add migration functions as cases here.
|
// TODO: Add migration functions as cases here.
|
||||||
default:
|
default:
|
||||||
// Unknown version.
|
// Unknown version.
|
||||||
trace('[SONGDATA] Song (${songId}) unknown metadata version: ${jsonData.version}');
|
trace('Song (${songId}) unknown metadata version: ${jsonData.version}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song metadata version is missing.');
|
trace('Song metadata version is missing.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
||||||
{
|
{
|
||||||
if (jsonData.version)
|
if (jsonData.version)
|
||||||
{
|
{
|
||||||
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.');
|
trace('Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.');
|
||||||
|
|
||||||
var songChartData:SongChartData = cast jsonData;
|
var songChartData:SongChartData = cast jsonData;
|
||||||
|
|
||||||
return songChartData;
|
return songChartData;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song (${songId}) chart version (${jsonData.version}) is outdated.');
|
trace('Song (${songId}) chart version (${jsonData.version}) is outdated.');
|
||||||
switch (jsonData.version)
|
switch (jsonData.version)
|
||||||
{
|
{
|
||||||
// TODO: Add migration functions as cases here.
|
// TODO: Add migration functions as cases here.
|
||||||
default:
|
default:
|
||||||
// Unknown version.
|
// Unknown version.
|
||||||
trace('[SONGDATA] Song (${songId}) unknown chart version: ${jsonData.version}');
|
trace('Song (${songId}) unknown chart version: ${jsonData.version}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[SONGDATA] Song chart version is missing.');
|
trace('Song chart version is missing.');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,393 +15,358 @@ typedef AnimationFinishedCallback = String->Void;
|
||||||
*/
|
*/
|
||||||
class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
class Bopper extends FlxSprite implements IPlayStateScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The bopper plays the dance animation once every `danceEvery` beats.
|
* The bopper plays the dance animation once every `danceEvery` beats.
|
||||||
* Set to 0 to disable idle animation.
|
* Set to 0 to disable idle animation.
|
||||||
*/
|
*/
|
||||||
public var danceEvery:Int = 1;
|
public var danceEvery:Int = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the bopper should dance left and right.
|
* Whether the bopper should dance left and right.
|
||||||
* - If true, alternate playing `danceLeft` and `danceRight`.
|
* - If true, alternate playing `danceLeft` and `danceRight`.
|
||||||
* - If false, play `idle` every time.
|
* - If false, play `idle` every time.
|
||||||
*
|
*
|
||||||
* You can manually set this value, or you can leave it as `null` to determine it automatically.
|
* You can manually set this value, or you can leave it as `null` to determine it automatically.
|
||||||
*/
|
*/
|
||||||
public var shouldAlternate:Null<Bool> = null;
|
public var shouldAlternate:Null<Bool> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offset the character's sprite by this much when playing each animation.
|
* Offset the character's sprite by this much when playing each animation.
|
||||||
*/
|
*/
|
||||||
public var animationOffsets:Map<String, Array<Float>> = new Map<String, Array<Float>>();
|
public var animationOffsets:Map<String, Array<Float>> = new Map<String, Array<Float>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a suffix to the `idle` animation (or `danceLeft` and `danceRight` animations)
|
* Add a suffix to the `idle` animation (or `danceLeft` and `danceRight` animations)
|
||||||
* that this bopper will play.
|
* that this bopper will play.
|
||||||
*/
|
*/
|
||||||
public var idleSuffix(default, set):String = "";
|
public var idleSuffix(default, set):String = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this bopper should bop every beat. By default it's true, but when used
|
* Whether this bopper should bop every beat. By default it's true, but when used
|
||||||
* for characters/players, it should be false so it doesn't cut off their animations!!!!!
|
* for characters/players, it should be false so it doesn't cut off their animations!!!!!
|
||||||
*/
|
*/
|
||||||
public var shouldBop:Bool = true;
|
public var shouldBop:Bool = true;
|
||||||
|
|
||||||
function set_idleSuffix(value:String):String
|
function set_idleSuffix(value:String):String
|
||||||
{
|
{
|
||||||
this.idleSuffix = value;
|
this.idleSuffix = value;
|
||||||
this.dance();
|
this.dance();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The offset of the character relative to the position specified by the stage.
|
* The offset of the character relative to the position specified by the stage.
|
||||||
*/
|
*/
|
||||||
public var globalOffsets(default, set):Array<Float> = [0, 0];
|
public var globalOffsets(default, set):Array<Float> = [0, 0];
|
||||||
|
|
||||||
function set_globalOffsets(value:Array<Float>)
|
function set_globalOffsets(value:Array<Float>)
|
||||||
{
|
{
|
||||||
if (globalOffsets == null)
|
if (globalOffsets == null)
|
||||||
globalOffsets = [0, 0];
|
globalOffsets = [0, 0];
|
||||||
if (globalOffsets == value)
|
if (globalOffsets == value)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
var xDiff = globalOffsets[0] - value[0];
|
var xDiff = globalOffsets[0] - value[0];
|
||||||
var yDiff = globalOffsets[1] - value[1];
|
var yDiff = globalOffsets[1] - value[1];
|
||||||
|
|
||||||
this.x += xDiff;
|
this.x += xDiff;
|
||||||
this.y += yDiff;
|
this.y += yDiff;
|
||||||
return animOffsets = value;
|
return animOffsets = value;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
private var animOffsets(default, set):Array<Float> = [0, 0];
|
||||||
private var animOffsets(default, set):Array<Float> = [0, 0];
|
|
||||||
|
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
||||||
public var originalPosition:FlxPoint = new FlxPoint(0, 0);
|
|
||||||
|
function set_animOffsets(value:Array<Float>)
|
||||||
function set_animOffsets(value:Array<Float>)
|
{
|
||||||
{
|
if (animOffsets == null)
|
||||||
if (animOffsets == null)
|
animOffsets = [0, 0];
|
||||||
animOffsets = [0, 0];
|
if (animOffsets == value)
|
||||||
if (animOffsets == value)
|
return value;
|
||||||
return value;
|
|
||||||
|
var xDiff = animOffsets[0] - value[0];
|
||||||
var xDiff = animOffsets[0] - value[0];
|
var yDiff = animOffsets[1] - value[1];
|
||||||
var yDiff = animOffsets[1] - value[1];
|
|
||||||
|
this.x += xDiff;
|
||||||
this.x += xDiff;
|
this.y += yDiff;
|
||||||
this.y += yDiff;
|
|
||||||
|
return animOffsets = value;
|
||||||
return animOffsets = value;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Whether to play `danceRight` next iteration.
|
||||||
* Whether to play `danceRight` next iteration.
|
* Only used when `shouldAlternate` is true.
|
||||||
* Only used when `shouldAlternate` is true.
|
*/
|
||||||
*/
|
var hasDanced:Bool = false;
|
||||||
var hasDanced:Bool = false;
|
|
||||||
|
public function new(danceEvery:Int = 1)
|
||||||
public function new(danceEvery:Int = 1)
|
{
|
||||||
{
|
super();
|
||||||
super();
|
this.danceEvery = danceEvery;
|
||||||
this.danceEvery = danceEvery;
|
|
||||||
|
this.animation.callback = this.onAnimationFrame;
|
||||||
this.animation.callback = this.onAnimationFrame;
|
this.animation.finishCallback = this.onAnimationFinished;
|
||||||
this.animation.finishCallback = this.onAnimationFinished;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Called when an animation finishes.
|
||||||
* Called when an animation finishes.
|
* @param name The name of the animation that just finished.
|
||||||
* @param name The name of the animation that just finished.
|
*/
|
||||||
*/
|
function onAnimationFinished(name:String)
|
||||||
function onAnimationFinished(name:String)
|
{
|
||||||
{
|
// TODO: Can we make a system of like, animation priority or something?
|
||||||
// TODO: Can we make a system of like, animation priority or something?
|
if (!canPlayOtherAnims)
|
||||||
if (!canPlayOtherAnims)
|
{
|
||||||
{
|
canPlayOtherAnims = true;
|
||||||
canPlayOtherAnims = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Called when the current animation's frame changes.
|
||||||
* Called when the current animation's frame changes.
|
* @param name The name of the current animation.
|
||||||
* @param name The name of the current animation.
|
* @param frameNumber The number of the current frame.
|
||||||
* @param frameNumber The number of the current frame.
|
* @param frameIndex The index of the current frame.
|
||||||
* @param frameIndex The index of the current frame.
|
*
|
||||||
*
|
* For example, if an animation was defined as having the indexes [3, 0, 1, 2],
|
||||||
* For example, if an animation was defined as having the indexes [3, 0, 1, 2],
|
* then the first callback would have frameNumber = 0 and frameIndex = 3.
|
||||||
* then the first callback would have frameNumber = 0 and frameIndex = 3.
|
*/
|
||||||
*/
|
function onAnimationFrame(name:String = "", frameNumber:Int = -1, frameIndex:Int = -1)
|
||||||
function onAnimationFrame(name:String = "", frameNumber:Int = -1, frameIndex:Int = -1)
|
{
|
||||||
{
|
// Do nothing by default.
|
||||||
// Do nothing by default.
|
// This can be overridden by, for example, scripted characters,
|
||||||
// This can be overridden by, for example, scripted characters,
|
// or by calling `animationFrame.add()`.
|
||||||
// or by calling `animationFrame.add()`.
|
|
||||||
|
// Try not to do anything expensive here, it runs many times a second.
|
||||||
// Try not to do anything expensive here, it runs many times a second.
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* If this Bopper was defined by the stage, return the prop to its original position.
|
||||||
* If this Bopper was defined by the stage, return the prop to its original position.
|
*/
|
||||||
*/
|
public function resetPosition()
|
||||||
public function resetPosition()
|
{
|
||||||
{
|
this.x = originalPosition.x + animOffsets[0];
|
||||||
this.x = originalPosition.x + animOffsets[0];
|
this.y = originalPosition.y + animOffsets[1];
|
||||||
this.y = originalPosition.y + animOffsets[1];
|
}
|
||||||
}
|
|
||||||
|
function update_shouldAlternate():Void
|
||||||
function update_shouldAlternate():Void
|
{
|
||||||
{
|
if (hasAnimation('danceLeft'))
|
||||||
if (hasAnimation('danceLeft'))
|
{
|
||||||
{
|
this.shouldAlternate = true;
|
||||||
this.shouldAlternate = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Called once every beat of the song.
|
||||||
* Called once every beat of the song.
|
*/
|
||||||
*/
|
public function onBeatHit(event:SongTimeScriptEvent):Void
|
||||||
public function onBeatHit(event:SongTimeScriptEvent):Void
|
{
|
||||||
{
|
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
||||||
if (danceEvery > 0 && event.beat % danceEvery == 0)
|
{
|
||||||
{
|
dance(shouldBop);
|
||||||
dance(shouldBop);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Called every `danceEvery` beats of the song.
|
||||||
* Called every `danceEvery` beats of the song.
|
*/
|
||||||
*/
|
public function dance(forceRestart:Bool = false):Void
|
||||||
public function dance(forceRestart:Bool = false):Void
|
{
|
||||||
{
|
if (this.animation == null)
|
||||||
if (this.animation == null)
|
{
|
||||||
{
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
if (shouldAlternate == null)
|
||||||
if (shouldAlternate == null)
|
{
|
||||||
{
|
update_shouldAlternate();
|
||||||
update_shouldAlternate();
|
}
|
||||||
}
|
|
||||||
|
if (shouldAlternate)
|
||||||
if (shouldAlternate)
|
{
|
||||||
{
|
if (hasDanced)
|
||||||
if (hasDanced)
|
{
|
||||||
{
|
playAnimation('danceRight$idleSuffix', forceRestart);
|
||||||
playAnimation('danceRight$idleSuffix', forceRestart);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
playAnimation('danceLeft$idleSuffix', forceRestart);
|
||||||
playAnimation('danceLeft$idleSuffix', forceRestart);
|
}
|
||||||
}
|
hasDanced = !hasDanced;
|
||||||
hasDanced = !hasDanced;
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
playAnimation('idle$idleSuffix', forceRestart);
|
||||||
playAnimation('idle$idleSuffix', forceRestart);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public function hasAnimation(id:String):Bool
|
||||||
public function hasAnimation(id:String):Bool
|
{
|
||||||
{
|
if (this.animation == null)
|
||||||
if (this.animation == null)
|
return false;
|
||||||
return false;
|
|
||||||
|
return this.animation.getByName(id) != null;
|
||||||
return this.animation.getByName(id) != null;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Ensure that a given animation exists before playing it.
|
||||||
* Ensure that a given animation exists before playing it.
|
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
|
||||||
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
|
* @param name
|
||||||
* @param name
|
*/
|
||||||
*/
|
function correctAnimationName(name:String)
|
||||||
function correctAnimationName(name:String)
|
{
|
||||||
{
|
// If the animation exists, we're good.
|
||||||
// If the animation exists, we're good.
|
if (hasAnimation(name))
|
||||||
if (hasAnimation(name))
|
return name;
|
||||||
return name;
|
|
||||||
|
trace('[BOPPER] Animation "$name" does not exist!');
|
||||||
trace('[BOPPER] Animation "$name" does not exist!');
|
|
||||||
|
// Attempt to strip a `-alt` suffix, if it exists.
|
||||||
// Attempt to strip a `-alt` suffix, if it exists.
|
if (name.lastIndexOf('-') != -1)
|
||||||
if (name.lastIndexOf('-') != -1)
|
{
|
||||||
{
|
var correctName = name.substring(0, name.lastIndexOf('-'));
|
||||||
var correctName = name.substring(0, name.lastIndexOf('-'));
|
trace('[BOPPER] Attempting to fallback to "$correctName"');
|
||||||
trace('[BOPPER] Attempting to fallback to "$correctName"');
|
return correctAnimationName(correctName);
|
||||||
return correctAnimationName(correctName);
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
if (name != 'idle')
|
||||||
if (name != 'idle')
|
{
|
||||||
{
|
trace('[BOPPER] Attempting to fallback to "idle"');
|
||||||
trace('[BOPPER] Attempting to fallback to "idle"');
|
return correctAnimationName('idle');
|
||||||
return correctAnimationName('idle');
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
trace('[BOPPER] Failing animation playback.');
|
||||||
trace('[BOPPER] Failing animation playback.');
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public var canPlayOtherAnims:Bool = true;
|
||||||
public var canPlayOtherAnims:Bool = true;
|
|
||||||
|
/**
|
||||||
/**
|
* @param name The name of the animation to play.
|
||||||
* @param name The name of the animation to play.
|
* @param restart Whether to restart the animation if it is already playing.
|
||||||
* @param restart Whether to restart the animation if it is already playing.
|
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
||||||
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
*/
|
||||||
*/
|
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
||||||
public function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false):Void
|
{
|
||||||
{
|
if (!canPlayOtherAnims && !ignoreOther)
|
||||||
if (!canPlayOtherAnims && !ignoreOther)
|
return;
|
||||||
return;
|
|
||||||
|
var correctName = correctAnimationName(name);
|
||||||
var correctName = correctAnimationName(name);
|
if (correctName == null)
|
||||||
if (correctName == null)
|
return;
|
||||||
return;
|
|
||||||
|
this.animation.play(correctName, restart, false, 0);
|
||||||
this.animation.play(correctName, restart, false, 0);
|
|
||||||
|
if (ignoreOther)
|
||||||
if (ignoreOther)
|
{
|
||||||
{
|
canPlayOtherAnims = false;
|
||||||
canPlayOtherAnims = false;
|
}
|
||||||
}
|
|
||||||
|
applyAnimationOffsets(correctName);
|
||||||
applyAnimationOffsets(correctName);
|
}
|
||||||
}
|
|
||||||
|
var forceAnimationTimer:FlxTimer = new FlxTimer();
|
||||||
var forceAnimationTimer:FlxTimer = new FlxTimer();
|
|
||||||
|
/**
|
||||||
/**
|
* @param name The animation to play.
|
||||||
* @param name The animation to play.
|
* @param duration The duration in which other (non-forced) animations will be skipped, in seconds (NOT MILLISECONDS).
|
||||||
* @param duration The duration in which other (non-forced) animations will be skipped, in seconds (NOT MILLISECONDS).
|
*/
|
||||||
*/
|
public function forceAnimationForDuration(name:String, duration:Float):Void
|
||||||
public function forceAnimationForDuration(name:String, duration:Float):Void
|
{
|
||||||
{
|
// TODO: Might be nice to rework this function, maybe have a numbered priority system?
|
||||||
// TODO: Might be nice to rework this function, maybe have a numbered priority system?
|
|
||||||
|
if (this.animation == null)
|
||||||
if (this.animation == null)
|
return;
|
||||||
return;
|
|
||||||
|
var correctName = correctAnimationName(name);
|
||||||
var correctName = correctAnimationName(name);
|
if (correctName == null)
|
||||||
if (correctName == null)
|
return;
|
||||||
return;
|
|
||||||
|
this.animation.play(correctName, false, false);
|
||||||
this.animation.play(correctName, false, false);
|
applyAnimationOffsets(correctName);
|
||||||
applyAnimationOffsets(correctName);
|
|
||||||
|
canPlayOtherAnims = false;
|
||||||
canPlayOtherAnims = false;
|
forceAnimationTimer.start(duration, (timer) ->
|
||||||
forceAnimationTimer.start(duration, (timer) ->
|
{
|
||||||
{
|
canPlayOtherAnims = true;
|
||||||
canPlayOtherAnims = true;
|
}, 1);
|
||||||
}, 1);
|
}
|
||||||
}
|
|
||||||
|
function applyAnimationOffsets(name:String)
|
||||||
function applyAnimationOffsets(name:String)
|
{
|
||||||
{
|
var offsets = animationOffsets.get(name);
|
||||||
var offsets = animationOffsets.get(name);
|
if (offsets != null)
|
||||||
if (offsets != null)
|
{
|
||||||
{
|
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
|
||||||
this.animOffsets = [offsets[0] + globalOffsets[0], offsets[1] + globalOffsets[1]];
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
this.animOffsets = globalOffsets;
|
||||||
this.animOffsets = globalOffsets;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public function isAnimationFinished():Bool
|
||||||
public function isAnimationFinished():Bool
|
{
|
||||||
{
|
return this.animation.finished;
|
||||||
return this.animation.finished;
|
}
|
||||||
}
|
|
||||||
|
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
|
||||||
public function setAnimationOffsets(name:String, xOffset:Float, yOffset:Float):Void
|
{
|
||||||
{
|
animationOffsets.set(name, [xOffset, yOffset]);
|
||||||
animationOffsets.set(name, [xOffset, yOffset]);
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Returns the name of the animation that is currently playing.
|
||||||
* Returns the name of the animation that is currently playing.
|
* If no animation is playing (usually this means the character is BROKEN!),
|
||||||
* If no animation is playing (usually this means the character is BROKEN!),
|
* returns an empty string to prevent NPEs.
|
||||||
* returns an empty string to prevent NPEs.
|
*/
|
||||||
*/
|
public function getCurrentAnimation():String
|
||||||
public function getCurrentAnimation():String
|
{
|
||||||
{
|
if (this.animation == null || this.animation.curAnim == null)
|
||||||
if (this.animation == null || this.animation.curAnim == null)
|
return "";
|
||||||
return "";
|
return this.animation.curAnim.name;
|
||||||
return this.animation.curAnim.name;
|
}
|
||||||
}
|
|
||||||
|
public function onScriptEvent(event:ScriptEvent) {}
|
||||||
public function onScriptEvent(event:ScriptEvent)
|
|
||||||
{
|
public function onCreate(event:ScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onDestroy(event:ScriptEvent) {}
|
||||||
public function onCreate(event:ScriptEvent)
|
|
||||||
{
|
public function onUpdate(event:UpdateScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onPause(event:PauseScriptEvent) {}
|
||||||
public function onDestroy(event:ScriptEvent)
|
|
||||||
{
|
public function onResume(event:ScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onSongStart(event:ScriptEvent) {}
|
||||||
public function onUpdate(event:UpdateScriptEvent)
|
|
||||||
{
|
public function onSongEnd(event:ScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onGameOver(event:ScriptEvent) {}
|
||||||
public function onPause(event:PauseScriptEvent)
|
|
||||||
{
|
public function onNoteHit(event:NoteScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
public function onResume(event:ScriptEvent)
|
|
||||||
{
|
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||||
public function onSongStart(event:ScriptEvent)
|
|
||||||
{
|
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||||
public function onSongEnd(event:ScriptEvent)
|
|
||||||
{
|
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||||
public function onGameOver(event:ScriptEvent)
|
|
||||||
{
|
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
||||||
}
|
|
||||||
|
public function onSongRetry(event:ScriptEvent) {}
|
||||||
public function onNoteHit(event:NoteScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onStepHit(event:SongTimeScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCountdownStart(event:CountdownScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCountdownStep(event:CountdownScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onSongLoaded(event:SongLoadScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onSongRetry(event:ScriptEvent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,507 +13,507 @@ import openfl.Assets;
|
||||||
*/
|
*/
|
||||||
class StageDataParser
|
class StageDataParser
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
* 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 STAGE_DATA_VERSION:String = "1.0.0";
|
public static final STAGE_DATA_VERSION:String = "1.0.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version rule check for the stage data format.
|
* The current version rule check for the stage data format.
|
||||||
*/
|
*/
|
||||||
public static final STAGE_DATA_VERSION_RULE:String = "1.0.x";
|
public static final STAGE_DATA_VERSION_RULE:String = "1.0.x";
|
||||||
|
|
||||||
static final stageCache:Map<String, Stage> = new Map<String, Stage>();
|
static final stageCache:Map<String, Stage> = new Map<String, Stage>();
|
||||||
|
|
||||||
static final DEFAULT_STAGE_ID = 'UNKNOWN';
|
static final DEFAULT_STAGE_ID = 'UNKNOWN';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and preloads the game's stage data and scripts when the game starts.
|
* Parses and preloads the game's stage data and scripts when the game starts.
|
||||||
*
|
*
|
||||||
* If you want to force stages to be reloaded, you can just call this function again.
|
* If you want to force stages to be reloaded, you can just call this function again.
|
||||||
*/
|
*/
|
||||||
public static function loadStageCache():Void
|
public static function loadStageCache():Void
|
||||||
{
|
{
|
||||||
// Clear any stages that are cached if there were any.
|
// Clear any stages that are cached if there were any.
|
||||||
clearStageCache();
|
clearStageCache();
|
||||||
trace("[STAGEDATA] Loading stage cache...");
|
trace("Loading stage cache...");
|
||||||
|
|
||||||
//
|
//
|
||||||
// SCRIPTED STAGES
|
// SCRIPTED STAGES
|
||||||
//
|
//
|
||||||
var scriptedStageClassNames:Array<String> = ScriptedStage.listScriptClasses();
|
var scriptedStageClassNames:Array<String> = ScriptedStage.listScriptClasses();
|
||||||
trace(' Instantiating ${scriptedStageClassNames.length} scripted stages...');
|
trace(' Instantiating ${scriptedStageClassNames.length} scripted stages...');
|
||||||
for (stageCls in scriptedStageClassNames)
|
for (stageCls in scriptedStageClassNames)
|
||||||
{
|
{
|
||||||
var stage:Stage = ScriptedStage.init(stageCls, DEFAULT_STAGE_ID);
|
var stage:Stage = ScriptedStage.init(stageCls, DEFAULT_STAGE_ID);
|
||||||
if (stage != null)
|
if (stage != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded scripted stage: ${stage.stageName}');
|
trace(' Loaded scripted stage: ${stage.stageName}');
|
||||||
// Disable the rendering logic for stage until it's loaded.
|
// Disable the rendering logic for stage until it's loaded.
|
||||||
// Note that kill() =/= destroy()
|
// Note that kill() =/= destroy()
|
||||||
stage.kill();
|
stage.kill();
|
||||||
|
|
||||||
// Then store it.
|
// Then store it.
|
||||||
stageCache.set(stage.stageId, stage);
|
stageCache.set(stage.stageId, stage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace(' Failed to instantiate scripted stage class: ${stageCls}');
|
trace(' Failed to instantiate scripted stage class: ${stageCls}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// UNSCRIPTED STAGES
|
// UNSCRIPTED STAGES
|
||||||
//
|
//
|
||||||
var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
|
var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
|
||||||
var unscriptedStageIds:Array<String> = stageIdList.filter(function(stageId:String):Bool
|
var unscriptedStageIds:Array<String> = stageIdList.filter(function(stageId:String):Bool
|
||||||
{
|
{
|
||||||
return !stageCache.exists(stageId);
|
return !stageCache.exists(stageId);
|
||||||
});
|
});
|
||||||
trace(' Instantiating ${unscriptedStageIds.length} non-scripted stages...');
|
trace(' Instantiating ${unscriptedStageIds.length} non-scripted stages...');
|
||||||
for (stageId in unscriptedStageIds)
|
for (stageId in unscriptedStageIds)
|
||||||
{
|
{
|
||||||
var stage:Stage;
|
var stage:Stage;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
stage = new Stage(stageId);
|
stage = new Stage(stageId);
|
||||||
if (stage != null)
|
if (stage != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded stage data: ${stage.stageName}');
|
trace(' Loaded stage data: ${stage.stageName}');
|
||||||
stageCache.set(stageId, stage);
|
stageCache.set(stageId, stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
trace(' An error occurred while loading stage data: ${stageId}');
|
trace(' An error occurred while loading stage data: ${stageId}');
|
||||||
// Assume error was already logged.
|
// Assume error was already logged.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace(' Successfully loaded ${Lambda.count(stageCache)} stages.');
|
trace(' Successfully loaded ${Lambda.count(stageCache)} stages.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchStage(stageId:String):Null<Stage>
|
public static function fetchStage(stageId:String):Null<Stage>
|
||||||
{
|
{
|
||||||
if (stageCache.exists(stageId))
|
if (stageCache.exists(stageId))
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] Successfully fetch stage: ${stageId}');
|
trace('Successfully fetch stage: ${stageId}');
|
||||||
var stage:Stage = stageCache.get(stageId);
|
var stage:Stage = stageCache.get(stageId);
|
||||||
stage.revive();
|
stage.revive();
|
||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] Failed to fetch stage, not found in cache: ${stageId}');
|
trace('Failed to fetch stage, not found in cache: ${stageId}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function clearStageCache():Void
|
static function clearStageCache():Void
|
||||||
{
|
{
|
||||||
if (stageCache != null)
|
if (stageCache != null)
|
||||||
{
|
{
|
||||||
for (stage in stageCache)
|
for (stage in stageCache)
|
||||||
{
|
{
|
||||||
stage.destroy();
|
stage.destroy();
|
||||||
}
|
}
|
||||||
stageCache.clear();
|
stageCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a stage's JSON file, parse its data, and return it.
|
* Load a stage's JSON file, parse its data, and return it.
|
||||||
*
|
*
|
||||||
* @param stageId The stage to load.
|
* @param stageId The stage to load.
|
||||||
* @return The stage data, or null if validation failed.
|
* @return The stage data, or null if validation failed.
|
||||||
*/
|
*/
|
||||||
public static function parseStageData(stageId:String):Null<StageData>
|
public static function parseStageData(stageId:String):Null<StageData>
|
||||||
{
|
{
|
||||||
var rawJson:String = loadStageFile(stageId);
|
var rawJson:String = loadStageFile(stageId);
|
||||||
|
|
||||||
var stageData:StageData = migrateStageData(rawJson, stageId);
|
var stageData:StageData = migrateStageData(rawJson, stageId);
|
||||||
|
|
||||||
return validateStageData(stageId, stageData);
|
return validateStageData(stageId, stageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function listStageIds():Array<String>
|
public static function listStageIds():Array<String>
|
||||||
{
|
{
|
||||||
return stageCache.keys().array();
|
return stageCache.keys().array();
|
||||||
}
|
}
|
||||||
|
|
||||||
static function loadStageFile(stagePath:String):String
|
static function loadStageFile(stagePath:String):String
|
||||||
{
|
{
|
||||||
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
var stageFilePath:String = Paths.json('stages/${stagePath}');
|
||||||
var rawJson = Assets.getText(stageFilePath).trim();
|
var rawJson = Assets.getText(stageFilePath).trim();
|
||||||
|
|
||||||
while (!rawJson.endsWith("}"))
|
while (!rawJson.endsWith("}"))
|
||||||
{
|
{
|
||||||
rawJson = rawJson.substr(0, rawJson.length - 1);
|
rawJson = rawJson.substr(0, rawJson.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawJson;
|
return rawJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function migrateStageData(rawJson:String, stageId:String)
|
static function migrateStageData(rawJson:String, stageId:String)
|
||||||
{
|
{
|
||||||
// If you update the stage data format in a breaking way,
|
// If you update the stage data format in a breaking way,
|
||||||
// handle migration here by checking the `version` value.
|
// handle migration here by checking the `version` value.
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stageData:StageData = cast Json.parse(rawJson);
|
var stageData:StageData = cast Json.parse(rawJson);
|
||||||
return stageData;
|
return stageData;
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
trace(' Error parsing data for stage: ${stageId}');
|
trace(' Error parsing data for stage: ${stageId}');
|
||||||
trace(' ${e}');
|
trace(' ${e}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final DEFAULT_ANIMTYPE:String = "sparrow";
|
static final DEFAULT_ANIMTYPE:String = "sparrow";
|
||||||
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
static final DEFAULT_CAMERAZOOM:Float = 1.0;
|
||||||
static final DEFAULT_DANCEEVERY:Int = 0;
|
static final DEFAULT_DANCEEVERY:Int = 0;
|
||||||
static final DEFAULT_ISPIXEL:Bool = false;
|
static final DEFAULT_ISPIXEL:Bool = false;
|
||||||
static final DEFAULT_NAME:String = "Untitled Stage";
|
static final DEFAULT_NAME:String = "Untitled Stage";
|
||||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_CAMERA_OFFSETS_BF:Array<Float> = [-100, -100];
|
static final DEFAULT_CAMERA_OFFSETS_BF:Array<Float> = [-100, -100];
|
||||||
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
static final DEFAULT_CAMERA_OFFSETS_DAD:Array<Float> = [150, -100];
|
||||||
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
static final DEFAULT_POSITION:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_SCALE:Float = 1.0;
|
static final DEFAULT_SCALE:Float = 1.0;
|
||||||
static final DEFAULT_ALPHA:Float = 1.0;
|
static final DEFAULT_ALPHA:Float = 1.0;
|
||||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_ZINDEX:Int = 0;
|
static final DEFAULT_ZINDEX:Int = 0;
|
||||||
|
|
||||||
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
|
static final DEFAULT_CHARACTER_DATA:StageDataCharacter = {
|
||||||
zIndex: DEFAULT_ZINDEX,
|
zIndex: DEFAULT_ZINDEX,
|
||||||
position: DEFAULT_POSITION,
|
position: DEFAULT_POSITION,
|
||||||
cameraOffsets: DEFAULT_OFFSETS,
|
cameraOffsets: DEFAULT_OFFSETS,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set unspecified parameters to their defaults.
|
* Set unspecified parameters to their defaults.
|
||||||
* If the parameter is mandatory, print an error message.
|
* If the parameter is mandatory, print an error message.
|
||||||
* @param id
|
* @param id
|
||||||
* @param input
|
* @param input
|
||||||
* @return The validated stage data
|
* @return The validated stage data
|
||||||
*/
|
*/
|
||||||
static function validateStageData(id:String, input:StageData):Null<StageData>
|
static function validateStageData(id:String, input:StageData):Null<StageData>
|
||||||
{
|
{
|
||||||
if (input == null)
|
if (input == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not parse stage data for "${id}".');
|
trace('ERROR: Could not parse stage data for "${id}".');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.version == null)
|
if (input.version == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing version');
|
trace('ERROR: Could not load stage data for "$id": missing version');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VersionUtil.validateVersion(input.version, STAGE_DATA_VERSION_RULE))
|
if (!VersionUtil.validateVersion(input.version, STAGE_DATA_VERSION_RULE))
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})');
|
trace('ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.name == null)
|
if (input.name == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] WARN: Stage data for "$id" missing name');
|
trace('WARN: Stage data for "$id" missing name');
|
||||||
input.name = DEFAULT_NAME;
|
input.name = DEFAULT_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.cameraZoom == null)
|
if (input.cameraZoom == null)
|
||||||
{
|
{
|
||||||
input.cameraZoom = DEFAULT_CAMERAZOOM;
|
input.cameraZoom = DEFAULT_CAMERAZOOM;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.props == null)
|
if (input.props == null)
|
||||||
{
|
{
|
||||||
input.props = [];
|
input.props = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (inputProp in input.props)
|
for (inputProp in input.props)
|
||||||
{
|
{
|
||||||
// It's fine for inputProp.name to be null
|
// It's fine for inputProp.name to be null
|
||||||
|
|
||||||
if (inputProp.assetPath == null)
|
if (inputProp.assetPath == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"');
|
trace('ERROR: Could not load stage data for "$id": missing assetPath for prop "${inputProp.name}"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.position == null)
|
if (inputProp.position == null)
|
||||||
{
|
{
|
||||||
inputProp.position = DEFAULT_POSITION;
|
inputProp.position = DEFAULT_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.zIndex == null)
|
if (inputProp.zIndex == null)
|
||||||
{
|
{
|
||||||
inputProp.zIndex = DEFAULT_ZINDEX;
|
inputProp.zIndex = DEFAULT_ZINDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.isPixel == null)
|
if (inputProp.isPixel == null)
|
||||||
{
|
{
|
||||||
inputProp.isPixel = DEFAULT_ISPIXEL;
|
inputProp.isPixel = DEFAULT_ISPIXEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.danceEvery == null)
|
if (inputProp.danceEvery == null)
|
||||||
{
|
{
|
||||||
inputProp.danceEvery = DEFAULT_DANCEEVERY;
|
inputProp.danceEvery = DEFAULT_DANCEEVERY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.scale == null)
|
if (inputProp.scale == null)
|
||||||
{
|
{
|
||||||
inputProp.scale = DEFAULT_SCALE;
|
inputProp.scale = DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.animType == null)
|
if (inputProp.animType == null)
|
||||||
{
|
{
|
||||||
inputProp.animType = DEFAULT_ANIMTYPE;
|
inputProp.animType = DEFAULT_ANIMTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Std.isOfType(inputProp.scale, Float))
|
if (Std.isOfType(inputProp.scale, Float))
|
||||||
{
|
{
|
||||||
inputProp.scale = [inputProp.scale, inputProp.scale];
|
inputProp.scale = [inputProp.scale, inputProp.scale];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.scroll == null)
|
if (inputProp.scroll == null)
|
||||||
{
|
{
|
||||||
inputProp.scroll = DEFAULT_SCROLL;
|
inputProp.scroll = DEFAULT_SCROLL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.alpha == null)
|
if (inputProp.alpha == null)
|
||||||
{
|
{
|
||||||
inputProp.alpha = DEFAULT_ALPHA;
|
inputProp.alpha = DEFAULT_ALPHA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Std.isOfType(inputProp.scroll, Float))
|
if (Std.isOfType(inputProp.scroll, Float))
|
||||||
{
|
{
|
||||||
inputProp.scroll = [inputProp.scroll, inputProp.scroll];
|
inputProp.scroll = [inputProp.scroll, inputProp.scroll];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.animations == null)
|
if (inputProp.animations == null)
|
||||||
{
|
{
|
||||||
inputProp.animations = [];
|
inputProp.animations = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProp.animations.length == 0 && inputProp.startingAnimation != null)
|
if (inputProp.animations.length == 0 && inputProp.startingAnimation != null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"');
|
trace('ERROR: Could not load stage data for "$id": missing animations for prop "${inputProp.name}"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (inputAnimation in inputProp.animations)
|
for (inputAnimation in inputProp.animations)
|
||||||
{
|
{
|
||||||
if (inputAnimation.name == null)
|
if (inputAnimation.name == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"');
|
trace('ERROR: Could not load stage data for "$id": missing animation name for prop "${inputProp.name}"');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.frameRate == null)
|
if (inputAnimation.frameRate == null)
|
||||||
{
|
{
|
||||||
inputAnimation.frameRate = 24;
|
inputAnimation.frameRate = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.offsets == null)
|
if (inputAnimation.offsets == null)
|
||||||
{
|
{
|
||||||
inputAnimation.offsets = DEFAULT_OFFSETS;
|
inputAnimation.offsets = DEFAULT_OFFSETS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.looped == null)
|
if (inputAnimation.looped == null)
|
||||||
{
|
{
|
||||||
inputAnimation.looped = true;
|
inputAnimation.looped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.flipX == null)
|
if (inputAnimation.flipX == null)
|
||||||
{
|
{
|
||||||
inputAnimation.flipX = false;
|
inputAnimation.flipX = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAnimation.flipY == null)
|
if (inputAnimation.flipY == null)
|
||||||
{
|
{
|
||||||
inputAnimation.flipY = false;
|
inputAnimation.flipY = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.characters == null)
|
if (input.characters == null)
|
||||||
{
|
{
|
||||||
trace('[STAGEDATA] ERROR: Could not load stage data for "$id": missing characters');
|
trace('ERROR: Could not load stage data for "$id": missing characters');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.characters.bf == null)
|
if (input.characters.bf == null)
|
||||||
{
|
{
|
||||||
input.characters.bf = DEFAULT_CHARACTER_DATA;
|
input.characters.bf = DEFAULT_CHARACTER_DATA;
|
||||||
input.characters.bf.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
input.characters.bf.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
||||||
}
|
}
|
||||||
if (input.characters.dad == null)
|
if (input.characters.dad == null)
|
||||||
{
|
{
|
||||||
input.characters.dad = DEFAULT_CHARACTER_DATA;
|
input.characters.dad = DEFAULT_CHARACTER_DATA;
|
||||||
input.characters.dad.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
input.characters.dad.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
||||||
}
|
}
|
||||||
if (input.characters.gf == null)
|
if (input.characters.gf == null)
|
||||||
{
|
{
|
||||||
input.characters.gf = DEFAULT_CHARACTER_DATA;
|
input.characters.gf = DEFAULT_CHARACTER_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf])
|
for (inputCharacter in [input.characters.bf, input.characters.dad, input.characters.gf])
|
||||||
{
|
{
|
||||||
if (inputCharacter.zIndex == null)
|
if (inputCharacter.zIndex == null)
|
||||||
{
|
{
|
||||||
inputCharacter.zIndex = 0;
|
inputCharacter.zIndex = 0;
|
||||||
}
|
}
|
||||||
if (inputCharacter.position == null || inputCharacter.position.length != 2)
|
if (inputCharacter.position == null || inputCharacter.position.length != 2)
|
||||||
{
|
{
|
||||||
inputCharacter.position = [0, 0];
|
inputCharacter.position = [0, 0];
|
||||||
}
|
}
|
||||||
if (inputCharacter.cameraOffsets == null || inputCharacter.cameraOffsets.length != 2)
|
if (inputCharacter.cameraOffsets == null || inputCharacter.cameraOffsets.length != 2)
|
||||||
{
|
{
|
||||||
if (inputCharacter == input.characters.bf)
|
if (inputCharacter == input.characters.bf)
|
||||||
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_BF;
|
||||||
else if (inputCharacter == input.characters.dad)
|
else if (inputCharacter == input.characters.dad)
|
||||||
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
inputCharacter.cameraOffsets = DEFAULT_CAMERA_OFFSETS_DAD;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inputCharacter.cameraOffsets = [0, 0];
|
inputCharacter.cameraOffsets = [0, 0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All good!
|
// All good!
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef StageData =
|
typedef StageData =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The sematic version number of the stage data JSON format.
|
* The sematic version number of the stage data JSON format.
|
||||||
* Supports fancy comparisons like NPM does it's neat.
|
* Supports fancy comparisons like NPM does it's neat.
|
||||||
*/
|
*/
|
||||||
var version:String;
|
var version:String;
|
||||||
|
|
||||||
var name:String;
|
var name:String;
|
||||||
var cameraZoom:Null<Float>;
|
var cameraZoom:Null<Float>;
|
||||||
var props:Array<StageDataProp>;
|
var props:Array<StageDataProp>;
|
||||||
var characters:
|
var characters:
|
||||||
{
|
{
|
||||||
bf:StageDataCharacter,
|
bf:StageDataCharacter,
|
||||||
dad:StageDataCharacter,
|
dad:StageDataCharacter,
|
||||||
gf:StageDataCharacter,
|
gf:StageDataCharacter,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef StageDataProp =
|
typedef StageDataProp =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name of the prop for later lookup by scripts.
|
* The name of the prop for later lookup by scripts.
|
||||||
* Optional; if unspecified, the prop can't be referenced by scripts.
|
* Optional; if unspecified, the prop can't be referenced by scripts.
|
||||||
*/
|
*/
|
||||||
var name:String;
|
var name:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The asset used to display the prop.
|
* The asset used to display the prop.
|
||||||
*/
|
*/
|
||||||
var assetPath:String;
|
var assetPath:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position of the prop as an [x, y] array of two floats.
|
* The position of the prop as an [x, y] array of two floats.
|
||||||
*/
|
*/
|
||||||
var position:Array<Float>;
|
var position:Array<Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A number determining the stack order of the prop, relative to other props and the characters in the stage.
|
* A number determining the stack order of the prop, relative to other props and the characters in the stage.
|
||||||
* Props with lower numbers render below those with higher numbers.
|
* Props with lower numbers render below those with higher numbers.
|
||||||
* This is just like CSS, it isn't hard.
|
* This is just like CSS, it isn't hard.
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
var zIndex:Null<Int>;
|
var zIndex:Null<Int>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to true, anti-aliasing will be forcibly disabled on the sprite.
|
* If set to true, anti-aliasing will be forcibly disabled on the sprite.
|
||||||
* This prevents blurry images on pixel-art levels.
|
* This prevents blurry images on pixel-art levels.
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
var isPixel:Null<Bool>;
|
var isPixel:Null<Bool>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||||
* @default 1
|
* @default 1
|
||||||
*/
|
*/
|
||||||
var scale:OneOfTwo<Float, Array<Float>>;
|
var scale:OneOfTwo<Float, Array<Float>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The alpha of the prop, as a float.
|
* The alpha of the prop, as a float.
|
||||||
* @default 1.0
|
* @default 1.0
|
||||||
*/
|
*/
|
||||||
var alpha:Null<Float>;
|
var alpha:Null<Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not zero, this prop will play an animation every X beats of the song.
|
* If not zero, this prop will play an animation every X beats of the song.
|
||||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||||
* they will alternated between, otherwise the `idle` animation will be used.
|
* they will alternated between, otherwise the `idle` animation will be used.
|
||||||
*
|
*
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
var danceEvery:Null<Int>;
|
var danceEvery:Null<Int>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||||
* Represented as a float or as an [x, y] array of two floats.
|
* Represented as a float or as an [x, y] array of two floats.
|
||||||
* [1, 1] means the prop moves 1:1 with the camera.
|
* [1, 1] means the prop moves 1:1 with the camera.
|
||||||
* [0.5, 0.5] means the prop half as much as the camera.
|
* [0.5, 0.5] means the prop half as much as the camera.
|
||||||
* [0, 0] means the prop is not moved.
|
* [0, 0] means the prop is not moved.
|
||||||
* @default [0, 0]
|
* @default [0, 0]
|
||||||
*/
|
*/
|
||||||
var scroll:OneOfTwo<Float, Array<Float>>;
|
var scroll:OneOfTwo<Float, Array<Float>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional array of animations which the prop can play.
|
* An optional array of animations which the prop can play.
|
||||||
* @default Prop has no animations.
|
* @default Prop has no animations.
|
||||||
*/
|
*/
|
||||||
var animations:Array<AnimationData>;
|
var animations:Array<AnimationData>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If animations are used, this is the name of the animation to play first.
|
* If animations are used, this is the name of the animation to play first.
|
||||||
* @default Don't play an animation.
|
* @default Don't play an animation.
|
||||||
*/
|
*/
|
||||||
var startingAnimation:String;
|
var startingAnimation:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The animation type to use.
|
* The animation type to use.
|
||||||
* Options: "sparrow", "packer"
|
* Options: "sparrow", "packer"
|
||||||
* @default "sparrow"
|
* @default "sparrow"
|
||||||
*/
|
*/
|
||||||
var animType:String;
|
var animType:String;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef StageDataCharacter =
|
typedef StageDataCharacter =
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* A number determining the stack order of the character, relative to props and other characters in the stage.
|
* A number determining the stack order of the character, relative to props and other characters in the stage.
|
||||||
* Again, just like CSS.
|
* Again, just like CSS.
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
zIndex:Null<Int>,
|
zIndex:Null<Int>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position to render the character at.
|
* The position to render the character at.
|
||||||
*/
|
*/
|
||||||
position:Array<Float>,
|
position:Array<Float>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The camera offsets to apply when focusing on the character on this stage.
|
* The camera offsets to apply when focusing on the character on this stage.
|
||||||
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
||||||
*/
|
*/
|
||||||
cameraOffsets:Array<Float>,
|
cameraOffsets:Array<Float>,
|
||||||
};
|
};
|
||||||
|
|
|
@ -55,11 +55,12 @@ class AddNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
if (appendToSelection)
|
if (appendToSelection)
|
||||||
{
|
{
|
||||||
state.currentSelection = state.currentSelection.concat(notes);
|
state.currentNoteSelection = state.currentNoteSelection.concat(notes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state.currentSelection = notes;
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
@ -74,7 +75,8 @@ class AddNotesCommand implements ChartEditorCommand
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
state.currentSelection = [];
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
|
@ -108,7 +110,8 @@ class RemoveNotesCommand implements ChartEditorCommand
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
state.currentSelection = [];
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
|
@ -124,7 +127,8 @@ class RemoveNotesCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData.push(note);
|
state.currentSongChartNoteData.push(note);
|
||||||
}
|
}
|
||||||
state.currentSelection = notes;
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = [];
|
||||||
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
|
@ -146,6 +150,241 @@ class RemoveNotesCommand implements ChartEditorCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends one or more items to the selection.
|
||||||
|
*/
|
||||||
|
class SelectItemsCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in this.notes)
|
||||||
|
{
|
||||||
|
state.currentNoteSelection.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event in this.events)
|
||||||
|
{
|
||||||
|
state.currentEventSelection.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||||
|
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||||
|
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var len:Int = notes.length + events.length;
|
||||||
|
|
||||||
|
if (notes.length == 0)
|
||||||
|
{
|
||||||
|
if (events.length == 1)
|
||||||
|
{
|
||||||
|
return 'Select Event';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 'Select ${events.length} Events';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (events.length == 0)
|
||||||
|
{
|
||||||
|
if (notes.length == 1)
|
||||||
|
{
|
||||||
|
return 'Select Note';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 'Select ${notes.length} Notes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Select ${len} Items';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddEventsCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
private var appendToSelection:Bool;
|
||||||
|
|
||||||
|
public function new(events:Array<SongEventData>, ?appendToSelection:Bool = false)
|
||||||
|
{
|
||||||
|
this.events = events;
|
||||||
|
this.appendToSelection = appendToSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appendToSelection)
|
||||||
|
{
|
||||||
|
state.currentEventSelection = state.currentEventSelection.concat(events);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
|
||||||
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
var len:Int = events.length;
|
||||||
|
return 'Add $len Events';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveEventsCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
|
||||||
|
public function new(events:Array<SongEventData>)
|
||||||
|
{
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData.push(event);
|
||||||
|
}
|
||||||
|
state.currentEventSelection = events;
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
if (events.length == 1 && events[0] != null)
|
||||||
|
{
|
||||||
|
return 'Remove Event';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Remove ${events.length} Events';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveItemsCommand implements ChartEditorCommand
|
||||||
|
{
|
||||||
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
|
||||||
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||||
|
{
|
||||||
|
this.notes = notes;
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
|
||||||
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undo(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
for (note in notes)
|
||||||
|
{
|
||||||
|
state.currentSongChartNoteData.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event in events)
|
||||||
|
{
|
||||||
|
state.currentSongChartEventData.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = events;
|
||||||
|
|
||||||
|
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||||
|
|
||||||
|
state.saveDataDirty = true;
|
||||||
|
state.noteDisplayDirty = true;
|
||||||
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
|
state.sortChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'Remove ${notes.length + events.length} Items';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SwitchDifficultyCommand implements ChartEditorCommand
|
class SwitchDifficultyCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var prevDifficulty:String;
|
private var prevDifficulty:String;
|
||||||
|
@ -185,61 +424,21 @@ class SwitchDifficultyCommand implements ChartEditorCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class DeselectItemsCommand implements ChartEditorCommand
|
||||||
* Adds one or more notes to the selection.
|
|
||||||
*/
|
|
||||||
class SelectNotesCommand implements ChartEditorCommand
|
|
||||||
{
|
{
|
||||||
private var notes:Array<SongNoteData>;
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>)
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
|
this.events = events;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
for (note in this.notes)
|
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentNoteSelection, this.notes);
|
||||||
{
|
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentEventSelection, this.events);
|
||||||
state.currentSelection.push(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
|
||||||
state.notePreviewDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
|
||||||
{
|
|
||||||
state.currentSelection = SongDataUtils.subtractNotes(state.currentSelection, this.notes);
|
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
|
||||||
state.notePreviewDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
if (notes.length == 1)
|
|
||||||
{
|
|
||||||
var dir:String = notes[0].getDirectionName();
|
|
||||||
return 'Select $dir Note';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Select ${notes.length} Notes';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeselectNotesCommand implements ChartEditorCommand
|
|
||||||
{
|
|
||||||
private var notes:Array<SongNoteData>;
|
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>)
|
|
||||||
{
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
|
||||||
{
|
|
||||||
state.currentSelection = SongDataUtils.subtractNotes(state.currentSelection, this.notes);
|
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -249,7 +448,12 @@ class DeselectNotesCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
for (note in this.notes)
|
for (note in this.notes)
|
||||||
{
|
{
|
||||||
state.currentSelection.push(note);
|
state.currentNoteSelection.push(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event in this.events)
|
||||||
|
{
|
||||||
|
state.currentEventSelection.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
@ -258,13 +462,15 @@ class DeselectNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
if (notes.length == 1)
|
var noteCount = notes.length + events.length;
|
||||||
|
|
||||||
|
if (noteCount == 1)
|
||||||
{
|
{
|
||||||
var dir:String = notes[0].getDirectionName();
|
var dir:String = notes[0].getDirectionName();
|
||||||
return 'Deselect $dir Note';
|
return 'Deselect $dir Items';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Deselect ${notes.length} Notes';
|
return 'Deselect ${noteCount} Items';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,20 +478,26 @@ class DeselectNotesCommand implements ChartEditorCommand
|
||||||
* Sets the selection rather than appends it.
|
* Sets the selection rather than appends it.
|
||||||
* Deselects any notes that are not in the new selection.
|
* Deselects any notes that are not in the new selection.
|
||||||
*/
|
*/
|
||||||
class SetNoteSelectionCommand implements ChartEditorCommand
|
class SetItemSelectionCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var notes:Array<SongNoteData>;
|
private var notes:Array<SongNoteData>;
|
||||||
private var previousSelection:Array<SongNoteData>;
|
private var events:Array<SongEventData>;
|
||||||
|
private var previousNoteSelection:Array<SongNoteData>;
|
||||||
|
private var previousEventSelection:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>, ?previousSelection:Array<SongNoteData>)
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>,
|
||||||
|
previousEventSelection:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
this.events = events;
|
||||||
|
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||||
|
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = notes;
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = events;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -293,7 +505,8 @@ class SetNoteSelectionCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = previousSelection;
|
state.currentNoteSelection = previousNoteSelection;
|
||||||
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -301,29 +514,34 @@ class SetNoteSelectionCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Select ${notes.length} Notes';
|
return 'Select ${notes.length} Items';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectAllNotesCommand implements ChartEditorCommand
|
class SelectAllItemsCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var previousSelection:Array<SongNoteData>;
|
private var previousNoteSelection:Array<SongNoteData>;
|
||||||
|
private var previousEventSelection:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(?previousSelection:Array<SongNoteData>)
|
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||||
|
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = state.currentSongChartNoteData;
|
state.currentNoteSelection = state.currentSongChartNoteData;
|
||||||
|
state.currentEventSelection = state.currentSongChartEventData;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = previousSelection;
|
state.currentNoteSelection = previousNoteSelection;
|
||||||
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -331,29 +549,33 @@ class SelectAllNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Select All Notes';
|
return 'Select All Items';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvertSelectedNotesCommand implements ChartEditorCommand
|
class InvertSelectedItemsCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var previousSelection:Array<SongNoteData>;
|
private var previousNoteSelection:Array<SongNoteData>;
|
||||||
|
private var previousEventSelection:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(?previousSelection:Array<SongNoteData>)
|
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||||
|
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousSelection);
|
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection);
|
||||||
|
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = previousSelection;
|
state.currentNoteSelection = previousNoteSelection;
|
||||||
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -361,22 +583,25 @@ class InvertSelectedNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Invert Selected Notes';
|
return 'Invert Selected Items';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeselectAllNotesCommand implements ChartEditorCommand
|
class DeselectAllItemsCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var previousSelection:Array<SongNoteData>;
|
private var previousNoteSelection:Array<SongNoteData>;
|
||||||
|
private var previousEventSelection:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(?previousSelection:Array<SongNoteData>)
|
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.previousSelection = previousSelection == null ? [] : previousSelection;
|
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
|
||||||
|
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = [];
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -384,7 +609,8 @@ class DeselectAllNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSelection = previousSelection;
|
state.currentNoteSelection = previousNoteSelection;
|
||||||
|
state.currentEventSelection = previousEventSelection;
|
||||||
|
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -392,27 +618,35 @@ class DeselectAllNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Deselect All Notes';
|
return 'Deselect All Items';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CutNotesCommand implements ChartEditorCommand
|
class CutItemsCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var notes:Array<SongNoteData>;
|
private var notes:Array<SongNoteData>;
|
||||||
|
private var events:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(notes:Array<SongNoteData>)
|
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
|
||||||
{
|
{
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
|
this.events = events;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
// Copy the notes.
|
// Copy the notes.
|
||||||
SongDataUtils.writeNotesToClipboard(SongDataUtils.buildClipboard(notes));
|
SongDataUtils.writeItemsToClipboard({
|
||||||
|
notes: SongDataUtils.buildNoteClipboard(notes),
|
||||||
|
events: SongDataUtils.buildEventClipboard(events)
|
||||||
|
});
|
||||||
|
|
||||||
// Delete the notes.
|
// Delete the notes.
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
state.currentSelection = [];
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
@ -422,19 +656,27 @@ class CutNotesCommand implements ChartEditorCommand
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||||
state.currentSelection = notes;
|
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
||||||
|
|
||||||
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = events;
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
var len:Int = notes.length;
|
var len:Int = notes.length + events.length;
|
||||||
return 'Cut $len Notes to Clipboard';
|
|
||||||
|
if (notes.length == 0)
|
||||||
|
return 'Cut $len Events to Clipboard';
|
||||||
|
else if (events.length == 0)
|
||||||
|
return 'Cut $len Notes to Clipboard';
|
||||||
|
else
|
||||||
|
return 'Cut $len Items to Clipboard';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,7 +699,8 @@ class FlipNotesCommand implements ChartEditorCommand
|
||||||
flippedNotes = SongDataUtils.flipNotes(notes);
|
flippedNotes = SongDataUtils.flipNotes(notes);
|
||||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
|
||||||
|
|
||||||
state.currentSelection = flippedNotes;
|
state.currentNoteSelection = flippedNotes;
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
@ -470,7 +713,8 @@ class FlipNotesCommand implements ChartEditorCommand
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
|
||||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
|
||||||
|
|
||||||
state.currentSelection = notes;
|
state.currentNoteSelection = notes;
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
@ -486,11 +730,12 @@ class FlipNotesCommand implements ChartEditorCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasteNotesCommand implements ChartEditorCommand
|
class PasteItemsCommand implements ChartEditorCommand
|
||||||
{
|
{
|
||||||
private var targetTimestamp:Float;
|
private var targetTimestamp:Float;
|
||||||
// Notes we added with this command, for undo.
|
// Notes we added with this command, for undo.
|
||||||
private var addedNotes:Array<SongNoteData>;
|
private var addedNotes:Array<SongNoteData>;
|
||||||
|
private var addedEvents:Array<SongEventData>;
|
||||||
|
|
||||||
public function new(targetTimestamp:Float)
|
public function new(targetTimestamp:Float)
|
||||||
{
|
{
|
||||||
|
@ -499,12 +744,15 @@ class PasteNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
var currentClipboard:Array<SongNoteData> = SongDataUtils.readNotesFromClipboard();
|
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||||
|
|
||||||
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard, Std.int(targetTimestamp));
|
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
|
||||||
|
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
|
||||||
|
|
||||||
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(addedNotes);
|
||||||
state.currentSelection = addedNotes.copy();
|
state.currentSongChartEventData = state.currentSongChartEventData.concat(addedEvents);
|
||||||
|
state.currentNoteSelection = addedNotes.copy();
|
||||||
|
state.currentEventSelection = addedEvents.copy();
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
@ -516,7 +764,9 @@ class PasteNotesCommand implements ChartEditorCommand
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||||
state.currentSelection = [];
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
|
||||||
|
state.currentNoteSelection = [];
|
||||||
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
state.saveDataDirty = true;
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
@ -527,52 +777,16 @@ class PasteNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
var currentClipboard:Array<SongNoteData> = SongDataUtils.readNotesFromClipboard();
|
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
|
||||||
return 'Paste ${currentClipboard.length} Notes from Clipboard';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AddEventsCommand implements ChartEditorCommand
|
var len:Int = currentClipboard.notes.length + currentClipboard.events.length;
|
||||||
{
|
|
||||||
private var events:Array<SongEventData>;
|
|
||||||
private var appendToSelection:Bool;
|
|
||||||
|
|
||||||
public function new(events:Array<SongEventData>, ?appendToSelection:Bool = false)
|
if (currentClipboard.notes.length == 0)
|
||||||
{
|
return 'Paste $len Events';
|
||||||
this.events = events;
|
else if (currentClipboard.events.length == 0)
|
||||||
this.appendToSelection = appendToSelection;
|
return 'Paste $len Notes';
|
||||||
}
|
else
|
||||||
|
return 'Paste $len Items';
|
||||||
public function execute(state:ChartEditorState):Void
|
|
||||||
{
|
|
||||||
state.currentSongChartEventData = state.currentSongChartEventData.concat(events);
|
|
||||||
// TODO: Allow selecting events.
|
|
||||||
// state.currentSelection = events;
|
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
|
||||||
state.noteDisplayDirty = true;
|
|
||||||
state.notePreviewDirty = true;
|
|
||||||
|
|
||||||
state.sortChartData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
|
||||||
{
|
|
||||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
|
||||||
|
|
||||||
state.currentSelection = [];
|
|
||||||
|
|
||||||
state.saveDataDirty = true;
|
|
||||||
state.noteDisplayDirty = true;
|
|
||||||
state.notePreviewDirty = true;
|
|
||||||
|
|
||||||
state.sortChartData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
var len:Int = events.length;
|
|
||||||
return 'Add $len Events';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
101
source/funkin/ui/debug/charting/ChartEditorEventSprite.hx
Normal file
101
source/funkin/ui/debug/charting/ChartEditorEventSprite.hx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import flixel.FlxObject;
|
||||||
|
import flixel.FlxBasic;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.graphics.frames.FlxFramesCollection;
|
||||||
|
import flixel.graphics.frames.FlxTileFrames;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import funkin.play.song.SongData.SongEventData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A event sprite that can be used to display a song event in a chart.
|
||||||
|
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||||
|
*/
|
||||||
|
class ChartEditorEventSprite extends FlxSprite
|
||||||
|
{
|
||||||
|
public var parentState:ChartEditorState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The note data that this sprite represents.
|
||||||
|
* You can set this to null to kill the sprite and flag it for recycling.
|
||||||
|
*/
|
||||||
|
public var eventData(default, set):SongEventData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The image used for all song events. Cached for performance.
|
||||||
|
*/
|
||||||
|
var eventGraphic:BitmapData;
|
||||||
|
|
||||||
|
public function new(parent:ChartEditorState)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.parentState = parent;
|
||||||
|
|
||||||
|
buildGraphic();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGraphic():Void
|
||||||
|
{
|
||||||
|
if (eventGraphic == null)
|
||||||
|
{
|
||||||
|
eventGraphic = Assets.getBitmapData(Paths.image('ui/chart-editor/event'));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadGraphic(eventGraphic);
|
||||||
|
setGraphicSize(ChartEditorState.GRID_SIZE);
|
||||||
|
this.updateHitbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_eventData(value:SongEventData):SongEventData
|
||||||
|
{
|
||||||
|
this.eventData = value;
|
||||||
|
|
||||||
|
if (this.eventData == null)
|
||||||
|
{
|
||||||
|
// Disown parent.
|
||||||
|
this.kill();
|
||||||
|
return this.eventData;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
// Update the position to match the note data.
|
||||||
|
updateEventPosition();
|
||||||
|
|
||||||
|
return this.eventData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateEventPosition(?origin:FlxObject)
|
||||||
|
{
|
||||||
|
this.x = (ChartEditorState.STRUMLINE_SIZE * 2 + 1 - 1) * ChartEditorState.GRID_SIZE;
|
||||||
|
if (this.eventData.stepTime >= 0)
|
||||||
|
this.y = this.eventData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
|
||||||
|
if (origin != null)
|
||||||
|
{
|
||||||
|
this.x += origin.x;
|
||||||
|
this.y += origin.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this note (or its parent) is currently visible.
|
||||||
|
*/
|
||||||
|
public function isEventVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
||||||
|
{
|
||||||
|
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
|
||||||
|
|
||||||
|
if (!outsideViewArea)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if this note's parent or child is visible.
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -169,7 +169,8 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
if (this.noteData.stepTime >= 0)
|
if (this.noteData.stepTime >= 0)
|
||||||
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
|
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
|
||||||
if (origin != null) {
|
if (origin != null)
|
||||||
|
{
|
||||||
this.x += origin.x;
|
this.x += origin.x;
|
||||||
this.y += origin.y;
|
this.y += origin.y;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,25 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
import funkin.play.song.SongData.SongTimeChange;
|
import haxe.ui.data.ArrayDataSource;
|
||||||
import haxe.ui.components.Slider;
|
|
||||||
import haxe.ui.components.NumberStepper;
|
|
||||||
import haxe.ui.components.NumberStepper;
|
|
||||||
import haxe.ui.components.TextField;
|
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
import funkin.play.event.SongEvent;
|
||||||
|
import funkin.play.song.SongData.SongTimeChange;
|
||||||
import funkin.play.song.SongSerializer;
|
import funkin.play.song.SongSerializer;
|
||||||
|
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||||
import haxe.ui.components.Button;
|
import haxe.ui.components.Button;
|
||||||
|
import haxe.ui.components.CheckBox;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
import haxe.ui.containers.Group;
|
import haxe.ui.components.Label;
|
||||||
|
import haxe.ui.components.NumberStepper;
|
||||||
|
import haxe.ui.components.NumberStepper;
|
||||||
|
import haxe.ui.components.Slider;
|
||||||
|
import haxe.ui.components.TextField;
|
||||||
import haxe.ui.containers.dialogs.Dialog;
|
import haxe.ui.containers.dialogs.Dialog;
|
||||||
|
import haxe.ui.containers.Box;
|
||||||
|
import haxe.ui.containers.Frame;
|
||||||
|
import haxe.ui.containers.Grid;
|
||||||
|
import haxe.ui.containers.Group;
|
||||||
|
import haxe.ui.core.Component;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,415 +27,556 @@ import haxe.ui.events.UIEvent;
|
||||||
*/
|
*/
|
||||||
enum ChartEditorToolMode
|
enum ChartEditorToolMode
|
||||||
{
|
{
|
||||||
Select;
|
Select;
|
||||||
Place;
|
Place;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChartEditorToolboxHandler
|
class ChartEditorToolboxHandler
|
||||||
{
|
{
|
||||||
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
|
||||||
{
|
{
|
||||||
if (shown)
|
if (shown)
|
||||||
showToolbox(state, id);
|
showToolbox(state, id);
|
||||||
else
|
else
|
||||||
hideToolbox(state, id);
|
hideToolbox(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function showToolbox(state:ChartEditorState, id:String)
|
public static function showToolbox(state:ChartEditorState, id:String)
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
var toolbox:Dialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
if (toolbox == null)
|
if (toolbox == null)
|
||||||
toolbox = initToolbox(state, id);
|
toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
if (toolbox != null)
|
if (toolbox != null)
|
||||||
{
|
{
|
||||||
toolbox.showDialog(false);
|
toolbox.showDialog(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('ChartEditorToolboxHandler.showToolbox() - Could not retrieve toolbox: $id');
|
trace('ChartEditorToolboxHandler.showToolbox() - Could not retrieve toolbox: $id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hideToolbox(state:ChartEditorState, id:String):Void
|
public static function hideToolbox(state:ChartEditorState, id:String):Void
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
var toolbox:Dialog = state.activeToolboxes.get(id);
|
||||||
|
|
||||||
if (toolbox == null)
|
if (toolbox == null)
|
||||||
toolbox = initToolbox(state, id);
|
toolbox = initToolbox(state, id);
|
||||||
|
|
||||||
if (toolbox != null)
|
if (toolbox != null)
|
||||||
{
|
{
|
||||||
toolbox.hideDialog(DialogButton.CANCEL);
|
toolbox.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('ChartEditorToolboxHandler.hideToolbox() - Could not retrieve toolbox: $id');
|
trace('ChartEditorToolboxHandler.hideToolbox() - Could not retrieve toolbox: $id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function minimizeToolbox(state:ChartEditorState, id:String):Void
|
public static function minimizeToolbox(state:ChartEditorState, id:String):Void {}
|
||||||
{
|
|
||||||
}
|
public static function maximizeToolbox(state:ChartEditorState, id:String):Void {}
|
||||||
|
|
||||||
public static function maximizeToolbox(state:ChartEditorState, id:String):Void
|
public static function initToolbox(state:ChartEditorState, id:String):Dialog
|
||||||
{
|
{
|
||||||
}
|
var toolbox:Dialog = null;
|
||||||
|
switch (id)
|
||||||
public static function initToolbox(state:ChartEditorState, id:String):Dialog
|
{
|
||||||
{
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||||
var toolbox:Dialog = null;
|
toolbox = buildToolboxToolsLayout(state);
|
||||||
switch (id)
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
||||||
{
|
toolbox = buildToolboxNoteDataLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
||||||
toolbox = buildToolboxToolsLayout(state);
|
toolbox = buildToolboxEventDataLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
|
||||||
toolbox = buildToolboxNoteDataLayout(state);
|
toolbox = buildToolboxDifficultyLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||||
toolbox = buildToolboxEventDataLayout(state);
|
toolbox = buildToolboxMetadataLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:
|
||||||
toolbox = buildToolboxDifficultyLayout(state);
|
toolbox = buildToolboxCharactersLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||||
toolbox = buildToolboxMetadataLayout(state);
|
toolbox = buildToolboxPlayerPreviewLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT:
|
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
||||||
toolbox = buildToolboxCharactersLayout(state);
|
toolbox = buildToolboxOpponentPreviewLayout(state);
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
default:
|
||||||
toolbox = buildToolboxPlayerPreviewLayout(state);
|
// This happens if you try to load an unknown layout.
|
||||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
|
||||||
toolbox = buildToolboxOpponentPreviewLayout(state);
|
toolbox = null;
|
||||||
default:
|
}
|
||||||
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
|
|
||||||
toolbox = null;
|
// This happens if the layout you try to load has a syntax error.
|
||||||
}
|
if (toolbox == null)
|
||||||
|
return null;
|
||||||
// Make sure we can reuse the toolbox later.
|
|
||||||
toolbox.destroyOnClose = false;
|
// Make sure we can reuse the toolbox later.
|
||||||
state.activeToolboxes.set(id, toolbox);
|
toolbox.destroyOnClose = false;
|
||||||
|
state.activeToolboxes.set(id, toolbox);
|
||||||
return toolbox;
|
|
||||||
}
|
return toolbox;
|
||||||
|
}
|
||||||
public static function getToolbox(state:ChartEditorState, id:String):Dialog
|
|
||||||
{
|
public static function getToolbox(state:ChartEditorState, id:String):Dialog
|
||||||
var toolbox:Dialog = state.activeToolboxes.get(id);
|
{
|
||||||
|
var toolbox:Dialog = state.activeToolboxes.get(id);
|
||||||
// Initialize the toolbox without showing it.
|
|
||||||
if (toolbox == null)
|
// Initialize the toolbox without showing it.
|
||||||
toolbox = initToolbox(state, id);
|
if (toolbox == null)
|
||||||
|
toolbox = initToolbox(state, id);
|
||||||
return toolbox;
|
|
||||||
}
|
return toolbox;
|
||||||
|
}
|
||||||
static function buildToolboxToolsLayout(state:ChartEditorState):Dialog
|
|
||||||
{
|
static function buildToolboxToolsLayout(state:ChartEditorState):Dialog
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
|
||||||
if (toolbox == null) return null;
|
|
||||||
|
if (toolbox == null)
|
||||||
// Starting position.
|
return null;
|
||||||
toolbox.x = 50;
|
|
||||||
toolbox.y = 50;
|
// Starting position.
|
||||||
|
toolbox.x = 50;
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
toolbox.y = 50;
|
||||||
{
|
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxTools', false);
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
}
|
{
|
||||||
|
state.setUICheckboxSelected('menubarItemToggleToolboxTools', false);
|
||||||
var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group);
|
}
|
||||||
|
|
||||||
if (toolsGroup == null) return null;
|
var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group);
|
||||||
|
|
||||||
toolsGroup.onChange = (event:UIEvent) ->
|
if (toolsGroup == null)
|
||||||
{
|
return null;
|
||||||
switch (event.target.id)
|
|
||||||
{
|
toolsGroup.onChange = (event:UIEvent) ->
|
||||||
case 'toolboxToolsGroupSelect':
|
{
|
||||||
state.currentToolMode = ChartEditorToolMode.Select;
|
switch (event.target.id)
|
||||||
case 'toolboxToolsGroupPlace':
|
{
|
||||||
state.currentToolMode = ChartEditorToolMode.Place;
|
case 'toolboxToolsGroupSelect':
|
||||||
default:
|
state.currentToolMode = ChartEditorToolMode.Select;
|
||||||
trace('ChartEditorToolboxHandler.buildToolboxToolsLayout() - Unknown toolbox tool selected: $event.target.id');
|
case 'toolboxToolsGroupPlace':
|
||||||
}
|
state.currentToolMode = ChartEditorToolMode.Place;
|
||||||
}
|
default:
|
||||||
|
trace('ChartEditorToolboxHandler.buildToolboxToolsLayout() - Unknown toolbox tool selected: $event.target.id');
|
||||||
return toolbox;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog
|
return toolbox;
|
||||||
{
|
}
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
|
||||||
|
static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog
|
||||||
if (toolbox == null) return null;
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||||
// Starting position.
|
|
||||||
toolbox.x = 75;
|
if (toolbox == null)
|
||||||
toolbox.y = 100;
|
return null;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
// Starting position.
|
||||||
{
|
toolbox.x = 75;
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false);
|
toolbox.y = 100;
|
||||||
}
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown);
|
{
|
||||||
|
state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false);
|
||||||
toolboxNotesNoteKind.onChange = (event:UIEvent) ->
|
}
|
||||||
{
|
|
||||||
state.selectedNoteKind = event.data.id;
|
var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown);
|
||||||
}
|
var toolboxNotesCustomKindLabel:Label = toolbox.findComponent("toolboxNotesCustomKindLabel", Label);
|
||||||
|
var toolboxNotesCustomKind:TextField = toolbox.findComponent("toolboxNotesCustomKind", TextField);
|
||||||
return toolbox;
|
|
||||||
}
|
toolboxNotesNoteKind.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog
|
var isCustom = (event.data.id == '~CUSTOM~');
|
||||||
{
|
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
if (isCustom)
|
||||||
|
{
|
||||||
if (toolbox == null) return null;
|
toolboxNotesCustomKindLabel.hidden = false;
|
||||||
|
toolboxNotesCustomKind.hidden = false;
|
||||||
// Starting position.
|
|
||||||
toolbox.x = 100;
|
state.selectedNoteKind = toolboxNotesCustomKind.text;
|
||||||
toolbox.y = 150;
|
}
|
||||||
|
else
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
{
|
||||||
{
|
toolboxNotesCustomKindLabel.hidden = true;
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false);
|
toolboxNotesCustomKind.hidden = true;
|
||||||
}
|
|
||||||
|
state.selectedNoteKind = event.data.id;
|
||||||
return toolbox;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildToolboxDifficultyLayout(state:ChartEditorState):Dialog
|
toolboxNotesCustomKind.onChange = (event:UIEvent) ->
|
||||||
{
|
{
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
state.selectedNoteKind = toolboxNotesCustomKind.text;
|
||||||
|
}
|
||||||
if (toolbox == null) return null;
|
|
||||||
|
return toolbox;
|
||||||
// Starting position.
|
}
|
||||||
toolbox.x = 125;
|
|
||||||
toolbox.y = 200;
|
static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
||||||
{
|
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false);
|
if (toolbox == null)
|
||||||
}
|
return null;
|
||||||
|
|
||||||
var difficultyToolboxSaveMetadata:Button = toolbox.findComponent("difficultyToolboxSaveMetadata", Button);
|
// Starting position.
|
||||||
var difficultyToolboxSaveChart:Button = toolbox.findComponent("difficultyToolboxSaveChart", Button);
|
toolbox.x = 100;
|
||||||
var difficultyToolboxSaveAll:Button = toolbox.findComponent("difficultyToolboxSaveAll", Button);
|
toolbox.y = 150;
|
||||||
var difficultyToolboxLoadMetadata:Button = toolbox.findComponent("difficultyToolboxLoadMetadata", Button);
|
|
||||||
var difficultyToolboxLoadChart:Button = toolbox.findComponent("difficultyToolboxLoadChart", Button);
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
difficultyToolboxSaveMetadata.onClick = (event:UIEvent) ->
|
state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false);
|
||||||
{
|
}
|
||||||
SongSerializer.exportSongMetadata(state.currentSongMetadata);
|
|
||||||
};
|
var toolboxEventsEventKind:DropDown = toolbox.findComponent("toolboxEventsEventKind", DropDown);
|
||||||
|
var toolboxEventsDataGrid:Grid = toolbox.findComponent("toolboxEventsDataGrid", Grid);
|
||||||
difficultyToolboxSaveChart.onClick = (event:UIEvent) ->
|
|
||||||
{
|
toolboxEventsEventKind.dataSource = new ArrayDataSource();
|
||||||
SongSerializer.exportSongChartData(state.currentSongChartData);
|
|
||||||
};
|
var songEvents:Array<SongEvent> = SongEventParser.listEvents();
|
||||||
|
|
||||||
difficultyToolboxSaveAll.onClick = (event:UIEvent) ->
|
for (event in songEvents)
|
||||||
{
|
{
|
||||||
state.exportAllSongData();
|
toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id});
|
||||||
};
|
}
|
||||||
|
|
||||||
difficultyToolboxLoadMetadata.onClick = (event:UIEvent) ->
|
toolboxEventsEventKind.onChange = (event:UIEvent) ->
|
||||||
{
|
{
|
||||||
// Replace metadata for current variation.
|
var eventType:String = event.data.value;
|
||||||
SongSerializer.importSongMetadataAsync(function(songMetadata)
|
|
||||||
{
|
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
|
||||||
state.currentSongMetadata = songMetadata;
|
|
||||||
});
|
var schema:SongEventSchema = SongEventParser.getEventSchema(eventType);
|
||||||
};
|
|
||||||
|
if (schema == null)
|
||||||
difficultyToolboxLoadChart.onClick = (event:UIEvent) ->
|
{
|
||||||
{
|
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: $eventType');
|
||||||
// Replace chart data for current variation.
|
return;
|
||||||
SongSerializer.importSongChartDataAsync(function(songChartData)
|
}
|
||||||
{
|
|
||||||
state.currentSongChartData = songChartData;
|
buildEventDataFormFromSchema(state, toolboxEventsDataGrid, schema);
|
||||||
state.noteDisplayDirty = true;
|
}
|
||||||
});
|
|
||||||
};
|
return toolbox;
|
||||||
|
}
|
||||||
state.difficultySelectDirty = true;
|
|
||||||
|
static function buildEventDataFormFromSchema(state:ChartEditorState, target:Box, schema:SongEventSchema):Void
|
||||||
return toolbox;
|
{
|
||||||
}
|
trace(schema);
|
||||||
|
// Clear the frame.
|
||||||
static function buildToolboxMetadataLayout(state:ChartEditorState):Dialog
|
target.removeAllComponents();
|
||||||
{
|
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
state.selectedEventData = {};
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
for (field in schema)
|
||||||
|
{
|
||||||
// Starting position.
|
// Add a label.
|
||||||
toolbox.x = 150;
|
var label:Label = new Label();
|
||||||
toolbox.y = 250;
|
label.text = field.title;
|
||||||
|
target.addComponent(label);
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
|
||||||
{
|
var input:Component;
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false);
|
switch (field.type)
|
||||||
}
|
{
|
||||||
|
case INTEGER:
|
||||||
var inputSongName:TextField = toolbox.findComponent('inputSongName', TextField);
|
var numberStepper:NumberStepper = new NumberStepper();
|
||||||
inputSongName.onChange = (event:UIEvent) ->
|
numberStepper.id = field.name;
|
||||||
{
|
numberStepper.step = field.step == null ? 1.0 : field.step;
|
||||||
var valid = event.target.text != null && event.target.text != "";
|
numberStepper.min = field.min;
|
||||||
|
numberStepper.max = field.max;
|
||||||
if (valid)
|
numberStepper.value = field.defaultValue;
|
||||||
{
|
input = numberStepper;
|
||||||
inputSongName.removeClass('invalid-value');
|
case FLOAT:
|
||||||
state.currentSongMetadata.songName = event.target.text;
|
var numberStepper:NumberStepper = new NumberStepper();
|
||||||
}
|
numberStepper.id = field.name;
|
||||||
else
|
numberStepper.step = field.step == null ? 0.1 : field.step;
|
||||||
{
|
numberStepper.min = field.min;
|
||||||
state.currentSongMetadata.songName = null;
|
numberStepper.max = field.max;
|
||||||
}
|
numberStepper.value = field.defaultValue;
|
||||||
};
|
input = numberStepper;
|
||||||
|
case BOOL:
|
||||||
var inputSongArtist:TextField = toolbox.findComponent('inputSongArtist', TextField);
|
var checkBox = new CheckBox();
|
||||||
inputSongArtist.onChange = (event:UIEvent) ->
|
checkBox.id = field.name;
|
||||||
{
|
checkBox.selected = field.defaultValue == true;
|
||||||
var valid = event.target.text != null && event.target.text != "";
|
input = checkBox;
|
||||||
|
case ENUM:
|
||||||
if (valid)
|
var dropDown:DropDown = new DropDown();
|
||||||
{
|
dropDown.id = field.name;
|
||||||
inputSongArtist.removeClass('invalid-value');
|
dropDown.dataSource = new ArrayDataSource();
|
||||||
state.currentSongMetadata.artist = event.target.text;
|
|
||||||
}
|
// Add entries to the dropdown.
|
||||||
else
|
for (optionName in field.keys.keys())
|
||||||
{
|
{
|
||||||
state.currentSongMetadata.artist = null;
|
var optionValue = field.keys.get(optionName);
|
||||||
}
|
trace('$optionName : $optionValue');
|
||||||
};
|
dropDown.dataSource.add({value: optionValue, text: optionName});
|
||||||
|
}
|
||||||
var inputStage:DropDown = toolbox.findComponent('inputStage', DropDown);
|
|
||||||
inputStage.onChange = (event:UIEvent) ->
|
dropDown.value = field.defaultValue;
|
||||||
{
|
|
||||||
var valid = event.data != null && event.data.id != null;
|
input = dropDown;
|
||||||
|
case STRING:
|
||||||
if (valid) {
|
input = new TextField();
|
||||||
state.currentSongMetadata.playData.stage = event.data.id;
|
input.id = field.name;
|
||||||
}
|
input.text = field.defaultValue;
|
||||||
};
|
default:
|
||||||
|
// Unknown type. Display a label so we know what it is.
|
||||||
var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown);
|
input = new Label();
|
||||||
inputNoteSkin.onChange = (event:UIEvent) ->
|
input.id = field.name;
|
||||||
{
|
input.text = field.type;
|
||||||
if (event.data.id == null)
|
}
|
||||||
return;
|
|
||||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
target.addComponent(input);
|
||||||
};
|
|
||||||
|
input.onChange = (event:UIEvent) ->
|
||||||
var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper);
|
{
|
||||||
inputBPM.onChange = (event:UIEvent) ->
|
trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${event.target.value}');
|
||||||
{
|
|
||||||
if (event.value == null || event.value <= 0)
|
if (event.target.value == null)
|
||||||
return;
|
state.selectedEventData.remove(event.target.id);
|
||||||
|
else
|
||||||
var timeChanges = state.currentSongMetadata.timeChanges;
|
state.selectedEventData.set(event.target.id, event.target.value);
|
||||||
if (timeChanges == null || timeChanges.length == 0)
|
}
|
||||||
{
|
}
|
||||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
}
|
||||||
}
|
|
||||||
else
|
static function buildToolboxDifficultyLayout(state:ChartEditorState):Dialog
|
||||||
{
|
{
|
||||||
timeChanges[0].bpm = event.value;
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||||
}
|
|
||||||
|
if (toolbox == null)
|
||||||
Conductor.forceBPM(event.value);
|
return null;
|
||||||
|
|
||||||
state.currentSongMetadata.timeChanges = timeChanges;
|
// Starting position.
|
||||||
};
|
toolbox.x = 125;
|
||||||
|
toolbox.y = 200;
|
||||||
var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider);
|
|
||||||
inputScrollSpeed.onChange = (event:UIEvent) ->
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
{
|
{
|
||||||
var valid = event.target.value != null && event.target.value > 0;
|
state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false);
|
||||||
|
}
|
||||||
if (valid)
|
|
||||||
{
|
var difficultyToolboxSaveMetadata:Button = toolbox.findComponent("difficultyToolboxSaveMetadata", Button);
|
||||||
inputScrollSpeed.removeClass('invalid-value');
|
var difficultyToolboxSaveChart:Button = toolbox.findComponent("difficultyToolboxSaveChart", Button);
|
||||||
state.currentSongChartData.scrollSpeed = event.target.value;
|
var difficultyToolboxSaveAll:Button = toolbox.findComponent("difficultyToolboxSaveAll", Button);
|
||||||
}
|
var difficultyToolboxLoadMetadata:Button = toolbox.findComponent("difficultyToolboxLoadMetadata", Button);
|
||||||
else
|
var difficultyToolboxLoadChart:Button = toolbox.findComponent("difficultyToolboxLoadChart", Button);
|
||||||
{
|
|
||||||
state.currentSongChartData.scrollSpeed = null;
|
difficultyToolboxSaveMetadata.onClick = (event:UIEvent) ->
|
||||||
}
|
{
|
||||||
};
|
SongSerializer.exportSongMetadata(state.currentSongMetadata);
|
||||||
|
};
|
||||||
|
|
||||||
return toolbox;
|
difficultyToolboxSaveChart.onClick = (event:UIEvent) ->
|
||||||
}
|
{
|
||||||
|
SongSerializer.exportSongChartData(state.currentSongChartData);
|
||||||
static function buildToolboxCharactersLayout(state:ChartEditorState):Dialog
|
};
|
||||||
{
|
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT);
|
difficultyToolboxSaveAll.onClick = (event:UIEvent) ->
|
||||||
|
{
|
||||||
if (toolbox == null) return null;
|
state.exportAllSongData();
|
||||||
|
};
|
||||||
// Starting position.
|
|
||||||
toolbox.x = 175;
|
difficultyToolboxLoadMetadata.onClick = (event:UIEvent) ->
|
||||||
toolbox.y = 300;
|
{
|
||||||
|
// Replace metadata for current variation.
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
SongSerializer.importSongMetadataAsync(function(songMetadata)
|
||||||
{
|
{
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false);
|
state.currentSongMetadata = songMetadata;
|
||||||
}
|
});
|
||||||
|
};
|
||||||
return toolbox;
|
|
||||||
}
|
difficultyToolboxLoadChart.onClick = (event:UIEvent) ->
|
||||||
|
{
|
||||||
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Dialog
|
// Replace chart data for current variation.
|
||||||
{
|
SongSerializer.importSongChartDataAsync(function(songChartData)
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
{
|
||||||
|
state.currentSongChartData = songChartData;
|
||||||
if (toolbox == null) return null;
|
state.noteDisplayDirty = true;
|
||||||
|
});
|
||||||
// Starting position.
|
};
|
||||||
toolbox.x = 200;
|
|
||||||
toolbox.y = 350;
|
state.difficultySelectDirty = true;
|
||||||
|
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
return toolbox;
|
||||||
{
|
}
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false);
|
|
||||||
}
|
static function buildToolboxMetadataLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||||
// TODO: We need to implement character swapping in ChartEditorState.
|
|
||||||
charPlayer.loadCharacter('bf');
|
if (toolbox == null)
|
||||||
//charPlayer.setScale(0.5);
|
return null;
|
||||||
charPlayer.setCharacterType(CharacterType.BF);
|
|
||||||
charPlayer.flip = true;
|
// Starting position.
|
||||||
|
toolbox.x = 150;
|
||||||
return toolbox;
|
toolbox.y = 250;
|
||||||
}
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):Dialog
|
{
|
||||||
{
|
state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false);
|
||||||
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
}
|
||||||
|
|
||||||
if (toolbox == null) return null;
|
var inputSongName:TextField = toolbox.findComponent('inputSongName', TextField);
|
||||||
|
inputSongName.onChange = (event:UIEvent) ->
|
||||||
// Starting position.
|
{
|
||||||
toolbox.x = 200;
|
var valid = event.target.text != null && event.target.text != "";
|
||||||
toolbox.y = 350;
|
|
||||||
|
if (valid)
|
||||||
toolbox.onDialogClosed = (event:DialogEvent) ->
|
{
|
||||||
{
|
inputSongName.removeClass('invalid-value');
|
||||||
state.setUICheckboxSelected('menubarItemToggleToolboxOpponentPreview', false);
|
state.currentSongMetadata.songName = event.target.text;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
{
|
||||||
// TODO: We need to implement character swapping in ChartEditorState.
|
state.currentSongMetadata.songName = null;
|
||||||
charPlayer.loadCharacter('dad');
|
}
|
||||||
// charPlayer.setScale(0.5);
|
};
|
||||||
charPlayer.setCharacterType(CharacterType.DAD);
|
|
||||||
charPlayer.flip = false;
|
var inputSongArtist:TextField = toolbox.findComponent('inputSongArtist', TextField);
|
||||||
|
inputSongArtist.onChange = (event:UIEvent) ->
|
||||||
return toolbox;
|
{
|
||||||
}
|
var valid = event.target.text != null && event.target.text != "";
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
inputSongArtist.removeClass('invalid-value');
|
||||||
|
state.currentSongMetadata.artist = event.target.text;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.currentSongMetadata.artist = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputStage:DropDown = toolbox.findComponent('inputStage', DropDown);
|
||||||
|
inputStage.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
var valid = event.data != null && event.data.id != null;
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
state.currentSongMetadata.playData.stage = event.data.id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown);
|
||||||
|
inputNoteSkin.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
if (event.data.id == null)
|
||||||
|
return;
|
||||||
|
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper);
|
||||||
|
inputBPM.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
if (event.value == null || event.value <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var timeChanges = state.currentSongMetadata.timeChanges;
|
||||||
|
if (timeChanges == null || timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timeChanges[0].bpm = event.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Conductor.forceBPM(event.value);
|
||||||
|
|
||||||
|
state.currentSongMetadata.timeChanges = timeChanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider);
|
||||||
|
inputScrollSpeed.onChange = (event:UIEvent) ->
|
||||||
|
{
|
||||||
|
var valid = event.target.value != null && event.target.value > 0;
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
inputScrollSpeed.removeClass('invalid-value');
|
||||||
|
state.currentSongChartData.scrollSpeed = event.target.value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.currentSongChartData.scrollSpeed = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxCharactersLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT);
|
||||||
|
|
||||||
|
if (toolbox == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 175;
|
||||||
|
toolbox.y = 300;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
||||||
|
|
||||||
|
if (toolbox == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 200;
|
||||||
|
toolbox.y = 350;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
||||||
|
// TODO: We need to implement character swapping in ChartEditorState.
|
||||||
|
charPlayer.loadCharacter('bf');
|
||||||
|
// charPlayer.setScale(0.5);
|
||||||
|
charPlayer.setCharacterType(CharacterType.BF);
|
||||||
|
charPlayer.flip = true;
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):Dialog
|
||||||
|
{
|
||||||
|
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
||||||
|
|
||||||
|
if (toolbox == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Starting position.
|
||||||
|
toolbox.x = 200;
|
||||||
|
toolbox.y = 350;
|
||||||
|
|
||||||
|
toolbox.onDialogClosed = (event:DialogEvent) ->
|
||||||
|
{
|
||||||
|
state.setUICheckboxSelected('menubarItemToggleToolboxOpponentPreview', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer');
|
||||||
|
// TODO: We need to implement character swapping in ChartEditorState.
|
||||||
|
charPlayer.loadCharacter('dad');
|
||||||
|
// charPlayer.setScale(0.5);
|
||||||
|
charPlayer.setCharacterType(CharacterType.DAD);
|
||||||
|
charPlayer.flip = false;
|
||||||
|
|
||||||
|
return toolbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
113
source/funkin/ui/haxeui/components/Notifbar.hx
Normal file
113
source/funkin/ui/haxeui/components/Notifbar.hx
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package funkin.ui.haxeui.components;
|
||||||
|
|
||||||
|
import flixel.FlxG;
|
||||||
|
import flixel.util.FlxTimer;
|
||||||
|
import haxe.ui.RuntimeComponentBuilder;
|
||||||
|
import haxe.ui.components.Button;
|
||||||
|
import haxe.ui.components.Label;
|
||||||
|
import haxe.ui.containers.Box;
|
||||||
|
import haxe.ui.containers.SideBar;
|
||||||
|
import haxe.ui.containers.VBox;
|
||||||
|
import haxe.ui.core.Component;
|
||||||
|
|
||||||
|
class Notifbar extends SideBar
|
||||||
|
{
|
||||||
|
final NOTIFICATION_DISMISS_TIME = 5.0; // seconds
|
||||||
|
var dismissTimer:FlxTimer = null;
|
||||||
|
|
||||||
|
var outerContainer:Box = null;
|
||||||
|
var container:VBox = null;
|
||||||
|
var message:Label = null;
|
||||||
|
var action:Button = null;
|
||||||
|
var dismiss:Button = null;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
buildSidebar();
|
||||||
|
buildChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showNotification(message:String, ?actionText:String = null, ?actionCallback:Void->Void = null, ?dismissTime:Float = null)
|
||||||
|
{
|
||||||
|
if (dismissTimer != null)
|
||||||
|
dismissNotification();
|
||||||
|
|
||||||
|
if (dismissTime == null)
|
||||||
|
dismissTime = NOTIFICATION_DISMISS_TIME;
|
||||||
|
|
||||||
|
// Message text.
|
||||||
|
this.message.text = message;
|
||||||
|
|
||||||
|
// Action
|
||||||
|
if (actionText != null)
|
||||||
|
{
|
||||||
|
this.action.text = actionText;
|
||||||
|
this.action.visible = true;
|
||||||
|
this.action.disabled = false;
|
||||||
|
this.action.onClick = (_) ->
|
||||||
|
{
|
||||||
|
actionCallback();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.action.visible = false;
|
||||||
|
this.action.disabled = false;
|
||||||
|
this.action.onClick = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
|
||||||
|
// Auto dismiss.
|
||||||
|
dismissTimer = new FlxTimer().start(dismissTime, (_:FlxTimer) -> dismissNotification());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dismissNotification()
|
||||||
|
{
|
||||||
|
if (dismissTimer != null)
|
||||||
|
{
|
||||||
|
dismissTimer.cancel();
|
||||||
|
dismissTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSidebar():Void
|
||||||
|
{
|
||||||
|
this.width = 256;
|
||||||
|
this.height = 80;
|
||||||
|
|
||||||
|
// border-top: 1px solid #000; border-left: 1px solid #000;
|
||||||
|
this.styleString = "border: 1px solid #000; background-color: #3d3f41; padding: 8px; border-top-left-radius: 8px;";
|
||||||
|
|
||||||
|
// float to the right
|
||||||
|
this.x = FlxG.width - this.width;
|
||||||
|
|
||||||
|
this.position = "bottom";
|
||||||
|
this.method = "float";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChildren():Void
|
||||||
|
{
|
||||||
|
outerContainer = cast(buildComponent("assets/data/notifbar.xml"), Box);
|
||||||
|
addComponent(outerContainer);
|
||||||
|
|
||||||
|
container = outerContainer.findComponent('notifbarContainer', VBox);
|
||||||
|
message = outerContainer.findComponent('notifbarMessage', Label);
|
||||||
|
action = outerContainer.findComponent('notifbarAction', Button);
|
||||||
|
dismiss = outerContainer.findComponent('notifbarDismiss', Button);
|
||||||
|
|
||||||
|
dismiss.onClick = (_) ->
|
||||||
|
{
|
||||||
|
dismissNotification();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildComponent(path:String):Component
|
||||||
|
{
|
||||||
|
return RuntimeComponentBuilder.fromAsset(path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,177 +13,177 @@ import openfl.net.FileReference;
|
||||||
|
|
||||||
class StageOffsetSubstate extends MusicBeatSubstate
|
class StageOffsetSubstate extends MusicBeatSubstate
|
||||||
{
|
{
|
||||||
var uiStuff:Component;
|
var uiStuff:Component;
|
||||||
|
|
||||||
override function create()
|
override function create()
|
||||||
{
|
{
|
||||||
super.create();
|
super.create();
|
||||||
|
|
||||||
FlxG.mouse.visible = true;
|
FlxG.mouse.visible = true;
|
||||||
PlayState.instance.pauseMusic();
|
PlayState.instance.pauseMusic();
|
||||||
FlxG.camera.target = null;
|
FlxG.camera.target = null;
|
||||||
|
|
||||||
var str = Paths.xml('ui/stage-editor-view');
|
var str = Paths.xml('ui/stage-editor-view');
|
||||||
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
||||||
|
|
||||||
uiStuff.findComponent("lol").onClick = saveCharacterCompile;
|
uiStuff.findComponent("lol").onClick = saveCharacterCompile;
|
||||||
uiStuff.findComponent('saveAs').onClick = saveStageFileRef;
|
uiStuff.findComponent('saveAs').onClick = saveStageFileRef;
|
||||||
|
|
||||||
add(uiStuff);
|
add(uiStuff);
|
||||||
|
|
||||||
PlayState.instance.persistentUpdate = true;
|
PlayState.instance.persistentUpdate = true;
|
||||||
uiStuff.cameras = [PlayState.instance.camHUD];
|
uiStuff.cameras = [PlayState.instance.camHUD];
|
||||||
// btn.cameras = [PlayState.instance.camHUD];
|
// btn.cameras = [PlayState.instance.camHUD];
|
||||||
|
|
||||||
for (thing in PlayState.instance.currentStage)
|
for (thing in PlayState.instance.currentStage)
|
||||||
{
|
{
|
||||||
FlxMouseEvent.add(thing, spr ->
|
FlxMouseEvent.add(thing, spr ->
|
||||||
{
|
{
|
||||||
char = cast thing;
|
char = cast thing;
|
||||||
trace("JUST PRESSED!");
|
trace("JUST PRESSED!");
|
||||||
sprOld.x = thing.x;
|
sprOld.x = thing.x;
|
||||||
sprOld.y = thing.y;
|
sprOld.y = thing.y;
|
||||||
|
|
||||||
mosPosOld.x = FlxG.mouse.x;
|
mosPosOld.x = FlxG.mouse.x;
|
||||||
mosPosOld.y = FlxG.mouse.y;
|
mosPosOld.y = FlxG.mouse.y;
|
||||||
}, null, spr ->
|
}, null, spr ->
|
||||||
{
|
{
|
||||||
// ID tag is to see if currently overlapping hold basically!, a bit more reliable than checking transparency!
|
// ID tag is to see if currently overlapping hold basically!, a bit more reliable than checking transparency!
|
||||||
// used for bug where you can click, and if you click on NO sprite, it snaps the thing to position! unintended!
|
// used for bug where you can click, and if you click on NO sprite, it snaps the thing to position! unintended!
|
||||||
spr.ID = 1;
|
spr.ID = 1;
|
||||||
spr.alpha = 0.5;
|
spr.alpha = 0.5;
|
||||||
}, spr ->
|
}, spr ->
|
||||||
{
|
{
|
||||||
spr.ID = 0;
|
spr.ID = 0;
|
||||||
spr.alpha = 1;
|
spr.alpha = 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mosPosOld:FlxPoint = new FlxPoint();
|
var mosPosOld:FlxPoint = new FlxPoint();
|
||||||
var sprOld:FlxPoint = new FlxPoint();
|
var sprOld:FlxPoint = new FlxPoint();
|
||||||
|
|
||||||
var char:FlxSprite = null;
|
var char:FlxSprite = null;
|
||||||
var overlappingChar:Bool = false;
|
var overlappingChar:Bool = false;
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
if (char != null && char.ID == 1 && FlxG.mouse.pressed)
|
if (char != null && char.ID == 1 && FlxG.mouse.pressed)
|
||||||
{
|
{
|
||||||
char.x = sprOld.x - (mosPosOld.x - FlxG.mouse.x);
|
char.x = sprOld.x - (mosPosOld.x - FlxG.mouse.x);
|
||||||
char.y = sprOld.y - (mosPosOld.y - FlxG.mouse.y);
|
char.y = sprOld.y - (mosPosOld.y - FlxG.mouse.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.mouse.visible = true;
|
FlxG.mouse.visible = true;
|
||||||
|
|
||||||
CoolUtil.mouseCamDrag();
|
CoolUtil.mouseCamDrag();
|
||||||
|
|
||||||
if (FlxG.keys.pressed.CONTROL)
|
if (FlxG.keys.pressed.CONTROL)
|
||||||
CoolUtil.mouseWheelZoom();
|
CoolUtil.mouseWheelZoom();
|
||||||
|
|
||||||
if (FlxG.mouse.wheel != 0)
|
if (FlxG.mouse.wheel != 0)
|
||||||
{
|
{
|
||||||
FlxG.camera.zoom += FlxG.mouse.wheel * 0.1;
|
FlxG.camera.zoom += FlxG.mouse.wheel * 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.Y)
|
if (FlxG.keys.justPressed.Y)
|
||||||
{
|
{
|
||||||
for (thing in PlayState.instance.currentStage)
|
for (thing in PlayState.instance.currentStage)
|
||||||
{
|
{
|
||||||
FlxMouseEvent.remove(thing);
|
FlxMouseEvent.remove(thing);
|
||||||
thing.alpha = 1;
|
thing.alpha = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiStuff != null)
|
if (uiStuff != null)
|
||||||
remove(uiStuff);
|
remove(uiStuff);
|
||||||
|
|
||||||
uiStuff = null;
|
uiStuff = null;
|
||||||
|
|
||||||
PlayState.instance.resetCamera();
|
PlayState.instance.resetCamera();
|
||||||
FlxG.mouse.visible = false;
|
FlxG.mouse.visible = false;
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _file:FileReference;
|
var _file:FileReference;
|
||||||
|
|
||||||
private function saveStageFileRef(_):Void
|
private function saveStageFileRef(_):Void
|
||||||
{
|
{
|
||||||
var jsonStr = prepStageStuff();
|
var jsonStr = prepStageStuff();
|
||||||
|
|
||||||
_file = new FileReference();
|
_file = new FileReference();
|
||||||
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
||||||
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
||||||
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
||||||
_file.save(jsonStr, PlayState.instance.currentStageId + ".json");
|
_file.save(jsonStr, PlayState.instance.currentStageId + ".json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSaveComplete(_)
|
function onSaveComplete(_)
|
||||||
{
|
{
|
||||||
fileRemoveListens();
|
fileRemoveListens();
|
||||||
FlxG.log.notice("Successfully saved!");
|
FlxG.log.notice("Successfully saved!");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSaveCancel(_)
|
function onSaveCancel(_)
|
||||||
{
|
{
|
||||||
fileRemoveListens();
|
fileRemoveListens();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSaveError(_)
|
function onSaveError(_)
|
||||||
{
|
{
|
||||||
fileRemoveListens();
|
fileRemoveListens();
|
||||||
FlxG.log.error("Problem saving Stage file!");
|
FlxG.log.error("Problem saving Stage file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileRemoveListens()
|
function fileRemoveListens()
|
||||||
{
|
{
|
||||||
_file.removeEventListener(Event.COMPLETE, onSaveComplete);
|
_file.removeEventListener(Event.COMPLETE, onSaveComplete);
|
||||||
_file.removeEventListener(Event.CANCEL, onSaveCancel);
|
_file.removeEventListener(Event.CANCEL, onSaveCancel);
|
||||||
_file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
_file.removeEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
||||||
_file = null;
|
_file = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function saveCharacterCompile(_):Void
|
private function saveCharacterCompile(_):Void
|
||||||
{
|
{
|
||||||
var outputJson:String = prepStageStuff();
|
var outputJson:String = prepStageStuff();
|
||||||
|
|
||||||
#if sys
|
#if sys
|
||||||
// save "local" to the current export.
|
// save "local" to the current export.
|
||||||
sys.io.File.saveContent('./assets/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
sys.io.File.saveContent('./assets/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
||||||
|
|
||||||
// save to the dev version
|
// save to the dev version
|
||||||
sys.io.File.saveContent('../../../../assets/preload/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
sys.io.File.saveContent('../../../../assets/preload/data/stages/' + PlayState.instance.currentStageId + '.json', outputJson);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepStageStuff():String
|
private function prepStageStuff():String
|
||||||
{
|
{
|
||||||
var stageLol:StageData = StageDataParser.parseStageData(PlayState.instance.currentStageId);
|
var stageLol:StageData = StageDataParser.parseStageData(PlayState.instance.currentStageId);
|
||||||
|
|
||||||
for (prop in stageLol.props)
|
for (prop in stageLol.props)
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var posStuff = PlayState.instance.currentStage.namedProps.get(prop.name);
|
var posStuff = PlayState.instance.currentStage.namedProps.get(prop.name);
|
||||||
|
|
||||||
prop.position[0] = posStuff.x;
|
prop.position[0] = posStuff.x;
|
||||||
prop.position[1] = posStuff.y;
|
prop.position[1] = posStuff.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bfPos = PlayState.instance.currentStage.getBoyfriend().feetPosition;
|
var bfPos = PlayState.instance.currentStage.getBoyfriend().feetPosition;
|
||||||
stageLol.characters.bf.position[0] = Std.int(bfPos.x);
|
stageLol.characters.bf.position[0] = Std.int(bfPos.x);
|
||||||
stageLol.characters.bf.position[1] = Std.int(bfPos.y);
|
stageLol.characters.bf.position[1] = Std.int(bfPos.y);
|
||||||
|
|
||||||
var dadPos = PlayState.instance.currentStage.getDad().feetPosition;
|
var dadPos = PlayState.instance.currentStage.getDad().feetPosition;
|
||||||
|
|
||||||
stageLol.characters.dad.position[0] = Std.int(dadPos.x);
|
stageLol.characters.dad.position[0] = Std.int(dadPos.x);
|
||||||
stageLol.characters.dad.position[1] = Std.int(dadPos.y);
|
stageLol.characters.dad.position[1] = Std.int(dadPos.y);
|
||||||
|
|
||||||
var GF_FEET_SNIIIIIIIIIIIIIFFFF = PlayState.instance.currentStage.getGirlfriend().feetPosition;
|
var GF_FEET_SNIIIIIIIIIIIIIFFFF = PlayState.instance.currentStage.getGirlfriend().feetPosition;
|
||||||
stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x);
|
stageLol.characters.gf.position[0] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.x);
|
||||||
stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y);
|
stageLol.characters.gf.position[1] = Std.int(GF_FEET_SNIIIIIIIIIIIIIFFFF.y);
|
||||||
|
|
||||||
return CoolUtil.jsonStringify(stageLol);
|
return CoolUtil.jsonStringify(stageLol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,35 +2,54 @@ package funkin.util;
|
||||||
|
|
||||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
|
|
||||||
|
#if cpp
|
||||||
|
@:cppFileCode('
|
||||||
|
#include <iostream>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
')
|
||||||
|
#end
|
||||||
class WindowUtil
|
class WindowUtil
|
||||||
{
|
{
|
||||||
public static function openURL(targetUrl:String)
|
public static function openURL(targetUrl:String)
|
||||||
{
|
{
|
||||||
#if CAN_OPEN_LINKS
|
#if CAN_OPEN_LINKS
|
||||||
#if linux
|
#if linux
|
||||||
// Sys.command('/usr/bin/xdg-open', [, "&"]);
|
// Sys.command('/usr/bin/xdg-open', [, "&"]);
|
||||||
Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]);
|
Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]);
|
||||||
#else
|
#else
|
||||||
FlxG.openURL(targetUrl);
|
FlxG.openURL(targetUrl);
|
||||||
#end
|
#end
|
||||||
#else
|
#else
|
||||||
trace('Cannot open');
|
trace('Cannot open');
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatched when the game window is closed.
|
* Dispatched when the game window is closed.
|
||||||
*/
|
*/
|
||||||
public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>();
|
public static final windowExit:FlxTypedSignal<Int->Void> = new FlxTypedSignal<Int->Void>();
|
||||||
|
|
||||||
public static function initWindowEvents()
|
public static function initWindowEvents()
|
||||||
{
|
{
|
||||||
// onUpdate is called every frame just before rendering.
|
// onUpdate is called every frame just before rendering.
|
||||||
|
|
||||||
// onExit is called when the game window is closed.
|
// onExit is called when the game window is closed.
|
||||||
openfl.Lib.current.stage.application.onExit.add(function(exitCode:Int)
|
openfl.Lib.current.stage.application.onExit.add(function(exitCode:Int)
|
||||||
{
|
{
|
||||||
windowExit.dispatch(exitCode);
|
windowExit.dispatch(exitCode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns off that annoying "Report to Microsoft" dialog that pops up when the game crashes.
|
||||||
|
*/
|
||||||
|
public static function disableCrashHandler()
|
||||||
|
{
|
||||||
|
#if cpp
|
||||||
|
untyped __cpp__('SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);');
|
||||||
|
#else
|
||||||
|
// Do nothing.
|
||||||
|
#end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ class DataAssets
|
||||||
{
|
{
|
||||||
static function buildDataPath(path:String):String
|
static function buildDataPath(path:String):String
|
||||||
{
|
{
|
||||||
return 'default:assets/data/${path}';
|
return 'assets/data/${path}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function listDataFilesInPath(path:String, ?suffix:String = '.json'):Array<String>
|
public static function listDataFilesInPath(path:String, ?suffix:String = '.json'):Array<String>
|
||||||
|
|
204
source/funkin/util/macro/ClassMacro.hx
Normal file
204
source/funkin/util/macro/ClassMacro.hx
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
import haxe.macro.Type;
|
||||||
|
import funkin.util.macro.MacroUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macros to generate lists of classes at compile time.
|
||||||
|
*
|
||||||
|
* This code is a bitch glad Jason figured it out.
|
||||||
|
* Based on code from CompileTime: https://github.com/jasononeil/compiletime
|
||||||
|
*/
|
||||||
|
class ClassMacro
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets a list of `Class<T>` for all classes in a specified package.
|
||||||
|
*
|
||||||
|
* Example: `var list:Array<Class<Dynamic>> = listClassesInPackage("funkin", true);`
|
||||||
|
*
|
||||||
|
* @param targetPackage A String containing the package name to query.
|
||||||
|
* @param includeSubPackages Whether to include classes located in sub-packages of the target package.
|
||||||
|
* @return A list of classes matching the specified criteria.
|
||||||
|
*/
|
||||||
|
public static macro function listClassesInPackage(targetPackage:String, ?includeSubPackages:Bool = true):ExprOf<Iterable<Class<Dynamic>>>
|
||||||
|
{
|
||||||
|
if (!onGenerateCallbackRegistered)
|
||||||
|
{
|
||||||
|
onGenerateCallbackRegistered = true;
|
||||||
|
Context.onGenerate(onGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request:String = 'package~${targetPackage}~${includeSubPackages ? "recursive" : "nonrecursive"}';
|
||||||
|
|
||||||
|
classListsToGenerate.push(request);
|
||||||
|
|
||||||
|
return macro funkin.util.macro.CompiledClassList.get($v{request});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of `Class<T>` for all classes extending a specified class.
|
||||||
|
*
|
||||||
|
* Example: `var list:Array<Class<FlxSprite>> = listSubclassesOf(FlxSprite);`
|
||||||
|
*
|
||||||
|
* @param targetClass The class to query for subclasses.
|
||||||
|
* @return A list of classes matching the specified criteria.
|
||||||
|
*/
|
||||||
|
public static macro function listSubclassesOf<T>(targetClassExpr:ExprOf<Class<T>>):ExprOf<List<Class<T>>>
|
||||||
|
{
|
||||||
|
if (!onGenerateCallbackRegistered)
|
||||||
|
{
|
||||||
|
onGenerateCallbackRegistered = true;
|
||||||
|
Context.onGenerate(onGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetClass:ClassType = MacroUtil.getClassTypeFromExpr(targetClassExpr);
|
||||||
|
var targetClassPath:String = null;
|
||||||
|
if (targetClass != null)
|
||||||
|
targetClassPath = targetClass.pack.join('.') + '.' + targetClass.name;
|
||||||
|
|
||||||
|
var request:String = 'extend~${targetClassPath}';
|
||||||
|
|
||||||
|
classListsToGenerate.push(request);
|
||||||
|
|
||||||
|
return macro funkin.util.macro.CompiledClassList.getTyped($v{request}, ${targetClassExpr});
|
||||||
|
}
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
/**
|
||||||
|
* Callback executed after the typing phase but before the generation phase.
|
||||||
|
* Receives a list of `haxe.macro.Type` for all types in the program.
|
||||||
|
*
|
||||||
|
* Only metadata can be modified at this time, which makes it a BITCH to access the data at runtime.
|
||||||
|
*/
|
||||||
|
static function onGenerate(allTypes:Array<haxe.macro.Type>)
|
||||||
|
{
|
||||||
|
// Reset these, since onGenerate persists across multiple builds.
|
||||||
|
classListsRaw = [];
|
||||||
|
|
||||||
|
for (request in classListsToGenerate)
|
||||||
|
{
|
||||||
|
classListsRaw.set(request, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (type in allTypes)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
// Class instances
|
||||||
|
case TInst(t, _params):
|
||||||
|
var classType:ClassType = t.get();
|
||||||
|
var className:String = t.toString();
|
||||||
|
|
||||||
|
if (classType.isInterface)
|
||||||
|
{
|
||||||
|
// Ignore interfaces.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (request in classListsToGenerate)
|
||||||
|
{
|
||||||
|
if (doesClassMatchRequest(classType, request))
|
||||||
|
{
|
||||||
|
classListsRaw.get(request).push(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Other types (things like enums)
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileClassLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At this stage in the program, `classListsRaw` is generated, but only accessible by macros.
|
||||||
|
* To make it accessible at runtime, we must:
|
||||||
|
* - Convert the String names to actual `Class<T>` instances, and store it as `classLists`
|
||||||
|
* - Insert the `classLists` into the metadata of the `CompiledClassList` class.
|
||||||
|
* `CompiledClassList` then extracts the metadata and stores it where it can be accessed at runtime.
|
||||||
|
*/
|
||||||
|
static function compileClassLists()
|
||||||
|
{
|
||||||
|
var compiledClassList:ClassType = MacroUtil.getClassType("funkin.util.macro.CompiledClassList");
|
||||||
|
|
||||||
|
if (compiledClassList == null)
|
||||||
|
throw "Could not find CompiledClassList class.";
|
||||||
|
|
||||||
|
// Reset outdated metadata.
|
||||||
|
if (compiledClassList.meta.has('classLists'))
|
||||||
|
compiledClassList.meta.remove('classLists');
|
||||||
|
|
||||||
|
var classLists:Array<Expr> = [];
|
||||||
|
// Generate classLists.
|
||||||
|
for (request in classListsToGenerate)
|
||||||
|
{
|
||||||
|
// Expression contains String, [Class<T>...]
|
||||||
|
var classListEntries:Array<Expr> = [macro $v{request}];
|
||||||
|
for (i in classListsRaw.get(request))
|
||||||
|
{
|
||||||
|
// TODO: Boost performance by making this an Array<Class<T>> instead of an Array<String>
|
||||||
|
// How to perform perform macro reificiation to types given a name?
|
||||||
|
classListEntries.push(macro $v{i});
|
||||||
|
}
|
||||||
|
|
||||||
|
classLists.push(macro $a{classListEntries});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert classLists into metadata.
|
||||||
|
compiledClassList.meta.add('classLists', classLists, Context.currentPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
static function doesClassMatchRequest(classType:ClassType, request:String):Bool
|
||||||
|
{
|
||||||
|
var splitRequest:Array<String> = request.split('~');
|
||||||
|
|
||||||
|
var requestType:String = splitRequest[0];
|
||||||
|
|
||||||
|
switch (requestType)
|
||||||
|
{
|
||||||
|
case 'package':
|
||||||
|
var targetPackage:String = splitRequest[1];
|
||||||
|
var recursive:Bool = splitRequest[2] == 'recursive';
|
||||||
|
|
||||||
|
var classPackage:String = classType.pack.join('.');
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
return StringTools.startsWith(classPackage, targetPackage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var regex:EReg = ~/^${targetPackage}(\.|$)/;
|
||||||
|
return regex.match(classPackage);
|
||||||
|
}
|
||||||
|
case 'extend':
|
||||||
|
var targetClassName:String = splitRequest[1];
|
||||||
|
|
||||||
|
var targetClassType:ClassType = MacroUtil.getClassType(targetClassName);
|
||||||
|
|
||||||
|
if (MacroUtil.implementsInterface(classType, targetClassType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (MacroUtil.isSubclassOf(classType, targetClassType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw 'Unknown request type: ${requestType}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var onGenerateCallbackRegistered:Bool = false;
|
||||||
|
|
||||||
|
static var classListsRaw:Map<String, Array<String>> = [];
|
||||||
|
static var classListsToGenerate:Array<String> = [];
|
||||||
|
#end
|
||||||
|
}
|
69
source/funkin/util/macro/CompiledClassList.hx
Normal file
69
source/funkin/util/macro/CompiledClassList.hx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
import haxe.rtti.Meta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complement to `ClassMacro`. See `ClassMacro` for more information.
|
||||||
|
*/
|
||||||
|
class CompiledClassList
|
||||||
|
{
|
||||||
|
static var classLists:Map<String, List<Class<Dynamic>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class lists are injected into this class's metadata during the typing phase.
|
||||||
|
* This function extracts the metadata, at runtime, and stores it in `classLists`.
|
||||||
|
*/
|
||||||
|
static function init():Void
|
||||||
|
{
|
||||||
|
classLists = [];
|
||||||
|
|
||||||
|
// Meta.getType returns Dynamic<Array<Dynamic>>.
|
||||||
|
var metaData = Meta.getType(CompiledClassList);
|
||||||
|
|
||||||
|
if (metaData.classLists != null)
|
||||||
|
{
|
||||||
|
for (list in metaData.classLists)
|
||||||
|
{
|
||||||
|
var data:Array<Dynamic> = cast list;
|
||||||
|
|
||||||
|
// First element is the list ID.
|
||||||
|
var id:String = cast data[0];
|
||||||
|
|
||||||
|
// All other elements are class types.
|
||||||
|
var classes:List<Class<Dynamic>> = new List();
|
||||||
|
for (i in 1...data.length)
|
||||||
|
{
|
||||||
|
var className:String = cast data[i];
|
||||||
|
// var classType:Class<Dynamic> = cast data[i];
|
||||||
|
var classType:Class<Dynamic> = cast Type.resolveClass(className);
|
||||||
|
classes.push(classType);
|
||||||
|
}
|
||||||
|
|
||||||
|
classLists.set(id, classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw "Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(request:String):List<Class<Dynamic>>
|
||||||
|
{
|
||||||
|
if (classLists == null)
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (!classLists.exists(request))
|
||||||
|
{
|
||||||
|
trace('[WARNING] Class list $request not properly generated. Please debug the build macro.');
|
||||||
|
classLists.set(request, new List()); // Make the error only appear once.
|
||||||
|
}
|
||||||
|
|
||||||
|
return classLists.get(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function getTyped<T>(request:String, type:Class<T>):List<Class<T>>
|
||||||
|
{
|
||||||
|
return cast get(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,59 +3,65 @@ package funkin.util.macro;
|
||||||
#if debug
|
#if debug
|
||||||
class GitCommit
|
class GitCommit
|
||||||
{
|
{
|
||||||
public static macro function getGitCommitHash():haxe.macro.Expr.ExprOf<String>
|
/**
|
||||||
{
|
* Get the SHA1 hash of the current Git commit.
|
||||||
#if !display
|
*/
|
||||||
// Get the current line number.
|
public static macro function getGitCommitHash():haxe.macro.Expr.ExprOf<String>
|
||||||
var pos = haxe.macro.Context.currentPos();
|
{
|
||||||
|
#if !display
|
||||||
|
// Get the current line number.
|
||||||
|
var pos = haxe.macro.Context.currentPos();
|
||||||
|
|
||||||
var process = new sys.io.Process('git', ['rev-parse', 'HEAD']);
|
var process = new sys.io.Process('git', ['rev-parse', 'HEAD']);
|
||||||
if (process.exitCode() != 0)
|
if (process.exitCode() != 0)
|
||||||
{
|
{
|
||||||
var message = process.stderr.readAll().toString();
|
var message = process.stderr.readAll().toString();
|
||||||
haxe.macro.Context.info('[WARN] Could not determine current git commit; is this a proper Git repository?', pos);
|
haxe.macro.Context.info('[WARN] Could not determine current git commit; is this a proper Git repository?', pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the output of the process
|
// read the output of the process
|
||||||
var commitHash:String = process.stdout.readLine();
|
var commitHash:String = process.stdout.readLine();
|
||||||
var commitHashSplice:String = commitHash.substr(0, 7);
|
var commitHashSplice:String = commitHash.substr(0, 7);
|
||||||
|
|
||||||
trace('Git Commit ID: ${commitHashSplice}');
|
trace('Git Commit ID: ${commitHashSplice}');
|
||||||
|
|
||||||
// Generates a string expression
|
// Generates a string expression
|
||||||
return macro $v{commitHashSplice};
|
return macro $v{commitHashSplice};
|
||||||
#else
|
#else
|
||||||
// `#if display` is used for code completion. In this case returning an
|
// `#if display` is used for code completion. In this case returning an
|
||||||
// empty string is good enough; We don't want to call git on every hint.
|
// empty string is good enough; We don't want to call git on every hint.
|
||||||
var commitHash:String = "";
|
var commitHash:String = "";
|
||||||
return macro $v{commitHashSplice};
|
return macro $v{commitHashSplice};
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public static macro function getGitBranch():haxe.macro.Expr.ExprOf<String>
|
/**
|
||||||
{
|
* Get the branch name of the current Git commit.
|
||||||
#if !display
|
*/
|
||||||
// Get the current line number.
|
public static macro function getGitBranch():haxe.macro.Expr.ExprOf<String>
|
||||||
var pos = haxe.macro.Context.currentPos();
|
{
|
||||||
var branchProcess = new sys.io.Process('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
|
#if !display
|
||||||
|
// Get the current line number.
|
||||||
|
var pos = haxe.macro.Context.currentPos();
|
||||||
|
var branchProcess = new sys.io.Process('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
|
||||||
|
|
||||||
if (branchProcess.exitCode() != 0)
|
if (branchProcess.exitCode() != 0)
|
||||||
{
|
{
|
||||||
var message = branchProcess.stderr.readAll().toString();
|
var message = branchProcess.stderr.readAll().toString();
|
||||||
haxe.macro.Context.info('[WARN] Could not determine current git commit; is this a proper Git repository?', pos);
|
haxe.macro.Context.info('[WARN] Could not determine current git commit; is this a proper Git repository?', pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
var branchName:String = branchProcess.stdout.readLine();
|
var branchName:String = branchProcess.stdout.readLine();
|
||||||
trace('Git Branch Name: ${branchName}');
|
trace('Git Branch Name: ${branchName}');
|
||||||
|
|
||||||
// Generates a string expression
|
// Generates a string expression
|
||||||
return macro $v{branchName};
|
return macro $v{branchName};
|
||||||
#else
|
#else
|
||||||
// `#if display` is used for code completion. In this case returning an
|
// `#if display` is used for code completion. In this case returning an
|
||||||
// empty string is good enough; We don't want to call git on every hint.
|
// empty string is good enough; We don't want to call git on every hint.
|
||||||
var branchName:String = "";
|
var branchName:String = "";
|
||||||
return macro $v{branchName};
|
return macro $v{branchName};
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -1,12 +1,173 @@
|
||||||
package funkin.util.macro;
|
package funkin.util.macro;
|
||||||
|
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
import haxe.macro.Type;
|
||||||
|
|
||||||
class MacroUtil
|
class MacroUtil
|
||||||
{
|
{
|
||||||
public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr
|
/**
|
||||||
{
|
* Gets the value of a Haxe compiler define.
|
||||||
var value = haxe.macro.Context.definedValue(key);
|
*
|
||||||
if (value == null)
|
* @param key The name of the define to get the value of.
|
||||||
value = defaultValue;
|
* @param defaultValue The value to return if the define is not set.
|
||||||
return macro $v{value};
|
* @return An expression containing the value of the define.
|
||||||
}
|
*/
|
||||||
|
public static macro function getDefine(key:String, defaultValue:String = null):haxe.macro.Expr
|
||||||
|
{
|
||||||
|
var value = haxe.macro.Context.definedValue(key);
|
||||||
|
if (value == null)
|
||||||
|
value = defaultValue;
|
||||||
|
return macro $v{value};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current date and time (at compile time).
|
||||||
|
* @return A `Date` object containing the current date and time.
|
||||||
|
*/
|
||||||
|
public static macro function getDate():ExprOf<Date>
|
||||||
|
{
|
||||||
|
var date = Date.now();
|
||||||
|
var year = toExpr(date.getFullYear());
|
||||||
|
var month = toExpr(date.getMonth());
|
||||||
|
var day = toExpr(date.getDate());
|
||||||
|
var hours = toExpr(date.getHours());
|
||||||
|
var mins = toExpr(date.getMinutes());
|
||||||
|
var secs = toExpr(date.getSeconds());
|
||||||
|
return macro new Date($year, $month, $day, $hours, $mins, $secs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
//
|
||||||
|
// MACRO HELPER FUNCTIONS
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an ExprOf<Class<T>> to a ClassType.
|
||||||
|
* @see https://github.com/jasononeil/compiletime/blob/master/src/CompileTime.hx#L201
|
||||||
|
*/
|
||||||
|
public static function getClassTypeFromExpr(e:Expr):ClassType
|
||||||
|
{
|
||||||
|
var classType:ClassType = null;
|
||||||
|
|
||||||
|
var parts:Array<String> = [];
|
||||||
|
var nextSection:ExprDef = e.expr;
|
||||||
|
|
||||||
|
while (nextSection != null)
|
||||||
|
{
|
||||||
|
var section:ExprDef = nextSection;
|
||||||
|
nextSection = null;
|
||||||
|
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
// Expression is a class name with no packages
|
||||||
|
case EConst(c):
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case CIdent(cn):
|
||||||
|
if (cn != "null") parts.unshift(cn);
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// Expression is a fully qualified package name.
|
||||||
|
// We need to traverse the expression tree to get the full package name.
|
||||||
|
case EField(exp, field):
|
||||||
|
nextSection = exp.expr;
|
||||||
|
parts.unshift(field);
|
||||||
|
|
||||||
|
// We've reached the end of the expression tree.
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullClassName:String = parts.join('.');
|
||||||
|
if (fullClassName != "")
|
||||||
|
{
|
||||||
|
var classType:Type = Context.getType(fullClassName);
|
||||||
|
// Follow typedefs to get the actual class type.
|
||||||
|
var classTypeParsed:Type = Context.follow(classType, false);
|
||||||
|
|
||||||
|
switch (classTypeParsed)
|
||||||
|
{
|
||||||
|
case TInst(t, params):
|
||||||
|
return t.get();
|
||||||
|
default:
|
||||||
|
// We couldn't parse this class type.
|
||||||
|
// This function may need to be updated to be more robust.
|
||||||
|
throw 'Class type could not be parsed: ${fullClassName}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a value to an equivalent macro expression.
|
||||||
|
*/
|
||||||
|
public static function toExpr(value:Dynamic):ExprOf<Dynamic>
|
||||||
|
{
|
||||||
|
return Context.makeExpr(value, Context.currentPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function areClassesEqual(class1:ClassType, class2:ClassType):Bool
|
||||||
|
{
|
||||||
|
return class1.pack.join('.') == class2.pack.join('.') && class1.name == class2.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a ClassType from a string name.
|
||||||
|
*/
|
||||||
|
public static function getClassType(name:String):ClassType
|
||||||
|
{
|
||||||
|
switch (Context.getType(name))
|
||||||
|
{
|
||||||
|
case TInst(t, _params):
|
||||||
|
return t.get();
|
||||||
|
default:
|
||||||
|
throw 'Class type could not be parsed: ${name}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a given ClassType is a subclass of a given superclass.
|
||||||
|
* @param classType The class to check.
|
||||||
|
* @param superClass The superclass to check for.
|
||||||
|
* @return Whether the class is a subclass of the superclass.
|
||||||
|
*/
|
||||||
|
public static function isSubclassOf(classType:ClassType, superClass:ClassType):Bool
|
||||||
|
{
|
||||||
|
if (areClassesEqual(classType, superClass))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (classType.superClass != null)
|
||||||
|
{
|
||||||
|
return isSubclassOf(classType.superClass.t.get(), superClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a given ClassType implements a given interface.
|
||||||
|
* @param classType The class to check.
|
||||||
|
* @param interfaceType The interface to check for.
|
||||||
|
* @return Whether the class implements the interface.
|
||||||
|
*/
|
||||||
|
public static function implementsInterface(classType:ClassType, interfaceType:ClassType):Bool
|
||||||
|
{
|
||||||
|
for (i in classType.interfaces)
|
||||||
|
{
|
||||||
|
if (areClassesEqual(i.t.get(), interfaceType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classType.superClass != null)
|
||||||
|
{
|
||||||
|
return implementsInterface(classType.superClass.t.get(), interfaceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
16
source/funkin/util/tools/MapTools.hx
Normal file
16
source/funkin/util/tools/MapTools.hx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package funkin.util.tools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static extension which provides utility functions for Maps.
|
||||||
|
*
|
||||||
|
* For example, add `using MapTools` then call `map.values()`.
|
||||||
|
*
|
||||||
|
* @see https://haxe.org/manual/lf-static-extension.html
|
||||||
|
*/
|
||||||
|
class MapTools
|
||||||
|
{
|
||||||
|
public static function values<K, T>(map:Map<K, T>):Array<T>
|
||||||
|
{
|
||||||
|
return [for (i in map.iterator()) i];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue