mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-20 17:09:21 +00:00
Merge branch 'rewrite/master' into rewrite/feature/remember-difficulty
This commit is contained in:
commit
c4e2f61e2d
1
.gitmodules
vendored
1
.gitmodules
vendored
|
@ -1,6 +1,7 @@
|
|||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin-history-rewrite-assets
|
||||
branch = master
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin-history-rewrite-art
|
||||
|
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -23,6 +23,12 @@
|
|||
"name": "Haxe Eval",
|
||||
"type": "haxe-eval",
|
||||
"request": "launch"
|
||||
},
|
||||
{
|
||||
// Attaches the debugger to an already running game
|
||||
"name": "HXCPP - Attach",
|
||||
"type": "hxcpp",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
|
||||
case 'Exit to Chart Editor':
|
||||
this.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
|
||||
case 'BACK':
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package funkin.data;
|
||||
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyNote;
|
||||
import hxjsonast.Json;
|
||||
import hxjsonast.Tools;
|
||||
import hxjsonast.Json.JObjectField;
|
||||
import haxe.ds.Either;
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteData;
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds;
|
||||
import haxe.ds.Either;
|
||||
import hxjsonast.Json;
|
||||
import hxjsonast.Json.JObjectField;
|
||||
import hxjsonast.Tools;
|
||||
import thx.semver.Version;
|
||||
import thx.semver.VersionRule;
|
||||
|
||||
/**
|
||||
* `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values.
|
||||
|
@ -23,7 +25,8 @@ class DataParse
|
|||
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
|
||||
* @param json Contains the `pos` and `value` of the property.
|
||||
* @param name The name of the property.
|
||||
* @throws If the property is not a string or is empty.
|
||||
* @throws Error If the property is not a string or is empty.
|
||||
* @return The string value.
|
||||
*/
|
||||
public static function stringNotEmpty(json:Json, name:String):String
|
||||
{
|
||||
|
@ -37,6 +40,42 @@ class DataParse
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `@:jcustomparse(funkin.data.DataParse.semverVersion)`
|
||||
* @param json Contains the `pos` and `value` of the property.
|
||||
* @param name The name of the property.
|
||||
* @return The value of the property as a `thx.semver.Version`.
|
||||
*/
|
||||
public static function semverVersion(json:Json, name:String):Version
|
||||
{
|
||||
switch (json.value)
|
||||
{
|
||||
case JString(s):
|
||||
if (s == "") throw 'Expected version property $name to be non-empty.';
|
||||
return s;
|
||||
default:
|
||||
throw 'Expected version property $name to be a string, but it was ${json.value}.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `@:jcustomparse(funkin.data.DataParse.semverVersionRule)`
|
||||
* @param json Contains the `pos` and `value` of the property.
|
||||
* @param name The name of the property.
|
||||
* @return The value of the property as a `thx.semver.VersionRule`.
|
||||
*/
|
||||
public static function semverVersionRule(json:Json, name:String):VersionRule
|
||||
{
|
||||
switch (json.value)
|
||||
{
|
||||
case JString(s):
|
||||
if (s == "") throw 'Expected version rule property $name to be non-empty.';
|
||||
return s;
|
||||
default:
|
||||
throw 'Expected version rule property $name to be a string, but it was ${json.value}.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser which outputs a Dynamic value, either a object or something else.
|
||||
* @param json
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package funkin.data;
|
||||
|
||||
import funkin.util.SerializerUtil;
|
||||
import thx.semver.Version;
|
||||
import thx.semver.VersionRule;
|
||||
|
||||
/**
|
||||
* `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON.
|
||||
|
@ -9,9 +11,30 @@ import funkin.util.SerializerUtil;
|
|||
*/
|
||||
class DataWrite
|
||||
{
|
||||
/**
|
||||
* `@:jcustomwrite(funkin.data.DataWrite.dynamicValue)`
|
||||
* @param value
|
||||
* @return String
|
||||
*/
|
||||
public static function dynamicValue(value:Dynamic):String
|
||||
{
|
||||
// Is this cheating? Yes. Do I care? No.
|
||||
return SerializerUtil.toJSON(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* `@:jcustomwrite(funkin.data.DataWrite.semverVersion)`
|
||||
*/
|
||||
public static function semverVersion(value:Version):String
|
||||
{
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* `@:jcustomwrite(funkin.data.DataWrite.semverVersionRule)`
|
||||
*/
|
||||
public static function semverVersionRule(value:VersionRule):String
|
||||
{
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ class SongMetadata
|
|||
*
|
||||
*/
|
||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
|
||||
@:default("Unknown")
|
||||
|
@ -203,6 +205,8 @@ class SongMusicData
|
|||
*
|
||||
*/
|
||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
|
||||
@:default("Unknown")
|
||||
|
@ -367,6 +371,8 @@ class SongCharacterData
|
|||
class SongChartData
|
||||
{
|
||||
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
|
||||
public var scrollSpeed:Map<String, Float>;
|
||||
|
|
|
@ -246,7 +246,8 @@ class SongDataUtils
|
|||
|
||||
typedef SongClipboardItems =
|
||||
{
|
||||
?valid:Bool,
|
||||
notes:Array<SongNoteData>,
|
||||
events:Array<SongEventData>
|
||||
@:optional
|
||||
var valid:Bool;
|
||||
var notes:Array<SongNoteData>;
|
||||
var events:Array<SongEventData>;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ class SongMetadata_v2_0_0
|
|||
// ==========
|
||||
// UNMODIFIED VALUES
|
||||
// ==========
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
|
||||
@:default("Unknown")
|
||||
|
|
|
@ -24,13 +24,14 @@ import openfl.utils.Assets;
|
|||
* - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);`
|
||||
* @author MasterEric
|
||||
*/
|
||||
@:nullSafety
|
||||
class HealthIcon extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* The character this icon is representing.
|
||||
* Setting this variable will automatically update the graphic.
|
||||
*/
|
||||
public var characterId(default, set):String;
|
||||
public var characterId(default, set):Null<String>;
|
||||
|
||||
/**
|
||||
* Whether this health icon should automatically update its state based on the character's health.
|
||||
|
@ -123,13 +124,12 @@ class HealthIcon extends FlxSprite
|
|||
initTargetSize();
|
||||
}
|
||||
|
||||
function set_characterId(value:String):String
|
||||
function set_characterId(value:Null<String>):Null<String>
|
||||
{
|
||||
if (value == characterId) return value;
|
||||
|
||||
characterId = value;
|
||||
loadCharacter(characterId);
|
||||
return value;
|
||||
characterId = value ?? Constants.DEFAULT_HEALTH_ICON;
|
||||
return characterId;
|
||||
}
|
||||
|
||||
function set_isPixel(value:Bool):Bool
|
||||
|
@ -137,8 +137,7 @@ class HealthIcon extends FlxSprite
|
|||
if (value == isPixel) return value;
|
||||
|
||||
isPixel = value;
|
||||
loadCharacter(characterId);
|
||||
return value;
|
||||
return isPixel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +155,38 @@ class HealthIcon extends FlxSprite
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the provided CharacterHealthIconData to configure this health icon's appearance.
|
||||
* @param data The data to use to configure this health icon.
|
||||
*/
|
||||
public function configure(data:Null<HealthIconData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
this.characterId = Constants.DEFAULT_HEALTH_ICON;
|
||||
this.isPixel = false;
|
||||
|
||||
loadCharacter(characterId);
|
||||
|
||||
this.size.set(1.0, 1.0);
|
||||
this.offset.x = 0.0;
|
||||
this.offset.y = 0.0;
|
||||
this.flipX = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.characterId = data.id;
|
||||
this.isPixel = data.isPixel ?? false;
|
||||
|
||||
loadCharacter(characterId);
|
||||
|
||||
this.size.set(data.scale ?? 1.0, data.scale ?? 1.0);
|
||||
this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0;
|
||||
this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0;
|
||||
this.flipX = data.flipX ?? false; // Face the OTHER way by default, since that is more common.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Flixel every frame. Includes logic to manage the currently playing animation.
|
||||
*/
|
||||
|
@ -341,12 +372,17 @@ class HealthIcon extends FlxSprite
|
|||
this.animation.add(Losing, [1], 0, false, false);
|
||||
}
|
||||
|
||||
function correctCharacterId(charId:String):String
|
||||
function correctCharacterId(charId:Null<String>):String
|
||||
{
|
||||
if (charId == null)
|
||||
{
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image('icons/icon-$charId')))
|
||||
{
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
return 'face';
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
return charId;
|
||||
|
@ -357,10 +393,11 @@ class HealthIcon extends FlxSprite
|
|||
return Assets.exists(Paths.file('images/icons/icon-$characterId.xml'));
|
||||
}
|
||||
|
||||
function loadCharacter(charId:String):Void
|
||||
function loadCharacter(charId:Null<String>):Void
|
||||
{
|
||||
if (correctCharacterId(charId) != charId)
|
||||
if (charId == null || correctCharacterId(charId) != charId)
|
||||
{
|
||||
// This will recursively trigger loadCharacter to be called again.
|
||||
characterId = correctCharacterId(charId);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -698,7 +698,15 @@ class PlayState extends MusicBeatSubState
|
|||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time = (startTimestamp);
|
||||
|
||||
vocals = currentChart.buildVocals();
|
||||
if (!overrideMusic)
|
||||
{
|
||||
vocals = currentChart.buildVocals();
|
||||
|
||||
if (vocals.members.length == 0)
|
||||
{
|
||||
trace('WARNING: No vocals found for this song.');
|
||||
}
|
||||
}
|
||||
vocals.pause();
|
||||
vocals.time = 0;
|
||||
|
||||
|
|
|
@ -317,12 +317,8 @@ class BaseCharacter extends Bopper
|
|||
trace('[WARN] Player 1 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP1.isPixel = _data.healthIcon?.isPixel ?? false;
|
||||
PlayState.instance.iconP1.characterId = _data.healthIcon.id;
|
||||
PlayState.instance.iconP1.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP1.offset.x = _data.healthIcon.offsets[0];
|
||||
PlayState.instance.iconP1.offset.y = _data.healthIcon.offsets[1];
|
||||
PlayState.instance.iconP1.flipX = !_data.healthIcon.flipX;
|
||||
PlayState.instance.iconP1.configure(_data.healthIcon);
|
||||
PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way.
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -331,12 +327,7 @@ class BaseCharacter extends Bopper
|
|||
trace('[WARN] Player 2 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP2.isPixel = _data.healthIcon?.isPixel ?? false;
|
||||
PlayState.instance.iconP2.characterId = _data.healthIcon.id;
|
||||
PlayState.instance.iconP2.size.set(_data.healthIcon.scale, _data.healthIcon.scale);
|
||||
PlayState.instance.iconP2.offset.x = _data.healthIcon.offsets[0];
|
||||
PlayState.instance.iconP2.offset.y = _data.healthIcon.offsets[1];
|
||||
PlayState.instance.iconP2.flipX = _data.healthIcon.flipX;
|
||||
PlayState.instance.iconP2.configure(_data.healthIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,6 +154,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
{
|
||||
if (metadata == null || metadata.playData == null) continue;
|
||||
|
||||
// If there are no difficulties in the metadata, there's a problem.
|
||||
if (metadata.playData.difficulties.length == 0)
|
||||
{
|
||||
throw 'Song $id has no difficulties listed in metadata!';
|
||||
}
|
||||
|
||||
// There may be more difficulties in the chart file than in the metadata,
|
||||
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
|
||||
// but all the difficulties in the metadata must be in the chart file.
|
||||
|
|
|
@ -421,6 +421,8 @@ typedef RawSaveData =
|
|||
/**
|
||||
* A semantic versioning string for the save data format.
|
||||
*/
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
var version:Version;
|
||||
|
||||
var api:SaveApiData;
|
||||
|
|
|
@ -66,7 +66,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||
state.currentEventSelection = [];
|
||||
}
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -80,7 +80,7 @@ class AddNotesCommand implements ChartEditorCommand
|
|||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -116,7 +116,8 @@ class RemoveNotesCommand implements ChartEditorCommand
|
|||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -133,7 +134,7 @@ class RemoveNotesCommand implements ChartEditorCommand
|
|||
}
|
||||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -254,7 +255,7 @@ class AddEventsCommand implements ChartEditorCommand
|
|||
state.currentEventSelection = events;
|
||||
}
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteLay'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -298,7 +299,8 @@ class RemoveEventsCommand implements ChartEditorCommand
|
|||
{
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||
state.currentEventSelection = [];
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -314,7 +316,7 @@ class RemoveEventsCommand implements ChartEditorCommand
|
|||
state.currentSongChartEventData.push(event);
|
||||
}
|
||||
state.currentEventSelection = events;
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -354,7 +356,7 @@ class RemoveItemsCommand implements ChartEditorCommand
|
|||
state.currentNoteSelection = [];
|
||||
state.currentEventSelection = [];
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-01'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/noteErase'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -378,7 +380,7 @@ class RemoveItemsCommand implements ChartEditorCommand
|
|||
state.currentNoteSelection = notes;
|
||||
state.currentEventSelection = events;
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -805,6 +807,8 @@ class PasteItemsCommand implements ChartEditorCommand
|
|||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, addedNotes);
|
||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, addedEvents);
|
||||
state.currentNoteSelection = [];
|
||||
|
@ -857,6 +861,8 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
note.length = oldLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
|
|
|
@ -404,7 +404,6 @@ class ChartEditorDialogHandler
|
|||
{
|
||||
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.fullPath);
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
|
@ -415,13 +414,12 @@ class ChartEditorDialogHandler
|
|||
});
|
||||
#end
|
||||
|
||||
state.switchToCurrentInstrumental();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load instrumental (${selectedFile.fullPath})');
|
||||
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
|
@ -452,6 +450,7 @@ class ChartEditorDialogHandler
|
|||
});
|
||||
#end
|
||||
|
||||
state.switchToCurrentInstrumental();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
|
@ -570,6 +569,12 @@ class ChartEditorDialogHandler
|
|||
|
||||
var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default');
|
||||
|
||||
newSongMetadata.playData.difficulties = switch (targetVariation)
|
||||
{
|
||||
case 'erect': ['erect', 'nightmare'];
|
||||
default: ['easy', 'normal', 'hard'];
|
||||
};
|
||||
|
||||
var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField);
|
||||
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
|
||||
inputSongName.onChange = function(event:UIEvent) {
|
||||
|
|
|
@ -87,9 +87,8 @@ using Lambda;
|
|||
*
|
||||
* @author MasterEric
|
||||
*/
|
||||
@:nullSafety
|
||||
// Give other classes access to private instance fields
|
||||
// @:nullSafety(Loose) // Enable this while developing, then disable to keep unit tests functional!
|
||||
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorCommand)
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorDropdowns)
|
||||
@:allow(funkin.ui.debug.charting.ChartEditorDialogHandler)
|
||||
|
@ -555,6 +554,9 @@ class ChartEditorState extends HaxeUIState
|
|||
notePreviewDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
|
||||
// Make sure the difficulty we selected is in the list of difficulties.
|
||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||
|
||||
return selectedDifficulty;
|
||||
}
|
||||
|
||||
|
@ -965,13 +967,14 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function get_currentSongChartNoteData():Array<SongNoteData>
|
||||
{
|
||||
var result:Array<SongNoteData> = currentSongChartData.notes.get(selectedDifficulty);
|
||||
var result:Null<Array<SongNoteData>> = currentSongChartData.notes.get(selectedDifficulty);
|
||||
if (result == null)
|
||||
{
|
||||
// Initialize to the default value if not set.
|
||||
result = [];
|
||||
trace('Initializing blank note data for difficulty ' + selectedDifficulty);
|
||||
currentSongChartData.notes.set(selectedDifficulty, result);
|
||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
|
@ -980,6 +983,7 @@ class ChartEditorState extends HaxeUIState
|
|||
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
|
||||
{
|
||||
currentSongChartData.notes.set(selectedDifficulty, value);
|
||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -1391,16 +1395,12 @@ class ChartEditorState extends HaxeUIState
|
|||
healthIconDad = new HealthIcon(currentSongMetadata.playData.characters.opponent);
|
||||
healthIconDad.autoUpdate = false;
|
||||
healthIconDad.size.set(0.5, 0.5);
|
||||
healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
||||
healthIconDad.y = gridTiledSprite.y + 5;
|
||||
add(healthIconDad);
|
||||
healthIconDad.zIndex = 30;
|
||||
|
||||
healthIconBF = new HealthIcon(currentSongMetadata.playData.characters.player);
|
||||
healthIconBF.autoUpdate = false;
|
||||
healthIconBF.size.set(0.5, 0.5);
|
||||
healthIconBF.x = gridTiledSprite.x + gridTiledSprite.width + 15;
|
||||
healthIconBF.y = gridTiledSprite.y + 5;
|
||||
healthIconBF.flipX = true;
|
||||
add(healthIconBF);
|
||||
healthIconBF.zIndex = 30;
|
||||
|
@ -1627,6 +1627,12 @@ class ChartEditorState extends HaxeUIState
|
|||
addUIClickListener('playbarForward', _ -> playbarButtonPressed = 'playbarForward');
|
||||
addUIClickListener('playbarEnd', _ -> playbarButtonPressed = 'playbarEnd');
|
||||
|
||||
// Cycle note snap quant.
|
||||
addUIClickListener('playbarNoteSnap', function(_) {
|
||||
noteSnapQuantIndex++;
|
||||
if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0;
|
||||
});
|
||||
|
||||
// Add functionality to the menu items.
|
||||
|
||||
addUIClickListener('menubarItemNewChart', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||
|
@ -2138,11 +2144,18 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
var dragLengthCurrent:Float = 0;
|
||||
var stretchySounds:Bool = false;
|
||||
|
||||
/**
|
||||
* Handle display of the mouse cursor.
|
||||
*/
|
||||
function handleCursor():Void
|
||||
{
|
||||
// Mouse sounds
|
||||
if (FlxG.mouse.justPressed) FlxG.sound.play(Paths.sound("chartingSounds/ClickDown"));
|
||||
if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp"));
|
||||
|
||||
// Note: If a menu is open in HaxeUI, don't handle cursor behavior.
|
||||
var shouldHandleCursor:Bool = !isCursorOverHaxeUI || (selectionBoxStartPos != null);
|
||||
var eventColumn:Int = (STRUMLINE_SIZE * 2 + 1) - 1;
|
||||
|
@ -2487,25 +2500,37 @@ class ChartEditorState extends HaxeUIState
|
|||
var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs;
|
||||
var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE;
|
||||
|
||||
if (dragLengthSteps > 0)
|
||||
if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null)
|
||||
{
|
||||
gridGhostHoldNote.visible = true;
|
||||
gridGhostHoldNote.noteData = gridGhostNote.noteData;
|
||||
gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
|
||||
if (dragLengthSteps > 0)
|
||||
{
|
||||
if (dragLengthCurrent != dragLengthSteps)
|
||||
{
|
||||
stretchySounds = !stretchySounds;
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/stretch' + (stretchySounds ? '1' : '2') + '_UI'));
|
||||
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels);
|
||||
dragLengthCurrent = dragLengthSteps;
|
||||
}
|
||||
|
||||
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridGhostHoldNote.visible = false;
|
||||
gridGhostHoldNote.visible = true;
|
||||
gridGhostHoldNote.noteData = gridGhostNote.noteData;
|
||||
gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
|
||||
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels);
|
||||
|
||||
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridGhostHoldNote.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.mouse.justReleased)
|
||||
{
|
||||
if (dragLengthSteps > 0)
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/stretchSNAP_UI'));
|
||||
// Apply the new length.
|
||||
performCommand(new ExtendNoteLengthCommand(currentPlaceNoteData, dragLengthMs));
|
||||
}
|
||||
|
@ -2654,7 +2679,7 @@ class ChartEditorState extends HaxeUIState
|
|||
if (cursorColumn == eventColumn)
|
||||
{
|
||||
if (gridGhostNote != null) gridGhostNote.visible = false;
|
||||
gridGhostHoldNote.visible = false;
|
||||
if (gridGhostHoldNote != null) gridGhostHoldNote.visible = false;
|
||||
|
||||
if (gridGhostEvent == null) throw "ERROR: Tried to handle cursor, but gridGhostEvent is null! Check ChartEditorState.buildGrid()";
|
||||
|
||||
|
@ -2714,11 +2739,11 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
else
|
||||
{
|
||||
if (FlxG.mouse.overlaps(notePreview))
|
||||
if (notePreview != null && FlxG.mouse.overlaps(notePreview))
|
||||
{
|
||||
targetCursorMode = Pointer;
|
||||
}
|
||||
else if (FlxG.mouse.overlaps(gridPlayheadScrollArea))
|
||||
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
|
||||
{
|
||||
targetCursorMode = Pointer;
|
||||
}
|
||||
|
@ -3030,18 +3055,35 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
if (healthIconsDirty)
|
||||
{
|
||||
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
|
||||
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
|
||||
var charDataBF = CharacterDataParser.fetchCharacterData(currentSongMetadata.playData.characters.player);
|
||||
var charDataDad = CharacterDataParser.fetchCharacterData(currentSongMetadata.playData.characters.opponent);
|
||||
if (healthIconBF != null)
|
||||
{
|
||||
healthIconBF.configure(charDataBF?.healthIcon);
|
||||
healthIconBF.size *= 0.5; // Make the icon smaller in Chart Editor.
|
||||
healthIconBF.flipX = !healthIconBF.flipX; // BF faces the other way.
|
||||
}
|
||||
if (healthIconDad != null)
|
||||
{
|
||||
healthIconDad.configure(charDataDad?.healthIcon);
|
||||
healthIconDad.size *= 0.5; // Make the icon smaller in Chart Editor.
|
||||
}
|
||||
healthIconsDirty = false;
|
||||
}
|
||||
|
||||
// Right align the BF health icon.
|
||||
// Right align, and visibly center, the BF health icon.
|
||||
if (healthIconBF != null)
|
||||
{
|
||||
// Base X position to the right of the grid.
|
||||
var baseHealthIconXPos:Float = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 15);
|
||||
// Will be 0 when not bopping. When bopping, will increase to push the icon left.
|
||||
var healthIconOffset:Float = healthIconBF.width - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
||||
healthIconBF.x = baseHealthIconXPos - healthIconOffset;
|
||||
healthIconBF.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 45 - (healthIconBF.width / 2));
|
||||
healthIconBF.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconBF.height / 2));
|
||||
}
|
||||
|
||||
// Visibly center the Dad health icon.
|
||||
if (healthIconDad != null)
|
||||
{
|
||||
healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2));
|
||||
healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3668,49 +3710,41 @@ class ChartEditorState extends HaxeUIState
|
|||
var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown);
|
||||
var stageId:String = currentSongMetadata.playData.stage;
|
||||
var stageData:Null<StageData> = StageDataParser.parseStageData(stageId);
|
||||
if (stageData != null)
|
||||
if (inputStage != null)
|
||||
{
|
||||
inputStage.value = {id: stageId, text: stageData.name};
|
||||
}
|
||||
else
|
||||
{
|
||||
inputStage.value = {id: "mainStage", text: "Main Stage"};
|
||||
inputStage.value = (stageData != null) ?
|
||||
{id: stageId, text: stageData.name} :
|
||||
{id: "mainStage", text: "Main Stage"};
|
||||
}
|
||||
|
||||
var inputCharacterPlayer:Null<DropDown> = toolbox.findComponent('inputCharacterPlayer', DropDown);
|
||||
var charIdPlayer:String = currentSongMetadata.playData.characters.player;
|
||||
var charDataPlayer:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdPlayer);
|
||||
if (charDataPlayer != null)
|
||||
if (inputCharacterPlayer != null)
|
||||
{
|
||||
inputCharacterPlayer.value = {id: charIdPlayer, text: charDataPlayer.name};
|
||||
}
|
||||
else
|
||||
{
|
||||
inputCharacterPlayer.value = {id: "bf", text: "Boyfriend"};
|
||||
inputCharacterPlayer.value = (charDataPlayer != null) ?
|
||||
{id: charIdPlayer, text: charDataPlayer.name} :
|
||||
{id: "bf", text: "Boyfriend"};
|
||||
}
|
||||
|
||||
var inputCharacterOpponent:Null<DropDown> = toolbox.findComponent('inputCharacterOpponent', DropDown);
|
||||
var charIdOpponent:String = currentSongMetadata.playData.characters.opponent;
|
||||
var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdOpponent);
|
||||
if (charDataOpponent != null)
|
||||
if (inputCharacterOpponent != null)
|
||||
{
|
||||
inputCharacterOpponent.value = {id: charIdOpponent, text: charDataOpponent.name};
|
||||
}
|
||||
else
|
||||
{
|
||||
inputCharacterOpponent.value = {id: "dad", text: "Dad"};
|
||||
inputCharacterOpponent.value = (charDataOpponent != null) ?
|
||||
{id: charIdOpponent, text: charDataOpponent.name} :
|
||||
{id: "dad", text: "Dad"};
|
||||
}
|
||||
|
||||
var inputCharacterGirlfriend:Null<DropDown> = toolbox.findComponent('inputCharacterGirlfriend', DropDown);
|
||||
var charIdGirlfriend:String = currentSongMetadata.playData.characters.girlfriend;
|
||||
var charDataGirlfriend:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdGirlfriend);
|
||||
if (charDataGirlfriend != null)
|
||||
if (inputCharacterGirlfriend != null)
|
||||
{
|
||||
inputCharacterGirlfriend.value = {id: charIdGirlfriend, text: charDataGirlfriend.name};
|
||||
}
|
||||
else
|
||||
{
|
||||
inputCharacterGirlfriend.value = {id: "none", text: "None"};
|
||||
inputCharacterGirlfriend.value = (charDataGirlfriend != null) ?
|
||||
{id: charIdGirlfriend, text: charDataGirlfriend.name} :
|
||||
{id: "none", text: "None"};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3897,9 +3931,9 @@ class ChartEditorState extends HaxeUIState
|
|||
switch (noteData.getStrumlineIndex())
|
||||
{
|
||||
case 0: // Player
|
||||
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/playerHitsound'));
|
||||
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/hitNotePlayer'));
|
||||
case 1: // Opponent
|
||||
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/opponentHitsound'));
|
||||
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/hitNoteOpponent'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4016,7 +4050,7 @@ class ChartEditorState extends HaxeUIState
|
|||
this.scrollPositionInPixels = value;
|
||||
|
||||
// Move the grid sprite to the correct position.
|
||||
if (gridTiledSprite != null)
|
||||
if (gridTiledSprite != null && gridPlayheadScrollArea != null)
|
||||
{
|
||||
if (isViewDownscroll)
|
||||
{
|
||||
|
@ -4076,7 +4110,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
|
||||
subStateClosed.add(fixCamera);
|
||||
subStateClosed.add(updateConductor);
|
||||
subStateClosed.add(resetConductorAfterTest);
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
|
@ -4109,10 +4143,9 @@ class ChartEditorState extends HaxeUIState
|
|||
add(this.component);
|
||||
}
|
||||
|
||||
function updateConductor(_:FlxSubState = null):Void
|
||||
function resetConductorAfterTest(_:FlxSubState = null):Void
|
||||
{
|
||||
var targetPos = scrollPositionInMs;
|
||||
Conductor.update(targetPos);
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
public function postLoadInstrumental():Void
|
||||
|
@ -4166,12 +4199,14 @@ class ChartEditorState extends HaxeUIState
|
|||
function moveSongToScrollPosition():Void
|
||||
{
|
||||
// Update the songPosition in the audio tracks.
|
||||
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
||||
// Update the songPosition in the Conductor.
|
||||
Conductor.update(audioInstTrack.time);
|
||||
}
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
// Update the songPosition in the Conductor.
|
||||
Conductor.update(audioInstTrack.time);
|
||||
|
||||
// We need to update the note sprites because we changed the scroll position.
|
||||
noteDisplayDirty = true;
|
||||
}
|
||||
|
@ -4275,7 +4310,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function playMetronomeTick(high:Bool = false):Void
|
||||
{
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('pianoStuff/piano-${high ? '001' : '008'}'));
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/metronome${high ? '1' : '2'}'));
|
||||
}
|
||||
|
||||
function isNoteSelected(note:Null<SongNoteData>):Bool
|
||||
|
|
|
@ -72,6 +72,8 @@ class ChartEditorToolboxHandler
|
|||
{
|
||||
toolbox.showDialog(false);
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/openWindow'));
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||
|
@ -109,6 +111,8 @@ class ChartEditorToolboxHandler
|
|||
{
|
||||
toolbox.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
ChartEditorAudioHandler.playSound(Paths.sound('chartingSounds/exitWindow'));
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
|
||||
|
|
30
source/funkin/ui/haxeui/components/FunkinClickLabel.hx
Normal file
30
source/funkin/ui/haxeui/components/FunkinClickLabel.hx
Normal file
|
@ -0,0 +1,30 @@
|
|||
package funkin.ui.haxeui.components;
|
||||
|
||||
import haxe.ui.components.Label;
|
||||
import funkin.input.Cursor;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
|
||||
/**
|
||||
* A HaxeUI label which:
|
||||
* - Changes the current cursor when hovered over (assume an onClick handler will be added!).
|
||||
*/
|
||||
class FunkinClickLabel extends Label
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
this.onMouseOver = handleMouseOver;
|
||||
this.onMouseOut = handleMouseOut;
|
||||
}
|
||||
|
||||
private function handleMouseOver(event:MouseEvent)
|
||||
{
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
private function handleMouseOut(event:MouseEvent)
|
||||
{
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
}
|
|
@ -131,6 +131,11 @@ class Constants
|
|||
*/
|
||||
public static final DEFAULT_CHARACTER:String = 'bf';
|
||||
|
||||
/**
|
||||
* Default player character for health icons.
|
||||
*/
|
||||
public static final DEFAULT_HEALTH_ICON:String = 'face';
|
||||
|
||||
/**
|
||||
* Default stage for charts.
|
||||
*/
|
||||
|
|
|
@ -60,6 +60,19 @@ class ArrayTools
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Push an element to the array if it is not already present.
|
||||
* @param input The array to push to
|
||||
* @param element The element to push
|
||||
* @return Whether the element was pushed
|
||||
*/
|
||||
public static function pushUnique<T>(input:Array<T>, element:T):Bool
|
||||
{
|
||||
if (input.contains(element)) return false;
|
||||
input.push(element);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all elements from the array, without creating a new array.
|
||||
* @param array The array to clear.
|
||||
|
|
Loading…
Reference in a new issue