mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-01-27 07:17:20 +00:00
Merge branch 'rewrite/master' into bugfix/chart-editor-positioning-fixes
This commit is contained in:
commit
99b7a8a75d
|
@ -111,7 +111,7 @@
|
|||
<haxelib name="tink_json" /> <!-- JSON parsing (DEPRECATED) -->
|
||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
||||
|
||||
<haxelib name="hmm" if="debug" /> <!-- Read library version data at compile time -->
|
||||
<haxelib name="hmm" /> <!-- Read library version data at compile time so it can be baked into logs -->
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support -->
|
||||
|
||||
<!--Disable the Flixel core focus lost screen-->
|
||||
|
@ -131,8 +131,8 @@
|
|||
<haxedef name="message.reporting" value="pretty" />
|
||||
|
||||
<!-- _________________________________ Custom _______________________________ -->
|
||||
<!-- Disable trace() calls in release builds to bump up performance. -->
|
||||
<haxeflag name="--no-traces" unless="debug" />
|
||||
<!-- Disable trace() calls in release builds to bump up performance.
|
||||
<haxeflag name="- -no-traces" unless="debug" />-->
|
||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
||||
<haxeflag name="-dce no" />
|
||||
<!-- Ensure all Funkin' classes are available at runtime. -->
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit a3e2277e6f12208f9a976b80883db67c54a2a897
|
||||
Subproject commit f1e42601b6ea2026c6e2f4627c5738bfb8b7b524
|
6
hmm.json
6
hmm.json
|
@ -54,14 +54,14 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "5086e59e7551d775ed4d1fb0188e31de22d1312b",
|
||||
"ref": "2561076c5abeee0a60f3a2a65a8ecb7832a6a62a",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2b9cff727999b53ed292b1675ac1c9089ac77600",
|
||||
"ref": "9c8ab039524086f5a8c8f35b9fb14538b5bfba5d",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
@ -107,7 +107,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "737b86f121cdc90358d59e2e527934f267c94a2c",
|
||||
"ref": "fff39ba6fc64969cd51987ef7491d9345043dc5d",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -93,7 +93,7 @@ class SongMetadata implements ICloneable<SongMetadata>
|
|||
result.version = this.version;
|
||||
result.timeFormat = this.timeFormat;
|
||||
result.divisions = this.divisions;
|
||||
result.offsets = this.offsets.clone();
|
||||
result.offsets = this.offsets != null ? this.offsets.clone() : new SongOffsets(); // if no song offsets found (aka null), so just create new ones
|
||||
result.timeChanges = this.timeChanges.deepClone();
|
||||
result.looped = this.looped;
|
||||
result.playData = this.playData.clone();
|
||||
|
@ -826,7 +826,13 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
@:alias("l")
|
||||
@:default(0)
|
||||
@:optional
|
||||
public var length:Float;
|
||||
public var length(default, set):Float;
|
||||
|
||||
function set_length(value:Float):Float
|
||||
{
|
||||
_stepLength = null;
|
||||
return length = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of the note.
|
||||
|
@ -883,6 +889,11 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return _stepTime = Conductor.instance.getTimeInSteps(this.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* The length of the note, if applicable, in steps.
|
||||
* Calculated from the length and the BPM.
|
||||
* Cached for performance. Set to `null` to recalculate.
|
||||
*/
|
||||
@:jignored
|
||||
var _stepLength:Null<Float> = null;
|
||||
|
||||
|
@ -907,9 +918,14 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
}
|
||||
else
|
||||
{
|
||||
var lengthMs:Float = Conductor.instance.getStepTimeInMs(value) - this.time;
|
||||
var endStep:Float = getStepTime() + value;
|
||||
var endMs:Float = Conductor.instance.getStepTimeInMs(endStep);
|
||||
var lengthMs:Float = endMs - this.time;
|
||||
|
||||
this.length = lengthMs;
|
||||
}
|
||||
|
||||
// Recalculate the step length next time it's requested.
|
||||
_stepLength = null;
|
||||
}
|
||||
|
||||
|
@ -980,6 +996,10 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
@:op(A == B)
|
||||
public function op_equals(other:SongNoteData):Bool
|
||||
{
|
||||
// Handle the case where one value is null.
|
||||
if (this == null) return other == null;
|
||||
if (other == null) return false;
|
||||
|
||||
if (this.kind == '')
|
||||
{
|
||||
if (other.kind != '' && other.kind != 'normal') return false;
|
||||
|
@ -995,6 +1015,10 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
@:op(A != B)
|
||||
public function op_notEquals(other:SongNoteData):Bool
|
||||
{
|
||||
// Handle the case where one value is null.
|
||||
if (this == null) return other == null;
|
||||
if (other == null) return false;
|
||||
|
||||
if (this.kind == '')
|
||||
{
|
||||
if (other.kind != '' && other.kind != 'normal') return true;
|
||||
|
@ -1010,24 +1034,32 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
|||
@:op(A > B)
|
||||
public function op_greaterThan(other:SongNoteData):Bool
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return this.time > other.time;
|
||||
}
|
||||
|
||||
@:op(A < B)
|
||||
public function op_lessThan(other:SongNoteData):Bool
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return this.time < other.time;
|
||||
}
|
||||
|
||||
@:op(A >= B)
|
||||
public function op_greaterThanOrEquals(other:SongNoteData):Bool
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return this.time >= other.time;
|
||||
}
|
||||
|
||||
@:op(A <= B)
|
||||
public function op_lessThanOrEquals(other:SongNoteData):Bool
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
return this.time <= other.time;
|
||||
}
|
||||
|
||||
|
|
|
@ -2270,8 +2270,10 @@ class PlayState extends MusicBeatSubState
|
|||
vocals.playerVolume = 1;
|
||||
|
||||
// Calculate the input latency (do this as late as possible).
|
||||
var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0;
|
||||
trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!');
|
||||
// trace('Compare: ${PreciseInputManager.getCurrentTimestamp()} - ${input.timestamp}');
|
||||
var inputLatencyNs:Int64 = PreciseInputManager.getCurrentTimestamp() - input.timestamp;
|
||||
var inputLatencyMs:Float = inputLatencyNs.toFloat() / Constants.NS_PER_MS;
|
||||
// trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!');
|
||||
|
||||
// Get the offset and compensate for input latency.
|
||||
// Round inward (trim remainder) for consistency.
|
||||
|
|
|
@ -82,6 +82,9 @@ class SustainTrail extends FlxSprite
|
|||
|
||||
public var isPixel:Bool;
|
||||
|
||||
var graphicWidth:Float = 0;
|
||||
var graphicHeight:Float = 0;
|
||||
|
||||
/**
|
||||
* Normally you would take strumTime:Float, noteData:Int, sustainLength:Float, parentNote:Note (?)
|
||||
* @param NoteData
|
||||
|
@ -110,8 +113,8 @@ class SustainTrail extends FlxSprite
|
|||
zoom *= 0.7;
|
||||
|
||||
// CALCULATE SIZE
|
||||
width = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
height = sustainHeight(sustainLength, getScrollSpeed());
|
||||
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
graphicHeight = sustainHeight(sustainLength, getScrollSpeed());
|
||||
// instead of scrollSpeed, PlayState.SONG.speed
|
||||
|
||||
flipY = Preferences.downscroll;
|
||||
|
@ -148,12 +151,21 @@ class SustainTrail extends FlxSprite
|
|||
|
||||
if (sustainLength == s) return s;
|
||||
|
||||
height = sustainHeight(s, getScrollSpeed());
|
||||
graphicHeight = sustainHeight(s, getScrollSpeed());
|
||||
this.sustainLength = s;
|
||||
updateClipping();
|
||||
updateHitbox();
|
||||
return this.sustainLength;
|
||||
}
|
||||
|
||||
public override function updateHitbox():Void
|
||||
{
|
||||
width = graphicWidth;
|
||||
height = graphicHeight;
|
||||
offset.set(0, 0);
|
||||
origin.set(width * 0.5, height * 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up new vertex and UV data to clip the trail.
|
||||
* If flipY is true, top and bottom bounds swap places.
|
||||
|
@ -161,7 +173,7 @@ class SustainTrail extends FlxSprite
|
|||
*/
|
||||
public function updateClipping(songTime:Float = 0):Void
|
||||
{
|
||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, height);
|
||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, graphicHeight);
|
||||
if (clipHeight <= 0.1)
|
||||
{
|
||||
visible = false;
|
||||
|
@ -178,10 +190,10 @@ class SustainTrail extends FlxSprite
|
|||
// ===HOLD VERTICES==
|
||||
// Top left
|
||||
vertices[0 * 2] = 0.0; // Inline with left side
|
||||
vertices[0 * 2 + 1] = flipY ? clipHeight : height - clipHeight;
|
||||
vertices[0 * 2 + 1] = flipY ? clipHeight : graphicHeight - clipHeight;
|
||||
|
||||
// Top right
|
||||
vertices[1 * 2] = width;
|
||||
vertices[1 * 2] = graphicWidth;
|
||||
vertices[1 * 2 + 1] = vertices[0 * 2 + 1]; // Inline with top left vertex
|
||||
|
||||
// Bottom left
|
||||
|
@ -197,7 +209,7 @@ class SustainTrail extends FlxSprite
|
|||
}
|
||||
|
||||
// Bottom right
|
||||
vertices[3 * 2] = width;
|
||||
vertices[3 * 2] = graphicWidth;
|
||||
vertices[3 * 2 + 1] = vertices[2 * 2 + 1]; // Inline with bottom left vertex
|
||||
|
||||
// ===HOLD UVs===
|
||||
|
@ -233,7 +245,7 @@ class SustainTrail extends FlxSprite
|
|||
|
||||
// Bottom left
|
||||
vertices[6 * 2] = vertices[2 * 2]; // Inline with left side
|
||||
vertices[6 * 2 + 1] = flipY ? (graphic.height * (-bottomClip + endOffset) * zoom) : (height + graphic.height * (bottomClip - endOffset) * zoom);
|
||||
vertices[6 * 2 + 1] = flipY ? (graphic.height * (-bottomClip + endOffset) * zoom) : (graphicHeight + graphic.height * (bottomClip - endOffset) * zoom);
|
||||
|
||||
// Bottom right
|
||||
vertices[7 * 2] = vertices[3 * 2]; // Inline with right side
|
||||
|
@ -277,6 +289,10 @@ class SustainTrail extends FlxSprite
|
|||
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||
camera.drawTriangles(processedGraphic, vertices, indices, uvtData, null, _point, blend, true, antialiasing);
|
||||
}
|
||||
|
||||
#if FLX_DEBUG
|
||||
if (FlxG.debugger.drawDebug) drawDebug();
|
||||
#end
|
||||
}
|
||||
|
||||
public override function kill():Void
|
||||
|
|
|
@ -176,6 +176,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
difficulty.generatedBy = metadata.generatedBy;
|
||||
difficulty.offsets = metadata.offsets;
|
||||
|
||||
difficulty.difficultyRating = metadata.playData.ratings.get(diffId) ?? 0;
|
||||
difficulty.album = metadata.playData.album;
|
||||
|
||||
difficulty.stage = metadata.playData.stage;
|
||||
difficulty.noteStyle = metadata.playData.noteStyle;
|
||||
|
||||
|
@ -405,6 +408,9 @@ class SongDifficulty
|
|||
|
||||
public var scrollSpeed:Float = Constants.DEFAULT_SCROLLSPEED;
|
||||
|
||||
public var difficultyRating:Int = 0;
|
||||
public var album:Null<String> = null;
|
||||
|
||||
public function new(song:Song, diffId:String, variation:String)
|
||||
{
|
||||
this.song = song;
|
||||
|
|
|
@ -274,4 +274,5 @@ enum abstract AtlasFont(String) from String to String
|
|||
{
|
||||
var DEFAULT = "default";
|
||||
var BOLD = "bold";
|
||||
var FREEPLAY_CLEAR = "freeplay-clear";
|
||||
}
|
||||
|
|
|
@ -750,7 +750,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
* `null` if the user isn't currently placing a note.
|
||||
* As the user drags, we will update this note's sustain length, and finalize the note when they release.
|
||||
*/
|
||||
var currentPlaceNoteData:Null<SongNoteData> = null;
|
||||
var currentPlaceNoteData(default, set):Null<SongNoteData> = null;
|
||||
|
||||
function set_currentPlaceNoteData(value:Null<SongNoteData>):Null<SongNoteData>
|
||||
{
|
||||
noteDisplayDirty = true;
|
||||
|
||||
return currentPlaceNoteData = value;
|
||||
}
|
||||
|
||||
// Note Movement
|
||||
|
||||
|
@ -2321,7 +2328,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
bounds.height = MIN_HEIGHT;
|
||||
}
|
||||
|
||||
trace('Note preview viewport bounds: ' + bounds.toString());
|
||||
// trace('Note preview viewport bounds: ' + bounds.toString());
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
@ -3172,8 +3179,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
if (holdNoteSprite == null || holdNoteSprite.noteData == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue;
|
||||
|
||||
if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT))
|
||||
if (holdNoteSprite.noteData == currentPlaceNoteData)
|
||||
{
|
||||
// This hold note is for the note we are currently dragging.
|
||||
// It will be displayed by gridGhostHoldNoteSprite instead.
|
||||
holdNoteSprite.kill();
|
||||
}
|
||||
else if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
|
||||
{
|
||||
// This hold note is off-screen.
|
||||
// Kill the hold note sprite and recycle it.
|
||||
holdNoteSprite.kill();
|
||||
}
|
||||
else if (!currentSongChartNoteData.fastContains(holdNoteSprite.noteData) || holdNoteSprite.noteData.length == 0)
|
||||
|
@ -3191,7 +3206,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
else
|
||||
{
|
||||
displayedHoldNoteData.push(holdNoteSprite.noteData);
|
||||
// Update the event sprite's position.
|
||||
// Update the event sprite's height and position.
|
||||
// var holdNoteHeight = holdNoteSprite.noteData.getStepLength() * GRID_SIZE;
|
||||
// holdNoteSprite.setHeightDirectly(holdNoteHeight);
|
||||
holdNoteSprite.updateHoldNotePosition(renderedNotes);
|
||||
}
|
||||
}
|
||||
|
@ -3269,7 +3286,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
noteSprite.updateNotePosition(renderedNotes);
|
||||
|
||||
// Add hold notes that are now visible (and not already displayed).
|
||||
if (noteSprite.noteData != null && noteSprite.noteData.length > 0 && displayedHoldNoteData.indexOf(noteSprite.noteData) == -1)
|
||||
if (noteSprite.noteData != null
|
||||
&& noteSprite.noteData.length > 0
|
||||
&& displayedHoldNoteData.indexOf(noteSprite.noteData) == -1
|
||||
&& noteSprite.noteData != currentPlaceNoteData)
|
||||
{
|
||||
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this));
|
||||
// trace('Creating new HoldNote... (${renderedHoldNotes.members.length})');
|
||||
|
@ -3282,6 +3302,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||
|
||||
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||
|
||||
trace(holdNoteSprite.x + ', ' + holdNoteSprite.y + ', ' + holdNoteSprite.width + ', ' + holdNoteSprite.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3320,6 +3342,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// Is the note a hold note?
|
||||
if (noteData == null || noteData.length <= 0) continue;
|
||||
|
||||
// Is the note the one we are dragging? If so, ghostHoldNoteSprite will handle it.
|
||||
if (noteData == currentPlaceNoteData) continue;
|
||||
|
||||
// Is the hold note rendered already?
|
||||
if (displayedHoldNoteData.indexOf(noteData) != -1) continue;
|
||||
|
||||
|
@ -3409,7 +3434,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
selectionSquare.x = noteSprite.x;
|
||||
selectionSquare.y = noteSprite.y;
|
||||
selectionSquare.width = GRID_SIZE;
|
||||
selectionSquare.height = GRID_SIZE;
|
||||
|
||||
var stepLength = noteSprite.noteData.getStepLength();
|
||||
selectionSquare.height = (stepLength <= 0) ? GRID_SIZE : ((stepLength + 1) * GRID_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3688,6 +3715,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
var overlapsGrid:Bool = FlxG.mouse.overlaps(gridTiledSprite);
|
||||
|
||||
var overlapsRenderedNotes:Bool = FlxG.mouse.overlaps(renderedNotes);
|
||||
var overlapsRenderedHoldNotes:Bool = FlxG.mouse.overlaps(renderedHoldNotes);
|
||||
var overlapsRenderedEvents:Bool = FlxG.mouse.overlaps(renderedEvents);
|
||||
|
||||
// Cursor position relative to the grid.
|
||||
var cursorX:Float = FlxG.mouse.screenX - gridTiledSprite.x;
|
||||
var cursorY:Float = FlxG.mouse.screenY - gridTiledSprite.y;
|
||||
|
@ -3929,12 +3960,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return event.alive && FlxG.mouse.overlaps(event);
|
||||
});
|
||||
}
|
||||
var highlightedHoldNote:Null<ChartEditorHoldNoteSprite> = null;
|
||||
if (highlightedNote == null && highlightedEvent == null)
|
||||
{
|
||||
highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool {
|
||||
return holdNote.alive && FlxG.mouse.overlaps(holdNote);
|
||||
});
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
if (highlightedNote != null && highlightedNote.noteData != null)
|
||||
{
|
||||
// TODO: Handle the case of clicking on a sustain piece.
|
||||
// Control click to select/deselect an individual note.
|
||||
if (isNoteSelected(highlightedNote.noteData))
|
||||
{
|
||||
|
@ -3957,6 +3994,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
performCommand(new SelectItemsCommand([], [highlightedEvent.eventData]));
|
||||
}
|
||||
}
|
||||
else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null)
|
||||
{
|
||||
// Control click to select/deselect an individual note.
|
||||
if (isNoteSelected(highlightedNote.noteData))
|
||||
{
|
||||
performCommand(new DeselectItemsCommand([highlightedHoldNote.noteData], []));
|
||||
}
|
||||
else
|
||||
{
|
||||
performCommand(new SelectItemsCommand([highlightedHoldNote.noteData], []));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing if you control-clicked on an empty space.
|
||||
|
@ -3974,6 +4023,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// Click an event to select it.
|
||||
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData]));
|
||||
}
|
||||
else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null)
|
||||
{
|
||||
// Click a hold note to select it.
|
||||
performCommand(new SetItemSelectionCommand([highlightedHoldNote.noteData], []));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Click on an empty space to deselect everything.
|
||||
|
@ -4126,7 +4180,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var dragLengthMs:Float = dragLengthSteps * Conductor.instance.stepLengthMs;
|
||||
var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE;
|
||||
|
||||
if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null)
|
||||
if (gridGhostHoldNote != null)
|
||||
{
|
||||
if (dragLengthSteps > 0)
|
||||
{
|
||||
|
@ -4139,8 +4193,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
|
||||
gridGhostHoldNote.visible = true;
|
||||
gridGhostHoldNote.noteData = gridGhostNote.noteData;
|
||||
gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
|
||||
gridGhostHoldNote.noteData = currentPlaceNoteData;
|
||||
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
|
||||
|
||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
|
||||
|
||||
|
@ -4161,6 +4215,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
// Apply the new length.
|
||||
performCommand(new ExtendNoteLengthCommand(currentPlaceNoteData, dragLengthMs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply the new (zero) length if we are changing the length.
|
||||
if (currentPlaceNoteData.length > 0)
|
||||
{
|
||||
this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI'));
|
||||
performCommand(new ExtendNoteLengthCommand(currentPlaceNoteData, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Finished dragging. Release the note.
|
||||
currentPlaceNoteData = null;
|
||||
|
@ -4193,6 +4256,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return event.alive && FlxG.mouse.overlaps(event);
|
||||
});
|
||||
}
|
||||
var highlightedHoldNote:Null<ChartEditorHoldNoteSprite> = null;
|
||||
if (highlightedNote == null && highlightedEvent == null)
|
||||
{
|
||||
highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool {
|
||||
// If holdNote.alive is false, the holdNote is dead and awaiting recycling.
|
||||
return holdNote.alive && FlxG.mouse.overlaps(holdNote);
|
||||
});
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
|
@ -4219,6 +4290,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
performCommand(new SelectItemsCommand([], [highlightedEvent.eventData]));
|
||||
}
|
||||
}
|
||||
else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null)
|
||||
{
|
||||
if (isNoteSelected(highlightedNote.noteData))
|
||||
{
|
||||
performCommand(new DeselectItemsCommand([highlightedHoldNote.noteData], []));
|
||||
}
|
||||
else
|
||||
{
|
||||
performCommand(new SelectItemsCommand([highlightedHoldNote.noteData], []));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing when control clicking nothing.
|
||||
|
@ -4252,6 +4334,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData]));
|
||||
}
|
||||
}
|
||||
else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null)
|
||||
{
|
||||
// Clicked a hold note, start dragging TO EXTEND NOTE LENGTH.
|
||||
currentPlaceNoteData = highlightedHoldNote.noteData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Click a blank space to place a note and select it.
|
||||
|
@ -4301,6 +4388,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
return event.alive && FlxG.mouse.overlaps(event);
|
||||
});
|
||||
}
|
||||
var highlightedHoldNote:Null<ChartEditorHoldNoteSprite> = null;
|
||||
if (highlightedNote == null && highlightedEvent == null)
|
||||
{
|
||||
highlightedHoldNote = renderedHoldNotes.members.find(function(holdNote:ChartEditorHoldNoteSprite):Bool {
|
||||
// If holdNote.alive is false, the holdNote is dead and awaiting recycling.
|
||||
return holdNote.alive && FlxG.mouse.overlaps(holdNote);
|
||||
});
|
||||
}
|
||||
|
||||
if (highlightedNote != null && highlightedNote.noteData != null)
|
||||
{
|
||||
|
@ -4352,13 +4447,40 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
performCommand(new RemoveEventsCommand([highlightedEvent.eventData]));
|
||||
}
|
||||
}
|
||||
else if (highlightedHoldNote != null && highlightedHoldNote.noteData != null)
|
||||
{
|
||||
if (FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
// Shift + Right click opens the context menu.
|
||||
// If we are clicking a large selection, open the Selection context menu, otherwise open the Note context menu.
|
||||
var isHighlightedNoteSelected:Bool = isNoteSelected(highlightedHoldNote.noteData);
|
||||
var useSingleNoteContextMenu:Bool = (!isHighlightedNoteSelected)
|
||||
|| (isHighlightedNoteSelected && currentNoteSelection.length == 1);
|
||||
// Show the context menu connected to the note.
|
||||
if (useSingleNoteContextMenu)
|
||||
{
|
||||
this.openHoldNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedHoldNote.noteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Right click removes hold from the note.
|
||||
this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI'));
|
||||
performCommand(new ExtendNoteLengthCommand(highlightedHoldNote.noteData, 0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Right clicked on nothing.
|
||||
}
|
||||
}
|
||||
|
||||
var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null;
|
||||
var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null || overlapsRenderedNotes || overlapsRenderedHoldNotes
|
||||
|| overlapsRenderedEvents;
|
||||
// Handle grid cursor.
|
||||
if (!isCursorOverHaxeUI && overlapsGrid && !isOrWillSelect && !overlapsSelectionBorder && !gridPlayheadScrollAreaPressed)
|
||||
{
|
||||
|
@ -4449,6 +4571,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
{
|
||||
targetCursorMode = Crosshair;
|
||||
}
|
||||
else if (overlapsRenderedNotes)
|
||||
{
|
||||
targetCursorMode = Pointer;
|
||||
}
|
||||
else if (overlapsRenderedHoldNotes)
|
||||
{
|
||||
targetCursorMode = Pointer;
|
||||
}
|
||||
else if (overlapsRenderedEvents)
|
||||
{
|
||||
targetCursorMode = Pointer;
|
||||
}
|
||||
else if (overlapsGrid)
|
||||
{
|
||||
targetCursorMode = Cell;
|
||||
|
@ -5177,7 +5311,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
throw "ERROR: Tried to build selection square, but selectionSquareBitmap is null! Check ChartEditorThemeHandler.updateSelectionSquare()";
|
||||
|
||||
// FlxG.bitmapLog.add(selectionSquareBitmap, "selectionSquareBitmap");
|
||||
var result = new ChartEditorSelectionSquareSprite();
|
||||
var result = new ChartEditorSelectionSquareSprite(this);
|
||||
result.loadGraphic(selectionSquareBitmap);
|
||||
return result;
|
||||
}
|
||||
|
@ -5468,6 +5602,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
if (displayAutosavePopup)
|
||||
{
|
||||
displayAutosavePopup = false;
|
||||
#if sys
|
||||
Toolkit.callLater(() -> {
|
||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
|
||||
|
@ -5477,6 +5612,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
]);
|
||||
});
|
||||
#else
|
||||
// TODO: No auto-save on HTML5?
|
||||
#end
|
||||
}
|
||||
|
||||
moveSongToScrollPosition();
|
||||
|
@ -5506,7 +5644,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
/**
|
||||
* HAXEUI FUNCTIONS
|
||||
*/
|
||||
// ====================
|
||||
// ==================
|
||||
|
||||
/**
|
||||
* Set the currently selected item in the Difficulty tree view to the node representing the current difficulty.
|
||||
|
@ -5597,7 +5735,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
/**
|
||||
* STATIC FUNCTIONS
|
||||
*/
|
||||
// ====================
|
||||
// ==================
|
||||
|
||||
function handleNotePreview():Void
|
||||
{
|
||||
|
|
|
@ -13,17 +13,25 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
var note:SongNoteData;
|
||||
var oldLength:Float;
|
||||
var newLength:Float;
|
||||
var unit:Unit;
|
||||
|
||||
public function new(note:SongNoteData, newLength:Float)
|
||||
public function new(note:SongNoteData, newLength:Float, unit:Unit = MILLISECONDS)
|
||||
{
|
||||
this.note = note;
|
||||
this.oldLength = note.length;
|
||||
this.newLength = newLength;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
note.length = newLength;
|
||||
switch (unit)
|
||||
{
|
||||
case MILLISECONDS:
|
||||
this.note.length = newLength;
|
||||
case STEPS:
|
||||
this.note.setStepLength(newLength);
|
||||
}
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -36,7 +44,8 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
{
|
||||
state.playSound(Paths.sound('chartingSounds/undo'));
|
||||
|
||||
note.length = oldLength;
|
||||
// Always use milliseconds for undoing
|
||||
this.note.length = oldLength;
|
||||
|
||||
state.saveDataDirty = true;
|
||||
state.noteDisplayDirty = true;
|
||||
|
@ -53,6 +62,23 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
|
|||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Extend Note Length';
|
||||
if (oldLength == 0)
|
||||
{
|
||||
return 'Add Hold to Note';
|
||||
}
|
||||
else if (newLength == 0)
|
||||
{
|
||||
return 'Remove Hold from Note';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Extend Hold Note Length';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Unit
|
||||
{
|
||||
MILLISECONDS;
|
||||
STEPS;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,17 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
setup();
|
||||
}
|
||||
|
||||
public override function updateHitbox():Void
|
||||
{
|
||||
// Expand the clickable hitbox to the full column width, then nudge to the left to re-center it.
|
||||
width = ChartEditorState.GRID_SIZE;
|
||||
height = graphicHeight;
|
||||
|
||||
var xOffset = (ChartEditorState.GRID_SIZE - graphicWidth) / 2;
|
||||
offset.set(-xOffset, 0);
|
||||
origin.set(width * 0.5, height * 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height directly, to a value in pixels.
|
||||
* @param h The desired height in pixels.
|
||||
|
@ -52,6 +63,23 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
fullSustainLength = sustainLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to override how debug bounding boxes are drawn for this sprite.
|
||||
*/
|
||||
public override function drawDebugOnCamera(camera:flixel.FlxCamera):Void
|
||||
{
|
||||
if (!camera.visible || !camera.exists || !isOnScreen(camera)) return;
|
||||
|
||||
var rect = getBoundingBox(camera);
|
||||
trace('hold note bounding box: ' + rect.x + ', ' + rect.y + ', ' + rect.width + ', ' + rect.height);
|
||||
|
||||
var gfx = beginDrawDebug(camera);
|
||||
debugBoundingBoxColor = 0xffFF66FF;
|
||||
gfx.lineStyle(2, color, 0.5); // thickness, color, alpha
|
||||
gfx.drawRect(rect.x, rect.y, rect.width, rect.height);
|
||||
endDrawDebug(camera);
|
||||
}
|
||||
|
||||
function setup():Void
|
||||
{
|
||||
strumTime = 999999999;
|
||||
|
@ -60,7 +88,9 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
active = true;
|
||||
visible = true;
|
||||
alpha = 1.0;
|
||||
width = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
|
||||
updateHitbox();
|
||||
}
|
||||
|
||||
public override function revive():Void
|
||||
|
@ -154,7 +184,7 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
}
|
||||
|
||||
this.x += ChartEditorState.GRID_SIZE / 2;
|
||||
this.x -= this.width / 2;
|
||||
this.x -= this.graphicWidth / 2;
|
||||
|
||||
this.y += ChartEditorState.GRID_SIZE / 2;
|
||||
|
||||
|
@ -163,5 +193,8 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
|||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
// Account for expanded clickable hitbox.
|
||||
this.x += this.offset.x;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import flixel.addons.display.FlxSliceSprite;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import flixel.math.FlxRect;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.ui.debug.charting.handlers.ChartEditorThemeHandler;
|
||||
|
||||
/**
|
||||
* A sprite that can be used to display a square over a selected note or event in the chart.
|
||||
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||
*/
|
||||
class ChartEditorSelectionSquareSprite extends FlxSprite
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorSelectionSquareSprite extends FlxSliceSprite
|
||||
{
|
||||
public var noteData:Null<SongNoteData>;
|
||||
public var eventData:Null<SongEventData>;
|
||||
|
||||
public function new()
|
||||
public function new(chartEditorState:ChartEditorState)
|
||||
{
|
||||
super();
|
||||
super(chartEditorState.selectionSquareBitmap,
|
||||
new FlxRect(ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH
|
||||
+ 4, ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH
|
||||
+ 4,
|
||||
ChartEditorState.GRID_SIZE
|
||||
- (2 * ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + 8),
|
||||
ChartEditorState.GRID_SIZE
|
||||
- (2 * ChartEditorThemeHandler.SELECTION_SQUARE_BORDER_WIDTH + 8)),
|
||||
32, 32);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package funkin.ui.debug.charting.contextmenus;
|
||||
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.core.Screen;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.ui.debug.charting.commands.FlipNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.RemoveNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
|
||||
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/hold-note.xml"))
|
||||
class ChartEditorHoldNoteContextMenu extends ChartEditorBaseContextMenu
|
||||
{
|
||||
var contextmenuFlip:MenuItem;
|
||||
var contextmenuDelete:MenuItem;
|
||||
|
||||
var data:SongNoteData;
|
||||
|
||||
public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongNoteData)
|
||||
{
|
||||
super(chartEditorState2, xPos2, yPos2);
|
||||
this.data = data;
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
function initialize():Void
|
||||
{
|
||||
// NOTE: Remember to use commands here to ensure undo/redo works properly
|
||||
contextmenuFlip.onClick = function(_) {
|
||||
chartEditorState.performCommand(new FlipNotesCommand([data]));
|
||||
}
|
||||
|
||||
contextmenuRemoveHold.onClick = function(_) {
|
||||
chartEditorState.performCommand(new ExtendNoteLengthCommand(data, 0));
|
||||
}
|
||||
|
||||
contextmenuDelete.onClick = function(_) {
|
||||
chartEditorState.performCommand(new RemoveNotesCommand([data]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import haxe.ui.core.Screen;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.ui.debug.charting.commands.FlipNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.RemoveNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
|
||||
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/note.xml"))
|
||||
|
@ -31,6 +32,10 @@ class ChartEditorNoteContextMenu extends ChartEditorBaseContextMenu
|
|||
chartEditorState.performCommand(new FlipNotesCommand([data]));
|
||||
}
|
||||
|
||||
contextmenuAddHold.onClick = function(_) {
|
||||
chartEditorState.performCommand(new ExtendNoteLengthCommand(data, 4, STEPS));
|
||||
}
|
||||
|
||||
contextmenuDelete.onClick = function(_) {
|
||||
chartEditorState.performCommand(new RemoveNotesCommand([data]));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package funkin.ui.debug.charting.handlers;
|
|||
|
||||
import funkin.ui.debug.charting.contextmenus.ChartEditorDefaultContextMenu;
|
||||
import funkin.ui.debug.charting.contextmenus.ChartEditorEventContextMenu;
|
||||
import funkin.ui.debug.charting.contextmenus.ChartEditorHoldNoteContextMenu;
|
||||
import funkin.ui.debug.charting.contextmenus.ChartEditorNoteContextMenu;
|
||||
import funkin.ui.debug.charting.contextmenus.ChartEditorSelectionContextMenu;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
|
@ -23,16 +24,33 @@ class ChartEditorContextMenuHandler
|
|||
displayMenu(state, new ChartEditorDefaultContextMenu(state, xPos, yPos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opened when shift+right-clicking a selection of multiple items.
|
||||
*/
|
||||
public static function openSelectionContextMenu(state:ChartEditorState, xPos:Float, yPos:Float)
|
||||
{
|
||||
displayMenu(state, new ChartEditorSelectionContextMenu(state, xPos, yPos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opened when shift+right-clicking a single note.
|
||||
*/
|
||||
public static function openNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData)
|
||||
{
|
||||
displayMenu(state, new ChartEditorNoteContextMenu(state, xPos, yPos, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opened when shift+right-clicking a single hold note.
|
||||
*/
|
||||
public static function openHoldNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData)
|
||||
{
|
||||
displayMenu(state, new ChartEditorHoldNoteContextMenu(state, xPos, yPos, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opened when shift+right-clicking a single event.
|
||||
*/
|
||||
public static function openEventContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongEventData)
|
||||
{
|
||||
displayMenu(state, new ChartEditorEventContextMenu(state, xPos, yPos, data));
|
||||
|
|
|
@ -52,7 +52,7 @@ class ChartEditorThemeHandler
|
|||
// Border on the square highlighting selected notes.
|
||||
static final SELECTION_SQUARE_BORDER_COLOR_LIGHT:FlxColor = 0xFF339933;
|
||||
static final SELECTION_SQUARE_BORDER_COLOR_DARK:FlxColor = 0xFF339933;
|
||||
static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1;
|
||||
public static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1;
|
||||
|
||||
// Fill on the square highlighting selected notes.
|
||||
// Make sure this is transparent so you can see the notes underneath.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.ui.freeplay;
|
||||
|
||||
import funkin.input.Controls;
|
||||
import flash.text.TextField;
|
||||
import flixel.addons.display.FlxGridOverlay;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
@ -23,33 +22,35 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.input.Controls.Control;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.shaders.AngleMask;
|
||||
import funkin.graphics.shaders.HSVShader;
|
||||
import funkin.graphics.shaders.PureColor;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.graphics.shaders.StrokeShader;
|
||||
import funkin.input.Controls;
|
||||
import funkin.input.Controls.Control;
|
||||
import funkin.play.components.HealthIcon;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.save.Save;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.freeplay.BGScrollingText;
|
||||
import funkin.ui.freeplay.DifficultyStars;
|
||||
import funkin.ui.freeplay.DJBoyfriend;
|
||||
import funkin.ui.freeplay.FreeplayScore;
|
||||
import funkin.ui.freeplay.LetterSort;
|
||||
import funkin.ui.freeplay.SongMenuItem;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.util.MathUtil;
|
||||
import lime.app.Future;
|
||||
import lime.utils.Assets;
|
||||
|
||||
|
@ -64,7 +65,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
var fp:FreeplayScore;
|
||||
var txtCompletion:FlxText;
|
||||
var txtCompletion:AtlasText;
|
||||
var lerpCompletion:Float = 0;
|
||||
var intendedCompletion:Float = 0;
|
||||
var lerpScore:Float = 0;
|
||||
|
@ -87,6 +88,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
var grpCapsules:FlxTypedGroup<SongMenuItem>;
|
||||
var curCapsule:SongMenuItem;
|
||||
var curPlaying:Bool = false;
|
||||
var ostName:FlxText;
|
||||
var difficultyStars:DifficultyStars;
|
||||
|
||||
var dj:DJBoyfriend;
|
||||
|
||||
|
@ -150,15 +153,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
for (songId in LevelRegistry.instance.parseEntryData(levelId).songs)
|
||||
{
|
||||
var song:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
var songBaseDifficulty:SongDifficulty = song.getDifficulty(Constants.DEFAULT_DIFFICULTY);
|
||||
|
||||
var songName = songBaseDifficulty.songName;
|
||||
var songOpponent = songBaseDifficulty.characters.opponent;
|
||||
var songDifficulties = song.listDifficulties();
|
||||
songs.push(new FreeplaySongData(levelId, songId, song));
|
||||
|
||||
songs.push(new FreeplaySongData(songId, songName, levelId, songOpponent, songDifficulties));
|
||||
|
||||
for (difficulty in songDifficulties)
|
||||
for (difficulty in song.listDifficulties())
|
||||
{
|
||||
diffIdsTotal.pushUnique(difficulty);
|
||||
}
|
||||
|
@ -334,6 +332,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true;
|
||||
}
|
||||
|
||||
// NOTE: This is an AtlasSprite because we use an animation to bring it into view.
|
||||
// TODO: Add the ability to select the album graphic.
|
||||
var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll"));
|
||||
albumArt.visible = false;
|
||||
add(albumArt);
|
||||
|
@ -347,7 +347,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var albumTitle:FlxSprite = new FlxSprite(947, 491).loadGraphic(Paths.image('freeplay/albumTitle-fnfvol1'));
|
||||
var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite'));
|
||||
var difficultyStars:DifficultyStars = new DifficultyStars(140, 39);
|
||||
difficultyStars = new DifficultyStars(140, 39);
|
||||
|
||||
difficultyStars.stars.visible = false;
|
||||
albumTitle.visible = false;
|
||||
|
@ -382,11 +382,16 @@ class FreeplayState extends MusicBeatSubState
|
|||
add(overhangStuff);
|
||||
FlxTween.tween(overhangStuff, {y: 0}, 0.3, {ease: FlxEase.quartOut});
|
||||
|
||||
var fnfFreeplay:FlxText = new FlxText(0, 12, 0, "FREEPLAY", 48);
|
||||
var fnfFreeplay:FlxText = new FlxText(8, 8, 0, "FREEPLAY", 48);
|
||||
fnfFreeplay.font = "VCR OSD Mono";
|
||||
fnfFreeplay.visible = false;
|
||||
|
||||
exitMovers.set([overhangStuff, fnfFreeplay],
|
||||
ostName = new FlxText(8, 8, FlxG.width - 8 - 8, "OFFICIAL OST", 48);
|
||||
ostName.font = "VCR OSD Mono";
|
||||
ostName.alignment = RIGHT;
|
||||
ostName.visible = false;
|
||||
|
||||
exitMovers.set([overhangStuff, fnfFreeplay, ostName],
|
||||
{
|
||||
y: -overhangStuff.height,
|
||||
x: 0,
|
||||
|
@ -397,8 +402,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
var sillyStroke = new StrokeShader(0xFFFFFFFF, 2, 2);
|
||||
fnfFreeplay.shader = sillyStroke;
|
||||
add(fnfFreeplay);
|
||||
add(ostName);
|
||||
|
||||
var fnfHighscoreSpr:FlxSprite = new FlxSprite(890, 70);
|
||||
var fnfHighscoreSpr:FlxSprite = new FlxSprite(860, 70);
|
||||
fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore');
|
||||
fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 24, false);
|
||||
fnfHighscoreSpr.visible = false;
|
||||
|
@ -415,8 +421,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
fp.visible = false;
|
||||
add(fp);
|
||||
|
||||
txtCompletion = new FlxText(1200, 77, 0, "0", 32);
|
||||
txtCompletion.font = "VCR OSD Mono";
|
||||
var clearBoxSprite:FlxSprite = new FlxSprite(1165, 65).loadGraphic(Paths.image('freeplay/clearBox'));
|
||||
add(clearBoxSprite);
|
||||
|
||||
txtCompletion = new AtlasText(1185, 87, "69", AtlasFont.FREEPLAY_CLEAR);
|
||||
txtCompletion.visible = false;
|
||||
add(txtCompletion);
|
||||
|
||||
|
@ -485,6 +493,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
new FlxTimer().start(1 / 24, function(handShit) {
|
||||
fnfHighscoreSpr.visible = true;
|
||||
fnfFreeplay.visible = true;
|
||||
ostName.visible = true;
|
||||
fp.visible = true;
|
||||
fp.updateScore(0);
|
||||
|
||||
|
@ -674,9 +683,32 @@ class FreeplayState extends MusicBeatSubState
|
|||
lerpScore = MathUtil.coolLerp(lerpScore, intendedScore, 0.2);
|
||||
lerpCompletion = MathUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9);
|
||||
|
||||
if (Math.isNaN(lerpScore))
|
||||
{
|
||||
lerpScore = intendedScore;
|
||||
}
|
||||
|
||||
if (Math.isNaN(lerpCompletion))
|
||||
{
|
||||
lerpCompletion = intendedCompletion;
|
||||
}
|
||||
|
||||
fp.updateScore(Std.int(lerpScore));
|
||||
|
||||
txtCompletion.text = Math.floor(lerpCompletion * 100) + "%";
|
||||
txtCompletion.text = '${Math.floor(lerpCompletion * 100)}';
|
||||
|
||||
// Right align the completion percentage
|
||||
switch (txtCompletion.text.length)
|
||||
{
|
||||
case 3:
|
||||
txtCompletion.x = 1185 - 10;
|
||||
case 2:
|
||||
txtCompletion.x = 1185;
|
||||
case 1:
|
||||
txtCompletion.x = 1185 + 24;
|
||||
default:
|
||||
txtCompletion.x = 1185;
|
||||
}
|
||||
|
||||
handleInputs(elapsed);
|
||||
}
|
||||
|
@ -913,6 +945,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
intendedCompletion = 0.0;
|
||||
}
|
||||
|
||||
if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion))
|
||||
{
|
||||
intendedCompletion = 0;
|
||||
}
|
||||
|
||||
grpDifficulties.group.forEach(function(diffSprite) {
|
||||
diffSprite.visible = false;
|
||||
});
|
||||
|
@ -938,6 +975,27 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (change != 0)
|
||||
{
|
||||
// Update the song capsules to reflect the new difficulty info.
|
||||
for (songCapsule in grpCapsules.members)
|
||||
{
|
||||
if (songCapsule == null) continue;
|
||||
if (songCapsule.songData != null)
|
||||
{
|
||||
songCapsule.songData.currentDifficulty = currentDifficulty;
|
||||
songCapsule.init(null, null, songCapsule.songData);
|
||||
}
|
||||
else
|
||||
{
|
||||
songCapsule.init(null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the difficulty star count on the right.
|
||||
difficultyStars.difficulty = daSong.songRating;
|
||||
}
|
||||
|
||||
// Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)
|
||||
|
@ -1046,6 +1104,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
currentDifficulty = rememberedDifficulty;
|
||||
}
|
||||
|
||||
// Set the difficulty star count on the right.
|
||||
var daSong = songs[curSelected];
|
||||
difficultyStars.difficulty = daSong?.songRating ?? 0;
|
||||
}
|
||||
|
||||
function changeSelection(change:Int = 0)
|
||||
|
@ -1176,19 +1238,47 @@ class FreeplaySongData
|
|||
{
|
||||
public var isFav:Bool = false;
|
||||
|
||||
public var songId:String = "";
|
||||
public var songName:String = "";
|
||||
public var levelId:String = "";
|
||||
public var songCharacter:String = "";
|
||||
public var songDifficulties:Array<String> = [];
|
||||
var song:Song;
|
||||
|
||||
public function new(songId:String, songName:String, levelId:String, songCharacter:String, songDifficulties:Array<String>)
|
||||
public var levelId(default, null):String = "";
|
||||
public var songId(default, null):String = "";
|
||||
|
||||
public var songDifficulties(default, null):Array<String> = [];
|
||||
|
||||
public var songName(default, null):String = "";
|
||||
public var songCharacter(default, null):String = "";
|
||||
public var songRating(default, null):Int = 0;
|
||||
|
||||
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
function set_currentDifficulty(value:String):String
|
||||
{
|
||||
if (currentDifficulty == value) return value;
|
||||
|
||||
currentDifficulty = value;
|
||||
updateValues();
|
||||
return value;
|
||||
}
|
||||
|
||||
public function new(levelId:String, songId:String, song:Song)
|
||||
{
|
||||
this.songId = songId;
|
||||
this.songName = songName;
|
||||
this.levelId = levelId;
|
||||
this.songCharacter = songCharacter;
|
||||
this.songDifficulties = songDifficulties;
|
||||
this.songId = songId;
|
||||
this.song = song;
|
||||
|
||||
updateValues();
|
||||
}
|
||||
|
||||
function updateValues():Void
|
||||
{
|
||||
this.songDifficulties = song.listDifficulties();
|
||||
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty);
|
||||
if (songDifficulty == null) return;
|
||||
this.songName = songDifficulty.songName;
|
||||
this.songCharacter = songDifficulty.characters.opponent;
|
||||
this.songRating = songDifficulty.difficultyRating;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,11 +35,6 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect"];
|
||||
|
||||
// lol...
|
||||
var diffRanks:Array<String> = [
|
||||
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "14", "15"
|
||||
];
|
||||
|
||||
public var targetPos:FlxPoint = new FlxPoint();
|
||||
public var doLerp:Bool = false;
|
||||
public var doJumpIn:Bool = false;
|
||||
|
@ -47,10 +42,12 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
public var doJumpOut:Bool = false;
|
||||
|
||||
public var onConfirm:Void->Void;
|
||||
public var diffGrayscale:Grayscale;
|
||||
public var grayscaleShader:Grayscale;
|
||||
|
||||
public var hsvShader(default, set):HSVShader;
|
||||
|
||||
var diffRatingSprite:FlxSprite;
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
@ -75,26 +72,30 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
add(ranking);
|
||||
grpHide.add(ranking);
|
||||
|
||||
diffGrayscale = new Grayscale(1);
|
||||
|
||||
var diffRank = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRankings/diff" + FlxG.random.getObject(diffRanks)));
|
||||
diffRank.shader = diffGrayscale;
|
||||
diffRank.visible = false;
|
||||
add(diffRank);
|
||||
diffRank.origin.set(capsule.origin.x - diffRank.x, capsule.origin.y - diffRank.y);
|
||||
grpHide.add(diffRank);
|
||||
|
||||
switch (rank)
|
||||
{
|
||||
case "perfect":
|
||||
ranking.x -= 10;
|
||||
}
|
||||
|
||||
grayscaleShader = new Grayscale(1);
|
||||
|
||||
diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image("freeplay/diffRatings/diff00"));
|
||||
diffRatingSprite.shader = grayscaleShader;
|
||||
diffRatingSprite.visible = false;
|
||||
add(diffRatingSprite);
|
||||
diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y);
|
||||
grpHide.add(diffRatingSprite);
|
||||
|
||||
songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled));
|
||||
add(songText);
|
||||
grpHide.add(songText);
|
||||
|
||||
pixelIcon = new FlxSprite(155, 15);
|
||||
// TODO: Use value from metadata instead of random.
|
||||
updateDifficultyRating(FlxG.random.int(0, 15));
|
||||
|
||||
pixelIcon = new FlxSprite(160, 35);
|
||||
|
||||
pixelIcon.makeGraphic(32, 32, 0x00000000);
|
||||
pixelIcon.antialiasing = false;
|
||||
pixelIcon.active = false;
|
||||
|
@ -113,6 +114,12 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
setVisibleGrp(false);
|
||||
}
|
||||
|
||||
function updateDifficultyRating(newRating:Int)
|
||||
{
|
||||
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
|
||||
diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
|
||||
}
|
||||
|
||||
function set_hsvShader(value:HSVShader):HSVShader
|
||||
{
|
||||
this.hsvShader = value;
|
||||
|
@ -149,16 +156,17 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
updateSelected();
|
||||
}
|
||||
|
||||
public function init(x:Float, y:Float, songData:Null<FreeplaySongData>)
|
||||
public function init(?x:Float, ?y:Float, songData:Null<FreeplaySongData>)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
if (x != null) this.x = x;
|
||||
if (y != null) this.y = y;
|
||||
this.songData = songData;
|
||||
|
||||
// Update capsule text.
|
||||
songText.text = songData?.songName ?? 'Random';
|
||||
// Update capsule character.
|
||||
if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
|
||||
updateDifficultyRating(songData?.songRating ?? 0);
|
||||
// Update opacity, offsets, etc.
|
||||
updateSelected();
|
||||
}
|
||||
|
@ -200,7 +208,14 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
pixelIcon.loadGraphic(Paths.image(charPath));
|
||||
pixelIcon.scale.x = pixelIcon.scale.y = 2;
|
||||
pixelIcon.origin.x = 100;
|
||||
|
||||
switch (char)
|
||||
{
|
||||
case "parents-christmas":
|
||||
pixelIcon.origin.x = 140;
|
||||
default:
|
||||
pixelIcon.origin.x = 100;
|
||||
}
|
||||
// pixelIcon.origin.x = capsule.origin.x;
|
||||
// pixelIcon.offset.x -= pixelIcon.origin.x;
|
||||
}
|
||||
|
@ -336,7 +351,7 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
function updateSelected():Void
|
||||
{
|
||||
diffGrayscale.setAmount(this.selected ? 0 : 0.8);
|
||||
grayscaleShader.setAmount(this.selected ? 0 : 0.8);
|
||||
songText.alpha = this.selected ? 1 : 0.6;
|
||||
songText.blurredText.visible = this.selected ? true : false;
|
||||
capsule.offset.x = this.selected ? 0 : -5;
|
||||
|
|
|
@ -11,12 +11,12 @@ class HaxelibVersions
|
|||
#else
|
||||
// `#if display` is used for code completion. In this case returning an
|
||||
// empty string is good enough; We don't want to call functions on every hint.
|
||||
var commitHash:String = "";
|
||||
return macro $v{commitHashSplice};
|
||||
var commitHash:Array<String> = [];
|
||||
return macro $v{commitHash};
|
||||
#end
|
||||
}
|
||||
|
||||
#if (debug && macro)
|
||||
#if (macro)
|
||||
static function readHmmData():hmm.HmmConfig
|
||||
{
|
||||
return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow();
|
||||
|
|
|
@ -1,32 +1,42 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import haxe.Int64;
|
||||
|
||||
/**
|
||||
* @see https://github.com/fponticelli/thx.core/blob/master/src/thx/Int64s.hx
|
||||
* Why `haxe.Int64` doesn't have a built-in `toFloat` function is beyond me.
|
||||
*/
|
||||
class Int64Tools
|
||||
{
|
||||
static var min = haxe.Int64.make(0x80000000, 0);
|
||||
static var one = haxe.Int64.make(0, 1);
|
||||
static var two = haxe.Int64.ofInt(2);
|
||||
static var zero = haxe.Int64.make(0, 0);
|
||||
static var ten = haxe.Int64.ofInt(10);
|
||||
private inline static var MAX_32_PRECISION:Float = 4294967296.0;
|
||||
|
||||
public static function toFloat(i:haxe.Int64):Float
|
||||
public static function fromFloat(f:Float):Int64
|
||||
{
|
||||
var isNegative = false;
|
||||
if (i < 0)
|
||||
var h = Std.int(f / MAX_32_PRECISION);
|
||||
var l = Std.int(f);
|
||||
return Int64.make(h, l);
|
||||
}
|
||||
|
||||
public static function toFloat(i:Int64):Float
|
||||
{
|
||||
var f:Float = Int64.getLow(i);
|
||||
if (f < 0) f += MAX_32_PRECISION;
|
||||
return (Int64.getHigh(i) * MAX_32_PRECISION + f);
|
||||
}
|
||||
|
||||
public static function isToIntSafe(i:Int64):Bool
|
||||
{
|
||||
return i.high != i.low >> 31;
|
||||
}
|
||||
|
||||
public static function toIntSafe(i:Int64):Int
|
||||
{
|
||||
try
|
||||
{
|
||||
if (i < min) return -9223372036854775808.0; // most -ve value can't be made +ve
|
||||
isNegative = true;
|
||||
i = -i;
|
||||
return Int64.toInt(i);
|
||||
}
|
||||
var multiplier = 1.0, ret = 0.0;
|
||||
for (_ in 0...64)
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
if (haxe.Int64.and(i, one) != zero) ret += multiplier;
|
||||
multiplier *= 2.0;
|
||||
i = haxe.Int64.shr(i, 1);
|
||||
throw 'Could not represent value "${Int64.toStr(i)}" as an integer.';
|
||||
}
|
||||
return (isNegative ? -1 : 1) * ret;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue