Many improvements to chart system and song format logic

This commit is contained in:
Eric Myllyoja 2022-10-26 01:14:29 -04:00
parent fc4f398157
commit 6e8feecef1
17 changed files with 1415 additions and 265 deletions

View file

@ -181,6 +181,7 @@
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
<haxedef name="CAN_CHEAT" if="switch debug" />
<haxedef name="haxeui_no_mouse_reset" />
<!-- Skip the Intro -->
<section if="debug">

View file

@ -49,14 +49,14 @@
"name": "haxeui-core",
"type": "git",
"dir": null,
"ref": "9b3a2fb",
"ref": "fc8d656b",
"url": "https://github.com/haxeui/haxeui-core/"
},
{
"name": "haxeui-flixel",
"type": "git",
"dir": null,
"ref": "master",
"ref": "80941a7",
"url": "https://github.com/haxeui/haxeui-flixel"
},
{

View file

@ -524,10 +524,7 @@ class FreeplayState extends MusicBeatSubstate
PlayState.currentSong_NEW = SongDataParser.fetchSong(songs[curSelected].songName.toLowerCase());
PlayState.isStoryMode = false;
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
// SongLoad.curDiff = Highscore.formatSong()
SongLoad.curDiff = switch (curDifficulty)
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{
case 0:
'easy';
@ -537,6 +534,9 @@ class FreeplayState extends MusicBeatSubstate
'hard';
default: 'normal';
};
// SongLoad.curDiff = Highscore.formatSong()
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
PlayState.storyWeek = songs[curSelected].week;
trace(' CUR WEEK ' + PlayState.storyWeek);
@ -565,7 +565,17 @@ class FreeplayState extends MusicBeatSubstate
intendedScore = FlxG.random.int(0, 100000);
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{
case 0:
'easy';
case 1:
'normal';
case 2:
'hard';
default:
'normal';
};
grpDifficulties.group.forEach(function(spr)
{

View file

@ -7,7 +7,6 @@ import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.util.FlxColor;
import funkin.charting.ChartingState;
import funkin.modding.module.ModuleHandler;
import funkin.play.PlayState;
import funkin.play.character.CharacterData.CharacterDataParser;
@ -15,8 +14,6 @@ import funkin.play.event.SongEvent.SongEventHandler;
import funkin.play.song.SongData.SongDataParser;
import funkin.play.stage.StageData;
import funkin.ui.PreferencesMenu;
import funkin.ui.animDebugShit.DebugBoundingState;
import funkin.ui.stageBuildShit.StageBuilderState;
import funkin.util.macro.MacroUtil;
import openfl.display.BitmapData;
@ -198,14 +195,14 @@ class InitState extends FlxTransitionableState
PlayState.currentSong_NEW = SongDataParser.fetchSong(song);
PlayState.isStoryMode = isStoryMode;
PlayState.storyDifficulty = dif;
PlayState.storyDifficulty_NEW = 'easy';
SongLoad.curDiff = switch (dif)
PlayState.storyDifficulty_NEW = switch (dif)
{
case 0: 'easy';
case 1: 'normal';
case 2: 'hard';
default: 'normal';
};
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
PlayState.storyWeek = week;
LoadingState.loadAndSwitchState(new PlayState());
}

View file

@ -189,7 +189,7 @@ class PauseSubState extends MusicBeatSubstate
SongLoad.curDiff = daSelected.toLowerCase();
PlayState.storyDifficulty = curSelected;
PlayState.storyDifficulty_NEW = 'easy';
PlayState.storyDifficulty_NEW = daSelected.toLowerCase();
PlayState.needsReset = true;

View file

@ -378,8 +378,7 @@ class StoryMenuState extends MusicBeatState
PlayState.campaignScore = 0;
PlayState.storyDifficulty = curDifficulty;
PlayState.storyDifficulty_NEW = 'easy';
SongLoad.curDiff = switch (curDifficulty)
PlayState.storyDifficulty_NEW = switch (curDifficulty)
{
case 0:
'easy';
@ -390,6 +389,7 @@ class StoryMenuState extends MusicBeatState
default:
'normal';
};
SongLoad.curDiff = PlayState.storyDifficulty_NEW;
new FlxTimer().start(1, function(tmr:FlxTimer)
{

View file

@ -396,7 +396,7 @@ class PlayState extends MusicBeatState
healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this,
'healthLerp', 0, 2);
healthBar.scrollFactor.set();
healthBar.createFilledBar(Constants.HEALTH_BAR_RED, Constants.HEALTH_BAR_GREEN);
healthBar.createFilledBar(Constants.COLOR_HEALTH_BAR_RED, Constants.COLOR_HEALTH_BAR_GREEN);
add(healthBar);
initStage();
@ -711,8 +711,19 @@ class PlayState extends MusicBeatState
// TODO: Switch playable character by manipulating this value.
// TODO: How to choose which one to use for story mode?
var playableChars = currentChart.getPlayableChars();
var currentPlayer = 'bf';
if (playableChars.length == 0)
{
trace('WARNING: No playable characters found for this song.');
}
else if (playableChars.indexOf(currentPlayer) == -1)
{
currentPlayer = playableChars[0];
}
var currentCharData:SongPlayableChar = currentChart.getPlayableChar(currentPlayer);
//

View file

@ -212,6 +212,11 @@ class SongDifficulty
return chars.get(id);
}
public function getPlayableChars():Array<String>
{
return [for (i in chars.keys()) i];
}
public function getEvents():Array<SongEvent>
{
return cast events;

View file

@ -13,6 +13,7 @@ class SongDataUtils
/**
* Given an array of SongNoteData objects, return a new array of SongNoteData objects
* whose timestamps are shifted by the given amount.
* Does not mutate the original array.
*
* @param notes The notes to modify.
* @param offset The time difference to apply in milliseconds.
@ -26,7 +27,8 @@ class SongDataUtils
}
/**
* Remove a certain subset of notes from an array of SongNoteData objects.
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
* Does not mutate the original array.
*
* @param notes The array of notes to be subtracted from.
* @param subtrahend The notes to remove from the `notes` array. Yes, subtrahend is a real word.
@ -50,7 +52,8 @@ class SongDataUtils
}
/**
* Remove a certain subset of events from an array of SongEventData objects.
* Return a new array without a certain subset of events from an array of SongEventData objects.
* Does not mutate the original array.
*
* @param events The array of events to be subtracted from.
* @param subtrahend The events to remove from the `events` array. Yes, subtrahend is a real word.
@ -67,6 +70,25 @@ class SongDataUtils
});
}
/**
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
* Does not mutate the original array.
*/
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
{
return notes.map(function(note:SongNoteData):SongNoteData
{
var newData = note.data;
if (newData < strumlineSize)
newData += strumlineSize;
else
newData -= strumlineSize;
return new SongNoteData(note.time, newData, note.length, note.kind);
});
}
/**
* Prepare an array of notes to be used as the clipboard data.
*
@ -77,6 +99,9 @@ class SongDataUtils
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
}
/**
* Sort an array of notes by strum time.
*/
public static function sortNotes(notes:Array<SongNoteData>, ?desc:Bool = false):Array<SongNoteData>
{
// TODO: Modifies the array in place. Is this okay?
@ -87,6 +112,9 @@ class SongDataUtils
return notes;
}
/**
* Serialize an array of note data and write it to the clipboard.
*/
public static function writeNotesToClipboard(notes:Array<SongNoteData>):Void
{
var notesString = SerializerUtil.toJSON(notes);
@ -98,6 +126,9 @@ class SongDataUtils
trace(notesString);
}
/**
* Read an array of note data from the clipboard and deserialize it.
*/
public static function readNotesFromClipboard():Array<SongNoteData>
{
var notesString = ClipboardUtil.getClipboard();
@ -117,4 +148,37 @@ class SongDataUtils
return notes;
}
}
/**
* Filter a list of notes to only include notes that are within the given time range.
*/
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return note.time >= start && note.time <= end;
});
}
/**
* Filter a list of notes to only include notes whose data is within the given range.
*/
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return note.data >= start && note.data <= end;
});
}
/**
* Filter a list of notes to only include notes whose data is one of the given values.
*/
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return data.indexOf(note.data) != -1;
});
}
}

View file

@ -38,10 +38,12 @@ interface ChartEditorCommand
class AddNotesCommand implements ChartEditorCommand
{
private var notes:Array<SongNoteData>;
private var appendToSelection:Bool;
public function new(notes:Array<SongNoteData>)
public function new(notes:Array<SongNoteData>, appendToSelection:Bool = false)
{
this.notes = notes;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
@ -51,7 +53,15 @@ class AddNotesCommand implements ChartEditorCommand
state.currentSongChartNoteData.push(note);
}
state.currentSelection = notes;
if (appendToSelection)
{
state.currentSelection = state.currentSelection.concat(notes);
}
else
{
state.currentSelection = notes;
}
state.playSound(Paths.sound('funnyNoise/funnyNoise-08'));
state.noteDisplayDirty = true;
@ -171,6 +181,9 @@ class SwitchDifficultyCommand implements ChartEditorCommand
}
}
/**
* Adds one or more notes to the selection.
*/
class SelectNotesCommand implements ChartEditorCommand
{
private var notes:Array<SongNoteData>;
@ -251,6 +264,43 @@ class DeselectNotesCommand implements ChartEditorCommand
}
}
/**
* Sets the selection rather than appends it.
* Deselects any notes that are not in the new selection.
*/
class SetNoteSelectionCommand implements ChartEditorCommand
{
private var notes:Array<SongNoteData>;
private var previousSelection:Array<SongNoteData>;
public function new(notes:Array<SongNoteData>, ?previousSelection:Array<SongNoteData>)
{
this.notes = notes;
this.previousSelection = previousSelection == null ? [] : previousSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentSelection = notes;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentSelection = previousSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Select ${notes.length} Notes';
}
}
class SelectAllNotesCommand implements ChartEditorCommand
{
private var previousSelection:Array<SongNoteData>;
@ -281,6 +331,36 @@ class SelectAllNotesCommand implements ChartEditorCommand
}
}
class InvertSelectedNotesCommand implements ChartEditorCommand
{
private var previousSelection:Array<SongNoteData>;
public function new(?previousSelection:Array<SongNoteData>)
{
this.previousSelection = previousSelection == null ? [] : previousSelection;
}
public function execute(state:ChartEditorState):Void
{
state.currentSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousSelection);
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function undo(state:ChartEditorState):Void
{
state.currentSelection = previousSelection;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
}
public function toString():String
{
return 'Invert Selected Notes';
}
}
class DeselectAllNotesCommand implements ChartEditorCommand
{
private var previousSelection:Array<SongNoteData>;
@ -352,6 +432,52 @@ class CutNotesCommand implements ChartEditorCommand
}
}
class FlipNotesCommand implements ChartEditorCommand
{
private var notes:Array<SongNoteData>;
private var flippedNotes:Array<SongNoteData>;
public function new(notes:Array<SongNoteData>)
{
this.notes = notes;
}
public function execute(state:ChartEditorState):Void
{
// Delete the notes.
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
// Add the flipped notes.
flippedNotes = SongDataUtils.flipNotes(notes);
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(flippedNotes);
state.currentSelection = flippedNotes;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, flippedNotes);
state.currentSongChartNoteData = state.currentSongChartNoteData.concat(notes);
state.currentSelection = notes;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
var len:Int = notes.length;
return 'Flip $len Notes';
}
}
class PasteNotesCommand implements ChartEditorCommand
{
private var targetTimestamp:Float;
@ -399,13 +525,12 @@ class PasteNotesCommand implements ChartEditorCommand
class AddEventsCommand implements ChartEditorCommand
{
private var events:Array<SongEventData>;
private var appendToSelection:Bool;
// private var previousSelection:Array<SongEventData>;
public function new(events:Array<SongEventData>, ?previousSelection:Array<SongEventData>)
public function new(events:Array<SongEventData>, ?appendToSelection:Bool = false)
{
this.events = events;
// this.previousSelection = previousSelection == null ? [] : previousSelection;
this.appendToSelection = appendToSelection;
}
public function execute(state:ChartEditorState):Void
@ -423,8 +548,8 @@ class AddEventsCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
// TODO: Allow selecting events.
// state.currentSelection = previousSelection;
state.currentSelection = [];
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
@ -438,3 +563,42 @@ class AddEventsCommand implements ChartEditorCommand
return 'Add $len Events';
}
}
class ExtendNoteLengthCommand implements ChartEditorCommand
{
private var note:SongNoteData;
private var oldLength:Float;
private var newLength:Float;
public function new(note:SongNoteData, newLength:Float)
{
this.note = note;
this.oldLength = note.length;
this.newLength = newLength;
}
public function execute(state:ChartEditorState):Void
{
note.length = newLength;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function undo(state:ChartEditorState):Void
{
note.length = oldLength;
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.sortChartData();
}
public function toString():String
{
return 'Extend Note Length';
}
}

View file

@ -0,0 +1,5 @@
package funkin.ui.debug.charting;
class ChartEditorDialogHandler
{
}

View file

@ -23,6 +23,16 @@ class ChartEditorNoteSprite extends FlxSprite
*/
public var noteSkin(default, set):String = 'Normal';
/**
* This note is the previous sprite in a sustain chain.
*/
public var parentNoteSprite(default, set):ChartEditorNoteSprite = null;
/**
* This note is the next sprite in a sustain chain.
*/
public var childNoteSprite(default, set):ChartEditorNoteSprite = null;
public function new()
{
super();
@ -41,22 +51,20 @@ class ChartEditorNoteSprite extends FlxSprite
this.animation.addByPrefix('tapUpNormal', 'green instance');
this.animation.addByPrefix('tapRightNormal', 'red instance');
this.animation.addByPrefix('holdLeftNormal', 'purple hold piece instance');
this.animation.addByPrefix('holdDownNormal', 'blue hold piece instance');
this.animation.addByPrefix('holdUpNormal', 'green hold piece instance');
this.animation.addByPrefix('holdRightNormal', 'red hold piece instance');
this.animation.addByPrefix('holdLeftNormal', 'LeftHoldPiece');
this.animation.addByPrefix('holdDownNormal', 'DownHoldPiece');
this.animation.addByPrefix('holdUpNormal', 'UpHoldPiece');
this.animation.addByPrefix('holdRightNormal', 'RightHoldPiece');
this.animation.addByPrefix('holdEndLeftNormal', 'pruple end hold instance');
this.animation.addByPrefix('holdEndDownNormal', 'blue end hold instance');
this.animation.addByPrefix('holdEndUpNormal', 'green end hold instance');
this.animation.addByPrefix('holdEndRightNormal', 'red end hold instance');
this.animation.addByPrefix('holdEndLeftNormal', 'LeftHoldEnd');
this.animation.addByPrefix('holdEndDownNormal', 'DownHoldEnd');
this.animation.addByPrefix('holdEndUpNormal', 'UpHoldEnd');
this.animation.addByPrefix('holdEndRightNormal', 'RightHoldEnd');
this.animation.addByPrefix('tapLeftPixel', 'pixel4');
this.animation.addByPrefix('tapDownPixel', 'pixel5');
this.animation.addByPrefix('tapUpPixel', 'pixel6');
this.animation.addByPrefix('tapRightPixel', 'pixel7');
resizeNote();
}
static var noteFrameCollection:FlxFramesCollection = null;
@ -77,6 +85,12 @@ class ChartEditorNoteSprite extends FlxSprite
{
noteFrameCollection.pushFrame(frame);
}
var frameCollectionNormal2 = Paths.getSparrowAtlas('NoteHoldNormal');
for (frame in frameCollectionNormal2.frames)
{
noteFrameCollection.pushFrame(frame);
}
// Pixel notes
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
@ -98,18 +112,27 @@ class ChartEditorNoteSprite extends FlxSprite
if (this.noteData == null)
{
// Disown parent.
this.parentNoteSprite = null;
if (this.childNoteSprite != null)
{
// Kill all children and disown them.
this.childNoteSprite.noteData = null;
this.childNoteSprite = null;
}
this.kill();
return this.noteData;
}
this.visible = true;
// Update the position to match the note skin.
setNotePosition();
// Update the animation to match the note skin.
// Update the animation to match the note data.
// Animation is updated first so size is correct before updating position.
playNoteAnimation();
// Update the position to match the note data.
setNotePosition();
return this.noteData;
}
@ -149,25 +172,125 @@ class ChartEditorNoteSprite extends FlxSprite
cursorColumn += ChartEditorState.STRUMLINE_SIZE;
}
}
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
// TODO: stepTime doesn't account for fluctuating BPMs.
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
if (parentNoteSprite == null)
{
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
// TODO: stepTime doesn't account for fluctuating BPMs.
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
}
else
{
// If this is a hold note, we need to adjust the position to be centered.
if (parentNoteSprite.parentNoteSprite == null)
{
this.x = parentNoteSprite.x;
this.x += (ChartEditorState.GRID_SIZE / 2);
this.x -= this.width / 2;
}
else
{
this.x = parentNoteSprite.x;
}
this.y = parentNoteSprite.y;
if (parentNoteSprite.parentNoteSprite == null)
{
this.y += parentNoteSprite.height / 2;
}
else
{
this.y += parentNoteSprite.height - 1;
}
}
}
function set_parentNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
{
this.parentNoteSprite = value;
if (this.parentNoteSprite != null)
{
this.noteData = this.parentNoteSprite.noteData;
this.noteSkin = this.parentNoteSprite.noteSkin;
}
return this.parentNoteSprite;
}
function set_childNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
{
this.childNoteSprite = value;
if (this.parentNoteSprite != null)
{
this.noteData = this.parentNoteSprite.noteData;
this.noteSkin = this.parentNoteSprite.noteSkin;
}
return this.childNoteSprite;
}
function playNoteAnimation()
{
var animationName = 'tap${this.noteData.getDirectionName()}${this.noteSkin}';
this.animation.play(animationName);
}
// Decide whether to display a note or a sustain.
var baseAnimationName:String = 'tap';
if (this.parentNoteSprite != null)
baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
function resizeNote()
{
this.setGraphicSize(ChartEditorState.GRID_SIZE);
// Play the appropriate animation for the type, direction, and skin.
var animationName = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteSkin}';
this.animation.play(animationName);
// Resize note.
switch (baseAnimationName)
{
case 'tap':
this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
case 'hold':
if (parentNoteSprite.parentNoteSprite == null)
{
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
}
else
{
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), ChartEditorState.GRID_SIZE);
}
case 'holdEnd':
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
}
this.updateHitbox();
// TODO: Make this an attribute of the note skin.
this.antialiasing = (noteSkin != 'Pixel');
}
/**
* Return whether this note (or its parent) is currently visible.
*/
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
{
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
if (!outsideViewArea)
{
return true;
}
// TODO: Check if this note's parent or child is visible.
return false;
}
public function getBaseNoteSprite()
{
if (this.parentNoteSprite == null)
return this;
else
return this.parentNoteSprite;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,172 @@
package funkin.ui.debug.charting;
import flixel.addons.display.FlxGridOverlay;
import flixel.addons.display.FlxSliceSprite;
import flixel.math.FlxRect;
import flixel.util.FlxColor;
import openfl.display.BitmapData;
import openfl.geom.Rectangle;
/**
* Available themes for the chart editor state.
*/
enum ChartEditorTheme
{
Light;
Dark;
}
/**
* Static functions which handle building themed UI elements for a provided ChartEditorState.
*/
class ChartEditorThemeHandler
{
// TODO: There's probably a better system of organization for these colors.
// An enum of typedefs or something?
// ================================
static final BACKGROUND_COLOR_LIGHT:FlxColor = 0xFF673AB7;
static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF673AB7;
// Color 1 of the grid pattern. Alternates with Color 2.
static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6;
static final GRID_COLOR_1_DARK:FlxColor = 0xFF181919;
// Color 2 of the grid pattern. Alternates with Color 1.
static final GRID_COLOR_2_LIGHT:FlxColor = 0xFFD9D5D5;
static final GRID_COLOR_2_DARK:FlxColor = 0xFF262A2A;
// Vertical divider between characters.
static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF000000;
static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = 2;
// Horizontal divider between measures.
static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF000000;
static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
static final GRID_MEASURE_DIVIDER_WIDTH:Float = 2;
// 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;
// Fill on the square highlighting selected notes.
// Make sure this is transparent so you can see the notes underneath.
static final SELECTION_SQUARE_FILL_COLOR_LIGHT:FlxColor = 0x4033FF33;
static final SELECTION_SQUARE_FILL_COLOR_DARK:FlxColor = 0x4033FF33;
// TODO: Un-hardcode these to be based on time signature.
static final STEPS_PER_BEAT:Int = 4;
static final BEATS_PER_MEASURE:Int = 4;
public static function updateTheme(state:ChartEditorState):Void
{
updateBackground(state);
updateGridBitmap(state);
updateSelectionSquare(state);
}
static function updateBackground(state:ChartEditorState):Void
{
state.menuBG.color = switch (state.currentTheme)
{
case ChartEditorTheme.Light: BACKGROUND_COLOR_LIGHT;
case ChartEditorTheme.Dark: BACKGROUND_COLOR_DARK;
default: BACKGROUND_COLOR_LIGHT;
}
}
/**
* Builds the checkerboard background image of the chart editor, and adds dividing lines to it.
* @param dark Whether to draw the grid in a dark color instead of a light one.
*/
static function updateGridBitmap(state:ChartEditorState):Void
{
var gridColor1:FlxColor = switch (state.currentTheme)
{
case Light: GRID_COLOR_1_LIGHT;
case Dark: GRID_COLOR_1_DARK;
default: GRID_COLOR_1_LIGHT;
};
var gridColor2:FlxColor = switch (state.currentTheme)
{
case Light: GRID_COLOR_2_LIGHT;
case Dark: GRID_COLOR_2_DARK;
default: GRID_COLOR_2_LIGHT;
};
// Draw the base grid.
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
// This gets reused to fill the screen.
var gridWidth = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
var gridHeight = ChartEditorState.GRID_SIZE * (STEPS_PER_BEAT * BEATS_PER_MEASURE);
state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1,
gridColor2);
// Draw dividers between the strumlines.
var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme)
{
case Light: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
case Dark: GRID_STRUMLINE_DIVIDER_COLOR_DARK;
default: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
};
// Divider at 1 * (Strumline Size)
var dividerLineAX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
// Divider at 2 * (Strumline Size)
var dividerLineBX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor);
// Draw dividers between the measures.
var gridMeasureDividerColor:FlxColor = switch (state.currentTheme)
{
case Light: GRID_MEASURE_DIVIDER_COLOR_LIGHT;
case Dark: GRID_MEASURE_DIVIDER_COLOR_DARK;
default: GRID_MEASURE_DIVIDER_COLOR_LIGHT;
};
// Divider at top
state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
// Divider at bottom
var dividerLineBY = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2);
state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, GRID_MEASURE_DIVIDER_WIDTH / 2, state.gridBitmap.height), gridMeasureDividerColor);
}
static function updateSelectionSquare(state:ChartEditorState):Void
{
var selectionSquareBorderColor:FlxColor = switch (state.currentTheme)
{
case Light: SELECTION_SQUARE_BORDER_COLOR_LIGHT;
case Dark: SELECTION_SQUARE_BORDER_COLOR_DARK;
default: SELECTION_SQUARE_BORDER_COLOR_LIGHT;
};
var selectionSquareFillColor:FlxColor = switch (state.currentTheme)
{
case Light: SELECTION_SQUARE_FILL_COLOR_LIGHT;
case Dark: SELECTION_SQUARE_FILL_COLOR_DARK;
default: SELECTION_SQUARE_FILL_COLOR_LIGHT;
};
state.selectionSquareBitmap = new BitmapData(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, true);
state.selectionSquareBitmap.fillRect(new Rectangle(0, 0, ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE), selectionSquareBorderColor);
state.selectionSquareBitmap.fillRect(new Rectangle(SELECTION_SQUARE_BORDER_WIDTH, SELECTION_SQUARE_BORDER_WIDTH,
ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2), ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2)),
selectionSquareFillColor);
state.selectionBoxSprite = new FlxSliceSprite(state.selectionSquareBitmap,
new FlxRect(SELECTION_SQUARE_BORDER_WIDTH
+ 4, SELECTION_SQUARE_BORDER_WIDTH
+ 4,
ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 8), ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 8)),
32, 32);
}
}

View file

@ -0,0 +1,184 @@
package funkin.ui.debug.charting;
import haxe.ui.components.DropDown;
import haxe.ui.containers.Group;
import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.events.UIEvent;
/**
* Available tools for the chart editor state.
*/
enum ChartEditorToolMode
{
Select;
Place;
}
class ChartEditorToolboxHandler
{
public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void
{
if (shown)
showToolbox(state, id);
else
hideToolbox(state, id);
}
public static function showToolbox(state:ChartEditorState, id:String)
{
var toolbox:Dialog = state.activeToolboxes.get(id);
if (toolbox == null)
toolbox = initToolbox(state, id);
if (toolbox != null)
{
toolbox.showDialog(false);
}
else
{
trace('ChartEditorToolboxHandler.showToolbox() - Could not retrieve toolbox: $id');
}
}
public static function hideToolbox(state:ChartEditorState, id:String):Void
{
var toolbox:Dialog = state.activeToolboxes.get(id);
if (toolbox == null)
toolbox = initToolbox(state, id);
if (toolbox != null)
{
toolbox.hideDialog(DialogButton.CANCEL);
}
else
{
trace('ChartEditorToolboxHandler.hideToolbox() - Could not retrieve toolbox: $id');
}
}
public static function minimizeToolbox(state:ChartEditorState, id:String):Void
{
}
public static function maximizeToolbox(state:ChartEditorState, id:String):Void
{
}
public static function initToolbox(state:ChartEditorState, id:String):Dialog
{
var toolbox:Dialog = null;
switch (id)
{
case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT:
toolbox = buildToolboxToolsLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:
toolbox = buildToolboxNoteDataLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
toolbox = buildToolboxEventDataLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_SONGDATA_LAYOUT:
toolbox = buildToolboxSongDataLayout(state);
default:
trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id');
toolbox = null;
}
state.activeToolboxes.set(id, toolbox);
return toolbox;
}
static function buildToolboxToolsLayout(state:ChartEditorState):Dialog
{
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT);
// Starting position.
toolbox.x = 50;
toolbox.y = 50;
toolbox.onDialogClosed = (event:DialogEvent) ->
{
state.setUISelected('menubarItemToggleToolboxTools', false);
}
var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group);
toolsGroup.onChange = (event:UIEvent) ->
{
switch (event.target.id)
{
case 'toolboxToolsGroupSelect':
state.currentToolMode = ChartEditorToolMode.Select;
case 'toolboxToolsGroupPlace':
state.currentToolMode = ChartEditorToolMode.Place;
default:
trace('ChartEditorToolboxHandler.buildToolboxToolsLayout() - Unknown toolbox tool selected: $event.target.id');
}
}
return toolbox;
}
static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog
{
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
// Starting position.
toolbox.x = 75;
toolbox.y = 100;
toolbox.onDialogClosed = (event:DialogEvent) ->
{
state.setUISelected('menubarItemToggleToolboxNotes', false);
}
var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown);
toolboxNotesNoteKind.onChange = (event:UIEvent) ->
{
state.selectedNoteKind = event.data.id;
}
return toolbox;
}
static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog
{
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
// Starting position.
toolbox.x = 100;
toolbox.y = 150;
toolbox.onDialogClosed = (event:DialogEvent) ->
{
state.setUISelected('menubarItemToggleToolboxEvents', false);
}
return toolbox;
}
static function buildToolboxSongDataLayout(state:ChartEditorState):Dialog
{
var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_SONGDATA_LAYOUT);
// Starting position.
toolbox.x = 950;
toolbox.y = 50;
toolbox.onDialogClosed = (event:DialogEvent) ->
{
state.setUISelected('menubarItemToggleToolboxSong', false);
}
return toolbox;
}
static function buildDialog(state:ChartEditorState, id:String):Dialog
{
var dialog:Dialog = cast state.buildComponent(id);
dialog.destroyOnClose = false;
return dialog;
}
}

View file

@ -0,0 +1,3 @@
# funkin.ui.haxeui.components
Since there is a line in `source/module.xml` pointing to this folder, all components in this folder will automatically be accessible in any HaxeUI layouts.

View file

@ -6,27 +6,29 @@ import lime.app.Application;
class Constants
{
/**
* The scale factor to use when increasing the size of pixel art graphics.
* ENGINE AND VERSION DATA
*/
public static final PIXEL_ART_SCALE = 6;
// ==============================
public static final HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
public static final HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
public static final COUNTDOWN_VOLUME = 0.6;
public static final VERSION_SUFFIX = ' PROTOTYPE';
public static var VERSION(get, null):String;
public static final FREAKY_MENU_BPM = 102;
// Change this if you're making an engine.
/**
* The title of the game, for debug printing purposes.
* Change this if you're making an engine.
*/
public static final TITLE = "Friday Night Funkin'";
#if debug
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash();
public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch();
/**
* The current version number of the game.
* Modify this in the `project.xml` file.
*/
public static var VERSION(get, null):String;
/**
* A suffix to add to the game version.
* Add a suffix to prototype builds and remove it for releases.
*/
public static final VERSION_SUFFIX = ' PROTOTYPE';
#if debug
static function get_VERSION():String
{
return 'v${Application.current.meta.get('version')} (${GIT_BRANCH} : ${GIT_HASH})' + VERSION_SUFFIX;
@ -38,6 +40,74 @@ class Constants
}
#end
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
/**
* URL DATA
*/
// ==============================
/**
* Link to download the game on Itch.io.
*/
public static final URL_ITCH:String = "https://ninja-muffin24.itch.io/funkin/purchase";
/**
* Link to the game's page on Kickstarter.
*/
public static final URL_KICKSTARTER:String = "https://www.kickstarter.com/projects/funkin/friday-night-funkin-the-full-ass-game/";
/**
* GIT REPO DATA
*/
// ==============================
#if debug
/**
* The current Git branch.
*/
public static final GIT_BRANCH = funkin.util.macro.GitCommit.getGitBranch();
/**
* The current Git commit hash.
*/
public static final GIT_HASH = funkin.util.macro.GitCommit.getGitCommitHash();
#end
/**
* COLORS
*/
// ==============================
/**
* The color used by the enemy health bar.
*/
public static final COLOR_HEALTH_BAR_RED:FlxColor = 0xFFFF0000;
/**
* The color used by the player health bar.
*/
public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
/**
* OTHER
*/
// ==============================
/**
* The scale factor to use when increasing the size of pixel art graphics.
*/
public static final PIXEL_ART_SCALE = 6;
/**
* The BPM of the title screen and menu music.
* TODO: Move to metadata file.
*/
public static final FREAKY_MENU_BPM = 102;
/**
* The volume at which to play the countdown before the song starts.
*/
public static final COUNTDOWN_VOLUME = 0.6;
public static final DEFAULT_VARIATION = 'default';
public static final DEFAULT_DIFFICULTY = 'normal';
}