1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-12-08 04:58:48 +00:00

Compare commits

...

29 commits

Author SHA1 Message Date
PurSnake 4369c72ed9 Charselect cursor's little animation in an in/out motion sequence 2025-12-07 16:51:28 +03:00
AbnormalPoof 758f712eb5 Add global offsets for Pico and Nene's pixel variants 2025-12-07 01:40:24 -07:00
Furo 01029817c0 Added funnyColor backwards compatibility 2025-12-07 01:40:20 -07:00
Furo 8a31e12d10 Change BGScrollingText to a single FlxText
BGScrollingText is now a single FlxText drawn multiple time in a single frame which is more optimized that a FlxSpriteGroup with multiple FlxSprites taking the text's graphic
2025-12-07 01:40:20 -07:00
EliteMasterEric 90ab04caa1 Tweak vertical offset on the measure numbers. 2025-12-07 01:40:20 -07:00
EliteMasterEric ba7f89b9a2 Redo the backing color of the measure ticks. 2025-12-07 01:40:20 -07:00
EliteMasterEric ee36cbbbcf Rewrite measure ticks to not get redrawn EVERY FRAME GODAM 2025-12-07 01:40:20 -07:00
Hyper_ d74b8fb4ca fix: Cursor not properly fading out when exiting CharSelect with backspace 2025-12-07 01:40:20 -07:00
Cameron Taylor 8fa622e147 remove unused pitch stuff in CharSelect 2025-12-07 01:40:20 -07:00
Cameron Taylor a12950d5a7 fix char select inputs for keyb and controllers 2025-12-07 01:40:20 -07:00
EliteMasterEric 71bc847698 Clean up the Lime target config dropdown by removing unused targets like AIR and Flash. 2025-12-07 01:39:25 -07:00
AbnormalPoof a560bf51a9 Re-export Pico's PERFECT rank animation with BTA 2025-12-07 01:39:25 -07:00
AbnormalPoof 866e5aa008 Fix the cursor lerping from the top left in character select 2025-12-07 01:39:22 -07:00
AbnormalPoof 3c213ad45c Add getFramesWithKeyword() 2025-12-07 01:39:22 -07:00
anysad a0b933ce8f Remove unused Chart Editor Context Menu code 2025-12-07 01:39:22 -07:00
MAJigsaw77 671e1435d2 Bump hxvlc to 2.2.5. 2025-11-30 19:20:23 -07:00
MAJigsaw77 369dad3951 Remove the yellow notice. 2025-11-30 19:20:23 -07:00
MAJigsaw77 8ecde809e6 Improve library version checks. 2025-11-30 19:20:23 -07:00
AbnormalPoof c4394f0d12 Lower him a little 2025-11-30 19:19:54 -07:00
AbnormalPoof 63cbaef4c4 Re-export Boyfriend (Car) to texture atlas 2025-11-30 19:19:51 -07:00
AbnormalPoof 4120b20a24 Revert "remove keyboard "scheme" related code, completely unused"
This reverts commit 7c8b7eee578b4c5caabf194d645b1473f233dd46.
2025-11-30 19:19:47 -07:00
EliteMasterEric a40972926d Update HXCPP to support building with C++20 2025-11-30 19:19:47 -07:00
Cameron Taylor 368531f6fa update our custom FunkinAction check() function with newer logic from FlxAction 2025-11-29 04:51:05 -07:00
Cameron Taylor b9d4ce70cc remove the unused _P and _R stuff from the Action enum abstract 2025-11-29 04:51:05 -07:00
Cameron Taylor 6ef3fe4faf more controls.hx cleanup, remove a few unused functions, and add Void return types to make checkstyle happier 2025-11-29 04:51:04 -07:00
Cameron Taylor 8abc9ab306 remove keyboard "scheme" related code, completely unused 2025-11-29 04:51:04 -07:00
Cameron Taylor 8349999833 remove more unused vars in controls.hx 2025-11-29 04:51:04 -07:00
AbnormalPoof cf52bcdf65 Adjust Darnell's idle animation 2025-11-29 04:50:41 -07:00
AbnormalPoof 4e03cf0a5d Re-export THE CHUDDER!!!! 2025-11-29 04:50:39 -07:00
13 changed files with 619 additions and 620 deletions

112
.vscode/settings.json vendored
View file

@ -1,31 +1,22 @@
{
"[haxe]": {
// Automatically keep Haxe files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "never"
},
"editor.codeActionsOnSave": { "source.organizeImports": "never" },
"editor.defaultFormatter": "nadako.vshaxe",
"editor.tabSize": 2
},
"[json]": {
// Automatically keep JSON files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
// Automatically keep JSONC files formatted.
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.tabWidth": 2,
// XML formatting style configuration
// XML Formatting
"xml.format.enabled": true,
"xml.format.legacy": false,
"xml.format.emptyElements": "collapse",
@ -33,7 +24,7 @@
"xml.format.enforceQuoteStyle": "preferred",
"xml.format.preserveAttributeLineBreaks": false,
"xml.format.preservedNewlines": 0,
"xml.format.splitAttributes": false,
"xml.format.splitAttributes": "preserve",
"xml.format.joinCDATALines": true,
"xml.format.preserveEmptyContent": false,
"xml.format.joinCommentLines": false,
@ -56,26 +47,19 @@
"xml.format.maxLineWidth": 0,
"xml.format.grammarAwareFormatting": true,
// Generic file formatting style configuration
// General formatting
"files.insertFinalNewline": true,
"files.trimFinalNewlines": false,
"files.trimTrailingWhitespace": true,
// Automatically detect indentation.
"editor.detectIndentation": true,
"editor.insertSpaces": true,
"editor.tabSize": 2,
// Automatically enforce Linux style line endings.
"prettier.tabWidth": 2,
"files.eol": "\n",
"haxe.displayPort": "auto",
"haxe.enableCompilationServer": true,
"haxe.enableServerView": true,
"haxe.displayServer": {
"arguments": ["-v"]
},
// Fix file associations for HScript.
"haxe.displayServer": { "arguments": ["-v"] },
"files.associations": {
"*.hxp": "haxe",
"*.hscript": "haxe",
@ -84,14 +68,27 @@
"*.hxc": "haxe"
},
"projectManager.git.baseFolders": ["./"],
"haxecheckstyle.sourceFolders": ["src", "source"],
"haxecheckstyle.externalSourceRoots": [],
"haxecheckstyle.configurationFile": "checkstyle.json",
"haxecheckstyle.codeSimilarityBufferSize": 100,
"lime.projectFile": "project.hxp",
"lime.targets": [
{ "name": "windows", "enabled": true, "label": "Windows" },
{ "name": "mac", "enabled": true, "label": "macOS" },
{ "name": "linux", "enabled": true, "label": "Linux" },
{ "name": "html5", "enabled": true, "label": "HTML5" },
{ "name": "android", "enabled": true, "label": "Android" },
{ "name": "ios", "enabled": true, "label": "iOS" },
// Disabled targets
{ "name": "hl", "enabled": false, "label": "HashLink" },
{ "name": "air", "enabled": false },
{ "name": "electron", "enabled": false },
{ "name": "flash", "enabled": false },
{ "name": "neko", "enabled": false },
{ "name": "tvos", "enabled": false }
],
"lime.defaultTargetConfiguration": "Windows / Debug",
"lime.targetConfigurations": [
{
"label": "Windows / Debug (Discord)",
@ -103,21 +100,11 @@
"target": "windows",
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (FlxAnimate Test)",
"target": "hl",
"args": ["-debug", "-DANIMATE"]
},
{
"label": "Windows / Debug (Straight to Freeplay)",
"target": "windows",
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Freeplay)",
"target": "hl",
"args": ["-debug", "-DFREEPLAY"]
},
{
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
"target": "windows",
@ -132,26 +119,6 @@
"target": "windows",
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
"target": "hl",
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
},
{
"label": "Windows / Debug (Conversation Test)",
"target": "windows",
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Conversation Test)",
"target": "hl",
"args": ["-debug", "-DDIALOGUE"]
},
{
"label": "Windows / Debug (Results Screen Test)",
"target": "windows",
"args": ["-debug", "-DRESULTS"]
},
{
"label": "Windows / Debug (Straight to Stage Editor)",
"target": "windows",
@ -172,36 +139,16 @@
"target": "windows",
"args": ["-debug", "-DHXVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Straight to Animation Editor)",
"target": "hl",
"args": ["-debug", "-DANIMDEBUG"]
},
{
"label": "Windows / Debug (Latency Test)",
"target": "windows",
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "HashLink / Debug (Latency Test)",
"target": "hl",
"args": ["-debug", "-DLATENCY"]
},
{
"label": "Windows / Debug (Waveform Test)",
"target": "windows",
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "Windows / Release (GitHub Actions)",
"target": "windows",
"args": ["-release", "-DGITHUB_BUILD"]
},
{
"label": "HashLink / Debug (Waveform Test)",
"target": "hl",
"args": ["-debug", "-DWAVEFORM"]
},
{
"label": "HTML5 / Debug (Watch)",
"target": "html5",
@ -209,10 +156,7 @@
}
],
"lime.buildTypes": [
{
"label": "Debug",
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{ "label": "Debug", "args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"] },
{
"label": "Debug (Unlock Everything)",
"args": ["-debug", "-DUNLOCK_EVERYTHING"]
@ -225,10 +169,7 @@
"label": "Debug (Straight to Chart Editor)",
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
},
{
"label": "Release",
"args": ["-release"]
},
{ "label": "Release", "args": ["-release"] },
{
"label": "Release (GitHub Actions)",
"args": ["-release", "-DGITHUB_BUILD"]
@ -244,9 +185,6 @@
],
"vscord.app.privacyMode.enable": true,
"json.schemas": [
{
"fileMatch": ["/hmm.json"],
"url": "./.vscode/schema/hmm.json"
}
{ "fileMatch": ["/hmm.json"], "url": "./.vscode/schema/hmm.json" }
]
}

2
assets

@ -1 +1 @@
Subproject commit 64d4667bcb19dab26aa9f7b64da22a6c684ac090
Subproject commit 1a961f111381eb3bfc452166c4e4b5a18b409781

View file

@ -111,7 +111,7 @@
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "6546fa5c3ad1bac065f144745122ab5a6d4195ff",
"ref": "5932340d095a7eea8635fe4d1355f1c0efd0b3c2",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
@ -146,7 +146,7 @@
{
"name": "hxvlc",
"type": "haxelib",
"version": "2.2.4"
"version": "2.2.5"
},
{
"name": "json2object",

View file

@ -2049,7 +2049,7 @@ class Project extends HXProject
@SuppressWarnings('checkstyle:Dynamic')
function checkLibraries():Void
{
var outdatedLibraries:Map<String, Array<String>> = new Map<String, Array<String>>();
var diffrentLibraries:Map<String, Array<String>> = new Map<String, Array<String>>();
var hmmData:Dynamic = haxe.Json.parse(sys.io.File.getContent(#if ios '../../../../../' + #end 'hmm.json'));
@ -2063,53 +2063,71 @@ class Project extends HXProject
var libraryCurrentVersion:String = readLibraryCurrentVersion(libraryName);
switch (library.type)
{
case 'haxelib':
if (libraryDev != "")
{
outdatedLibraries.set(libraryName, [libraryDev, library.version]);
}
else if (library.version != libraryCurrentVersion)
{
outdatedLibraries.set(libraryName, [libraryCurrentVersion, library.version]);
}
case 'git':
if (libraryDev != "" && !isLibraryGitDev(libraryName))
{
outdatedLibraries.set(libraryName, [libraryDev, library.ref]);
}
else
{
var commitHash:String = readLibraryGitCommitHash(libraryName);
var libraryCurrentCommitHash:String = readLibraryGitCommitHash(libraryName);
if (commitHash != library.ref && commitHash != "")
if (libraryDev != "" && !isLibraryLocalGitDev(libraryName))
{
switch (library.type)
{
case 'haxelib':
diffrentLibraries.set(libraryName, [libraryDev, libraryCurrentCommitHash, library.version, 'haxelib']);
case 'git':
if (libraryCurrentCommitHash != library.ref)
{
outdatedLibraries.set(libraryName, [commitHash, library.ref]);
diffrentLibraries.set(libraryName, [libraryDev, libraryCurrentCommitHash, library.ref, 'git']);
}
}
}
}
else
{
switch (library.type)
{
case 'haxelib':
if (library.version != libraryCurrentVersion)
{
diffrentLibraries.set(libraryName, ['', libraryCurrentVersion, library.version, 'haxelib']);
}
case 'git':
if (libraryCurrentCommitHash != library.ref)
{
diffrentLibraries.set(libraryName, ['', libraryCurrentCommitHash, library.ref, 'git']);
}
}
}
}
if (Lambda.count(outdatedLibraries) > 0)
if (Lambda.count(diffrentLibraries) > 0)
{
warn("The following haxelibs diverge from the versions set in hmm.json.".bold().yellow());
warn("They may be outdated, so it is recommended to abort compilation and run `hmm reinstall [library]` to update each library.".bold().yellow());
warn("You may ignore this warning if your libraries are newer than the versions in hmm.json, or if you know what you're doing.".bold().yellow());
warn("Some libraries differ from the versions defined in hmm.json.".bold().yellow());
warn("To ensure consistency, it's recommended to stop the build and run `hmm reinstall [library]`.".bold().yellow());
warn("If you're intentionally using development builds or newer versions, you can ignore this warning.".bold().yellow());
Sys.println('');
for (libraryName in outdatedLibraries.keys())
for (libraryName in diffrentLibraries.keys())
{
var versions:Null<Array<String>> = outdatedLibraries.get(libraryName);
if (versions == null) continue;
var infos:Null<Array<String>> = diffrentLibraries.get(libraryName);
var outdatedVersion:String = versions[0];
var expectedVersion:String = versions[1];
if (infos == null) continue;
Sys.println("- " + libraryName.replace(",", ".") + (haxe.io.Path.isAbsolute(outdatedVersion) ? " (Development Build)".blue().bold() : ""));
Sys.println((" Current version: " + outdatedVersion).red());
Sys.println((" Expected version: " + expectedVersion).green());
var devPath:String = infos[0];
var currentVersion:String = infos[1];
var expectedVersion:String = infos[2];
var libType:String = infos[3];
if (haxe.io.Path.isAbsolute(devPath))
{
Sys.println("- " + libraryName.replace(",", ".") + " (Development Build)".blue().bold());
Sys.println((" Path: " + haxe.io.Path.removeTrailingSlashes(devPath)).blue());
}
else
{
Sys.println("- " + libraryName.replace(",", "."));
}
Sys.println((" Current: " + currentVersion).red());
Sys.println((" Expected: " + expectedVersion).green());
}
Sys.println('');
@ -2136,11 +2154,20 @@ class Project extends HXProject
static function readLibraryGitCommitHash(libraryName:String):String
{
var commit:String = '';
var gitProccess = new sys.io.Process("git", ["-C", getLibraryGitPath(libraryName), "rev-parse", "HEAD"]);
gitProccess.exitCode(true);
commit = gitProccess.stdout.readAll().toString().trim();
return gitProccess.stdout.readAll().toString().trim();
if (commit.length <= 0)
{
gitProccess = new sys.io.Process("git", ["-C", readLibraryDev(libraryName), "rev-parse", "HEAD"]);
gitProccess.exitCode(true);
commit = gitProccess.stdout.readAll().toString().trim();
}
return commit;
}
static function getLibraryCurrentFile(libraryName:String):String
@ -2158,7 +2185,7 @@ class Project extends HXProject
return haxe.io.Path.join([haxe.io.Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, 'git']);
}
static function isLibraryGitDev(libraryName:String):Bool
static function isLibraryLocalGitDev(libraryName:String):Bool
{
final gitPath:String = getLibraryGitPath(libraryName);
final devFile:String = getLibraryDevFile(libraryName);

View file

@ -451,6 +451,46 @@ class FunkinSprite extends FlxAnimate
return false;
}
/**
* Gets every frame on every symbol that starts with the given keyword.
* @param keyword The keyword to search for.
* @return An array of frames.
*/
public function getFramesWithKeyword(keyword:String):Array<animate.internal.Frame>
{
if (!this.isAnimate)
{
trace('WARNING: getFramesWithKeyword() only works texture atlases!');
return [];
}
var symbolItems:Array<animate.internal.SymbolItem> = [];
var frames:Array<animate.internal.Frame> = [];
@:privateAccess
for (symbol in this.library.dictionary.keys())
{
var symbolItem:Null<animate.internal.SymbolItem> = this.library.getSymbol(symbol);
if (symbolItem == null) continue;
if (symbolItem.name.contains(keyword))
{
symbolItems.push(symbolItem);
}
}
for (symbolItem in symbolItems)
{
symbolItem.timeline.forEachLayer((layer) -> {
layer.forEachFrame((frame) -> {
frames.push(frame);
});
});
}
return frames;
}
/**
* Gets the current animation ID.
*/

View file

@ -19,19 +19,20 @@ import flixel.math.FlxPoint;
*/
class Controls extends FlxActionSet
{
/**
/*
* A list of actions that a player would invoke via some input device.
* Uses FlxActions to funnel various inputs to a single action.
*/
var _ui_up = new FunkinAction(Action.UI_UP);
var _ui_left = new FunkinAction(Action.UI_LEFT);
var _ui_right = new FunkinAction(Action.UI_RIGHT);
var _ui_down = new FunkinAction(Action.UI_DOWN);
var _note_up = new FunkinAction(Action.NOTE_UP);
var _note_left = new FunkinAction(Action.NOTE_LEFT);
var _note_right = new FunkinAction(Action.NOTE_RIGHT);
var _note_down = new FunkinAction(Action.NOTE_DOWN);
var _accept = new FunkinAction(Action.ACCEPT);
var _back = new FunkinAction(Action.BACK);
var _pause = new FunkinAction(Action.PAUSE);
@ -126,26 +127,6 @@ class Controls extends FlxActionSet
inline function get_UI_DOWN_R()
return _ui_down.checkJustReleased();
public var UI_UP_GAMEPAD(get, never):Bool;
inline function get_UI_UP_GAMEPAD()
return _ui_up.checkPressedGamepad();
public var UI_LEFT_GAMEPAD(get, never):Bool;
inline function get_UI_LEFT_GAMEPAD()
return _ui_left.checkPressedGamepad();
public var UI_RIGHT_GAMEPAD(get, never):Bool;
inline function get_UI_RIGHT_GAMEPAD()
return _ui_right.checkPressedGamepad();
public var UI_DOWN_GAMEPAD(get, never):Bool;
inline function get_UI_DOWN_GAMEPAD()
return _ui_down.checkPressedGamepad();
public var NOTE_UP(get, never):Bool;
inline function get_NOTE_UP()
@ -527,9 +508,8 @@ class Controls extends FlxActionSet
* Calls a function passing each action bound by the specified control
* @param control
* @param func
* @return ->Void)
*/
function forEachBound(control:Control, func:FunkinAction->FlxInputState->Void)
function forEachBound(control:Control, func:FunkinAction->FlxInputState->Void):Void
{
switch (control)
{
@ -624,7 +604,7 @@ class Controls extends FlxActionSet
}
}
public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int)
public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int):Void
{
if (toAdd == toRemove) return;
@ -638,7 +618,7 @@ class Controls extends FlxActionSet
}
}
function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState)
function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState):Void
{
if (action.inputs.length == 0)
{
@ -689,7 +669,7 @@ class Controls extends FlxActionSet
}
}
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState)
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState):Void
{
if (action.inputs.length == 0)
{
@ -717,7 +697,7 @@ class Controls extends FlxActionSet
}
}
public function copyFrom(controls:Controls, ?device:Device)
public function copyFrom(controls:Controls, ?device:Device):Void
{
for (name in controls.byName.keys())
{
@ -744,7 +724,7 @@ class Controls extends FlxActionSet
}
}
inline public function copyTo(controls:Controls, ?device:Device)
inline public function copyTo(controls:Controls, ?device:Device):Void
{
controls.copyFrom(this, device);
}
@ -767,7 +747,7 @@ class Controls extends FlxActionSet
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
* If binder is a literal you can inline this
*/
public function bindKeys(control:Control, keys:Array<FlxKey>)
public function bindKeys(control:Control, keys:Array<FlxKey>):Void
{
forEachBound(control, function(action, state) addKeys(action, keys, state));
}
@ -776,12 +756,12 @@ class Controls extends FlxActionSet
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
* If binder is a literal you can inline this
*/
public function unbindKeys(control:Control, keys:Array<FlxKey>)
public function unbindKeys(control:Control, keys:Array<FlxKey>):Void
{
forEachBound(control, function(action, _) removeKeys(action, keys));
}
static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState)
static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState):Void
{
for (key in keys)
{
@ -790,7 +770,7 @@ class Controls extends FlxActionSet
}
}
static function removeKeys(action:FlxActionDigital, keys:Array<FlxKey>)
static function removeKeys(action:FlxActionDigital, keys:Array<FlxKey>):Void
{
var i = action.inputs.length;
while (i-- > 0)
@ -951,7 +931,7 @@ class Controls extends FlxActionSet
return [];
}
function removeKeyboard()
function removeKeyboard():Void
{
for (action in this.digitalActions)
{
@ -971,16 +951,6 @@ class Controls extends FlxActionSet
fromSaveData(padData, Gamepad(id));
}
public function getGamepadIds():Array<Int>
{
return gamepadsAdded;
}
public function getGamepads():Array<FlxGamepad>
{
return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)];
}
inline function addGamepadLiteral(id:Int, ?buttonMap:Map<Control, Array<FlxGamepadInputID>>):Void
{
gamepadsAdded.push(id);
@ -1004,7 +974,7 @@ class Controls extends FlxActionSet
gamepadsAdded.remove(deviceID);
}
public function addDefaultGamepad(id):Void
public function addDefaultGamepad(id:Int):Void
{
addGamepadLiteral(id, [
Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT),
@ -1048,32 +1018,32 @@ class Controls extends FlxActionSet
function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID>
{
switch (control)
return switch (control)
{
case Control.ACCEPT:
return [#if switch B #else A #end];
[A];
case Control.BACK:
return [#if switch A #else B #end];
[B];
case Control.UI_UP:
return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
[DPAD_UP, LEFT_STICK_DIGITAL_UP];
case Control.UI_DOWN:
return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
[DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
case Control.UI_LEFT:
return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
[DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
case Control.UI_RIGHT:
return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT];
[DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT];
case Control.NOTE_UP:
return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP];
[DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP];
case Control.NOTE_DOWN:
return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN];
[DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN];
case Control.NOTE_LEFT:
return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
[DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
case Control.NOTE_RIGHT:
return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
[DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
case Control.PAUSE:
return [START];
[START];
case Control.RESET:
return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
[FlxGamepadInputID.BACK]; // Back (i.e. Select)
case Control.WINDOW_FULLSCREEN:
[];
#if FEATURE_SCREENSHOTS
@ -1081,19 +1051,19 @@ class Controls extends FlxActionSet
[];
#end
case Control.CUTSCENE_ADVANCE:
return [A];
[A];
case Control.FREEPLAY_FAVORITE:
return [Y]; // Back (i.e. Select)
[Y]; // Back (i.e. Select)
case Control.FREEPLAY_LEFT:
return [LEFT_SHOULDER];
[LEFT_SHOULDER];
case Control.FREEPLAY_RIGHT:
return [RIGHT_SHOULDER];
[RIGHT_SHOULDER];
case Control.FREEPLAY_CHAR_SELECT:
return [X];
[X];
case Control.FREEPLAY_JUMP_TO_TOP:
return [RIGHT_STICK_DIGITAL_UP];
[RIGHT_STICK_DIGITAL_UP];
case Control.FREEPLAY_JUMP_TO_BOTTOM:
return [RIGHT_STICK_DIGITAL_DOWN];
[RIGHT_STICK_DIGITAL_DOWN];
case Control.VOLUME_UP:
[];
case Control.VOLUME_DOWN:
@ -1115,16 +1085,15 @@ class Controls extends FlxActionSet
case Control.DEBUG_DISPLAY:
[];
default:
// Fallthrough.
[];
}
return [];
}
/**
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
* If binder is a literal you can inline this
*/
public function bindButtons(control:Control, id, buttons)
public function bindButtons(control:Control, id:Int, buttons):Void
{
forEachBound(control, function(action, state) addButtons(action, buttons, state, id));
}
@ -1133,12 +1102,12 @@ class Controls extends FlxActionSet
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
* If binder is a literal you can inline this
*/
public function unbindButtons(control:Control, gamepadID:Int, buttons)
public function unbindButtons(control:Control, gamepadID:Int, buttons):Void
{
forEachBound(control, function(action, _) removeButtons(action, gamepadID, buttons));
}
inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id)
inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id:Int):Void
{
for (button in buttons)
{
@ -1147,7 +1116,7 @@ class Controls extends FlxActionSet
}
}
static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array<FlxGamepadInputID>)
static function removeButtons(action:FlxActionDigital, gamepadID:Int, buttons:Array<FlxGamepadInputID>):Void
{
var i = action.inputs.length;
while (i-- > 0)
@ -1177,17 +1146,6 @@ class Controls extends FlxActionSet
return list;
}
public function removeDevice(device:Device)
{
switch (device)
{
case Keys:
setKeyboardScheme(None);
case Gamepad(id):
removeGamepad(id);
}
}
/**
* NOTE: When loading controls:
* An EMPTY array means the control is uninitialized and needs to be reset to default.
@ -1270,7 +1228,7 @@ class Controls extends FlxActionSet
return isEmpty ? null : data;
}
static function isDevice(input:FlxActionInput, device:Device)
static function isDevice(input:FlxActionInput, device:Device):Bool
{
return switch (device)
{
@ -1279,7 +1237,7 @@ class Controls extends FlxActionSet
}
}
inline static function isGamepad(input:FlxActionInput, deviceID:Int)
inline static function isGamepad(input:FlxActionInput, deviceID:Int):Bool
{
return input.device == GAMEPAD && (deviceID == FlxInputDeviceID.ALL || input.deviceID == deviceID);
}
@ -1393,14 +1351,8 @@ class FunkinAction extends FlxActionDigital
public function checkMultiFiltered(?filterTriggers:Array<FlxInputState>, ?filterDevices:Array<FlxInputDevice>):Bool
{
if (filterTriggers == null)
{
filterTriggers = [PRESSED, JUST_PRESSED];
}
if (filterDevices == null)
{
filterDevices = [];
}
filterTriggers ??= [PRESSED, JUST_PRESSED];
filterDevices ??= [];
// Perform checkFiltered for each combination.
for (i in filterTriggers)
@ -1431,6 +1383,7 @@ class FunkinAction extends FlxActionDigital
* @param action The action to check for.
* @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`).
* @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`).
* @return bool if our input has been triggered
*/
public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool
{
@ -1442,20 +1395,22 @@ class FunkinAction extends FlxActionDigital
{
return cacheEntry.value;
}
// Use a for loop instead so we can remove inputs while iterating.
// We don't return early because we need to call check() on ALL inputs.
var result = false;
var len = inputs != null ? inputs.length : 0;
for (i in 0...len)
_x = null;
_y = null;
_timestamp = FlxG.game.ticks;
triggered = false;
var i = inputs?.length ?? 0;
while (i-- > 0) // Iterate backwards, since we may remove items
{
var j = len - i - 1;
var input = inputs[j];
var input = inputs[i];
// Filter out dead inputs.
if (input.destroyed)
{
inputs.splice(j, 1);
inputs.remove(input);
continue;
}
@ -1477,14 +1432,13 @@ class FunkinAction extends FlxActionDigital
// Check whether the input has triggered.
if (input.check(this))
{
result = true;
triggered = true;
}
}
// We need to cache this result.
cache.set(key, {timestamp: FlxG.game.ticks, value: result});
cache.set(key, {timestamp: FlxG.game.ticks, value: triggered});
return result;
return triggered;
}
}
@ -1505,10 +1459,10 @@ enum Control
UI_DOWN;
UI_UP;
UI_RIGHT;
RESET;
ACCEPT;
BACK;
PAUSE;
RESET;
// CUTSCENE
CUTSCENE_ADVANCE;
// FREEPLAY
@ -1539,27 +1493,11 @@ enum abstract Action(String) to String from String
var NOTE_LEFT = "note_left";
var NOTE_RIGHT = "note_right";
var NOTE_DOWN = "note_down";
var NOTE_UP_P = "note_up-press";
var NOTE_LEFT_P = "note_left-press";
var NOTE_RIGHT_P = "note_right-press";
var NOTE_DOWN_P = "note_down-press";
var NOTE_UP_R = "note_up-release";
var NOTE_LEFT_R = "note_left-release";
var NOTE_RIGHT_R = "note_right-release";
var NOTE_DOWN_R = "note_down-release";
// UI
var UI_UP = "ui_up";
var UI_LEFT = "ui_left";
var UI_RIGHT = "ui_right";
var UI_DOWN = "ui_down";
var UI_UP_P = "ui_up-press";
var UI_LEFT_P = "ui_left-press";
var UI_RIGHT_P = "ui_right-press";
var UI_DOWN_P = "ui_down-press";
var UI_UP_R = "ui_up-release";
var UI_LEFT_R = "ui_left-release";
var UI_RIGHT_R = "ui_right-release";
var UI_DOWN_R = "ui_down-release";
var ACCEPT = "accept";
var BACK = "back";
var PAUSE = "pause";

View file

@ -10,6 +10,9 @@ import funkin.util.MathUtil;
class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
{
/**
* The main cursor sprite for this class.
*/
public var main:FunkinSprite;
var lightBlue:FunkinSprite;
@ -18,6 +21,8 @@ class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
var cursorConfirmed:FunkinSprite;
var cursorDenied:FunkinSprite;
var cursorsIntroOutroOffset:Float = 240;
public function new()
{
super();
@ -56,6 +61,7 @@ class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
add(cursorDenied);
scrollFactor.set();
directAlpha = true;
}
public function confirm():Void
@ -86,6 +92,26 @@ class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
main.visible = lightBlue.visible = darkBlue.visible = true;
}
/**
* Snaps the cursors to the given position.
* @param intendedPosition The position to snap to as a `FlxPoint`.
*/
public function snapToLocation(intendedPosition:FlxPoint):Void
{
main.setPosition(intendedPosition.x, intendedPosition.y);
// Using intendedPosition since we anyway snapping them
lightBlue.setPosition(intendedPosition.x, intendedPosition.y);
darkBlue.setPosition(intendedPosition.x, intendedPosition.y);
cursorConfirmed.setPosition(main.x - 2, main.x - 4);
cursorDenied.setPosition(main.x - 2, main.x - 4);
}
/**
* Lerps the cursors to the given position.
* @param intendedPosition The position to lerp to as a `FlxPoint`.
*/
public function lerpToLocation(intendedPosition:FlxPoint):Void
{
main.x = MathUtil.snap(MathUtil.smoothLerpPrecision(main.x, intendedPosition.x, FlxG.elapsed, 0.1), intendedPosition.x, 1);
@ -103,4 +129,29 @@ class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
cursorDenied.x = main.x - 2;
cursorDenied.y = main.y - 4;
}
public function slide(slideIn:Bool)
{
if (slideIn)
{
main.alpha = 0;
lightBlue.alpha = 0;
darkBlue.alpha = 0;
main.y += cursorsIntroOutroOffset;
lightBlue.y += cursorsIntroOutroOffset;
darkBlue.y += cursorsIntroOutroOffset;
FlxTween.tween(main, {alpha: 1, y: main.y - cursorsIntroOutroOffset}, 0.9, {ease: FlxEase.expoOut});
FlxTween.tween(lightBlue, {alpha: 1, y: lightBlue.y - cursorsIntroOutroOffset}, 0.95, {ease: FlxEase.expoOut});
FlxTween.tween(darkBlue, {alpha: 1, y: darkBlue.y - cursorsIntroOutroOffset}, 1.0, {ease: FlxEase.expoOut});
}
else
{
FlxTween.tween(main, {alpha: 0, y: main.y + cursorsIntroOutroOffset}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(lightBlue, {alpha: 0, y: lightBlue.y + cursorsIntroOutroOffset}, 0.82, {ease: FlxEase.backIn});
FlxTween.tween(darkBlue, {alpha: 0, y: darkBlue.y + cursorsIntroOutroOffset}, 0.84, {ease: FlxEase.backIn});
FlxTween.tween(cursorConfirmed, {alpha: 0, y: cursorConfirmed.y + cursorsIntroOutroOffset}, 0.8, {ease: FlxEase.expoOut});
}
}
}

View file

@ -43,6 +43,11 @@ import funkin.util.TouchUtil;
@:nullSafety
class CharSelectSubState extends MusicBeatSubState
{
/**
* The default index for the cursor.
*/
final DEFAULT_CURSOR_INDEX:Int = 4;
var cursors:CharSelectCursors;
var cursorX:Int = 0;
@ -235,14 +240,17 @@ class CharSelectSubState extends MusicBeatSubState
{
if (charId == rememberedChar)
{
setCursorPosition(pos);
setCursorPosition(pos, true);
break;
}
}
@:bypassAccessor curChar = rememberedChar;
}
else
{
setupPlayerChill(Constants.DEFAULT_CHARACTER);
setCursorPosition(DEFAULT_CURSOR_INDEX, true);
}
var speakers:FunkinSprite = FunkinSprite.createTextureAtlas(cutoutSize - 10, 0, "charSelect/charSelectSpeakers",
{
@ -307,15 +315,12 @@ class CharSelectSubState extends MusicBeatSubState
charHitbox.scrollFactor.set();
selectSound.loadEmbedded(Paths.sound('CS_select'));
selectSound.pitch = 1;
selectSound.volume = 0.7;
FlxG.sound.defaultSoundGroup.add(selectSound);
FlxG.sound.list.add(selectSound);
unlockSound.loadEmbedded(Paths.sound('CS_unlock'));
unlockSound.pitch = 1;
unlockSound.volume = 0;
unlockSound.play(true);
@ -323,18 +328,13 @@ class CharSelectSubState extends MusicBeatSubState
FlxG.sound.list.add(unlockSound);
lockedSound.loadEmbedded(Paths.sound('CS_locked'));
lockedSound.pitch = 1;
lockedSound.volume = 1.;
FlxG.sound.defaultSoundGroup.add(lockedSound);
FlxG.sound.list.add(lockedSound);
staticSound.loadEmbedded(Paths.sound('static loop'));
staticSound.pitch = 1;
staticSound.looped = true;
staticSound.volume = 0.6;
FlxG.sound.defaultSoundGroup.add(staticSound);
@ -357,6 +357,8 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.tween(member, {y: member.y - 300}, 1, {ease: FlxEase.expoOut});
}
cursors.slide(true);
FlxG.debugger.addTrackerProfile(new TrackerProfile(CharSelectSubState, ["curChar", "grpXSpread", "grpYSpread"]));
FlxG.debugger.track(this);
@ -415,7 +417,6 @@ class CharSelectSubState extends MusicBeatSubState
introSound = new FunkinSound();
introSound.loadEmbedded(Paths.sound('CS_Lights'));
introSound.pitch = 1;
introSound.volume = 0;
FlxG.sound.defaultSoundGroup.add(introSound);
@ -683,7 +684,7 @@ class CharSelectSubState extends MusicBeatSubState
}
#end
FlxTween.tween(cursors, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
cursors.slide(false);
FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn});
FlxTween.tween(nametag, {y: nametag.y + 80}, 0.8, {ease: FlxEase.backIn});
@ -722,6 +723,7 @@ class CharSelectSubState extends MusicBeatSubState
var holdTmrLeft:Float = 0;
var holdTmrRight:Float = 0;
var spamDirections:FlxDirectionFlags = NONE;
var initSpam = 0.5;
var mobileDeny:Bool = false;
var mobileAccept:Bool = false;
@ -736,9 +738,6 @@ class CharSelectSubState extends MusicBeatSubState
mobileAccept = false;
if (controls.UI_UP_R || controls.UI_DOWN_R || controls.UI_LEFT_R || controls.UI_RIGHT_R #if FEATURE_TOUCH_CONTROLS || TouchUtil.justReleased #end)
selectSound.pitch = 1;
if (allowInput && !pressedSelect)
{
#if FEATURE_TOUCH_CONTROLS
@ -774,41 +773,6 @@ class CharSelectSubState extends MusicBeatSubState
}
#end
if (controls.UI_UP) holdTmrUp += elapsed;
if (controls.UI_UP_R)
{
holdTmrUp = 0;
spamDirections = spamDirections.without(UP);
}
if (controls.UI_DOWN) holdTmrDown += elapsed;
if (controls.UI_DOWN_R)
{
holdTmrDown = 0;
spamDirections = spamDirections.without(DOWN);
}
if (controls.UI_LEFT) holdTmrLeft += elapsed;
if (controls.UI_LEFT_R)
{
holdTmrLeft = 0;
spamDirections = spamDirections.without(LEFT);
}
if (controls.UI_RIGHT) holdTmrRight += elapsed;
if (controls.UI_RIGHT_R)
{
holdTmrRight = 0;
spamDirections = spamDirections.without(RIGHT);
}
var initSpam = 0.5;
if (holdTmrUp >= initSpam) spamDirections = spamDirections.with(UP);
if (holdTmrDown >= initSpam) spamDirections = spamDirections.with(DOWN);
if (holdTmrLeft >= initSpam) spamDirections = spamDirections.with(LEFT);
if (holdTmrRight >= initSpam) spamDirections = spamDirections.with(RIGHT);
if (controls.UI_UP_P)
{
cursorY -= 1;
@ -841,6 +805,39 @@ class CharSelectSubState extends MusicBeatSubState
selectSound.play(true);
}
if (controls.UI_UP) holdTmrUp += elapsed;
if (controls.UI_UP_R || !controls.UI_UP)
{
holdTmrUp = 0;
spamDirections = spamDirections.without(UP);
}
if (controls.UI_DOWN) holdTmrDown += elapsed;
if (controls.UI_DOWN_R || !controls.UI_DOWN)
{
holdTmrDown = 0;
spamDirections = spamDirections.without(DOWN);
}
if (controls.UI_LEFT) holdTmrLeft += elapsed;
if (controls.UI_LEFT_R || !controls.UI_LEFT)
{
holdTmrLeft = 0;
spamDirections = spamDirections.without(LEFT);
}
if (controls.UI_RIGHT) holdTmrRight += elapsed;
if (controls.UI_RIGHT_R || !controls.UI_RIGHT)
{
holdTmrRight = 0;
spamDirections = spamDirections.without(RIGHT);
}
if (holdTmrUp >= initSpam) spamDirections = spamDirections.with(UP);
if (holdTmrDown >= initSpam) spamDirections = spamDirections.with(DOWN);
if (holdTmrLeft >= initSpam) spamDirections = spamDirections.with(LEFT);
if (holdTmrRight >= initSpam) spamDirections = spamDirections.with(RIGHT);
if (controls.BACK_P) goBack();
}
@ -895,7 +892,7 @@ class CharSelectSubState extends MusicBeatSubState
cursors.confirm();
FlxG.sound.play(Paths.sound('CS_confirm'));
FunkinSound.playOnce(Paths.sound('CS_confirm'));
dispatchEvent(new CharacterSelectScriptEvent(CHARACTER_CONFIRMED, curChar));
@ -1124,8 +1121,7 @@ class CharSelectSubState extends MusicBeatSubState
return gridPosition;
}
// Moved this code into a function because is now used twice
function setCursorPosition(index:Int)
function setCursorPosition(index:Int, instant:Bool = false):Void
{
var copy = 3;
var yThing = -1;
@ -1141,6 +1137,17 @@ class CharSelectSubState extends MusicBeatSubState
// Look, I'd write better code but I had better aneurysms, my bad - Cheems
cursorY = yThing;
cursorX = xThing;
if (instant)
{
cursorLocIntended.x = (cursorFactor * cursorX) + (FlxG.width / 2) - cursors.main.width / 2;
cursorLocIntended.y = (cursorFactor * cursorY) + (FlxG.height / 2) - cursors.main.height / 2;
cursorLocIntended.x += cursorOffsetX;
cursorLocIntended.y += cursorOffsetY;
cursors.snapToLocation(cursorLocIntended);
}
}
function set_curChar(value:String):String

View file

@ -386,12 +386,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (isViewDownscroll)
{
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
measureTicks.y = gridTiledSprite.y;
}
else
{
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
measureTicks.y = gridTiledSprite.y;
for (member in audioWaveforms.members)
{
@ -2173,11 +2171,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var notePreviewViewportBitmap:Null<BitmapData> = null;
/**
* The IMAGE used for the measure ticks. Updated by ChartEditorThemeHandler.
*/
var measureTickBitmap:Null<BitmapData> = null;
/**
* The IMAGE used for the offset ticks. Updated by ChartEditorThemeHandler.
*/
@ -2408,7 +2401,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Setup the onClick listeners for the UI after it's been created.
setupUIListeners();
setupContextMenu();
setupTurboKeyHandlers();
setupAutoSave();
@ -2736,10 +2728,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
measureTicks = new ChartEditorMeasureTicks(this);
var measureTicksWidth = (GRID_SIZE);
measureTicks.x = gridTiledSprite.x - measureTicksWidth;
measureTicks.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
measureTicks.zIndex = 20;
add(measureTicks);
handleMeasureTickPosition();
}
function buildNotePreview():Void
@ -3376,21 +3368,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// registerContextMenu(null, Paths.ui('chart-editor/context/test'));
}
function setupContextMenu():Void
{
Screen.instance.registerEvent(MouseEvent.RIGHT_MOUSE_UP, function(e:MouseEvent) {
var xPos = e.screenX;
var yPos = e.screenY;
onContextMenu(xPos, yPos);
});
}
function onContextMenu(xPos:Float, yPos:Float)
{
trace('User right clicked to open menu at (${xPos}, ${yPos})');
// this.openDefaultContextMenu(xPos, yPos);
}
function copySelection():Void
{
// Doesn't use a command because it's not undoable.
@ -6447,6 +6424,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridTiledSprite != null)
{
gridTiledSprite.height = songLengthInPixels;
measureTicks.setHeight(gridTiledSprite.height);
}
// Remove any notes past the end of the song.
@ -6802,32 +6780,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
/**
* Updates the measure tick bitmap forcibly to make sure it's correct.
*/
function updateTimeSignature():Void
{
this.updateMeasureTicks(true);
gridTiledSprite.loadGraphic(gridBitmap);
}
function updateTimeSignature():Void {}
/**
* Handle positioning the measure ticks sprite.
*/
function handleMeasureTickPosition():Void
{
this.updateMeasureTicks();
var currentMeasureTime = Conductor.instance.getMeasureTimeInMs(Math.floor(Conductor.instance.getTimeInMeasures(scrollPositionInMs)));
var currentMeasurePos = currentMeasureTime < 0 ? 0 : Conductor.instance.getTimeInSteps(currentMeasureTime) * GRID_SIZE;
measureTicks.y = gridTiledSprite?.y + currentMeasurePos;
// Make sure the measure tick bitmap does not past the grid itself.
var totalMeasureTicksHeight:Float = currentMeasurePos + measureTickBitmap.height;
if (totalMeasureTicksHeight >= songLengthInPixels)
{
var spillOver:Float = totalMeasureTicksHeight - songLengthInPixels;
measureTicks.setClipRect(new FlxRect(0, 0, measureTickBitmap.width, measureTickBitmap.height - spillOver));
}
else
{
measureTicks.setClipRect(null);
}
// var currentMeasureTime = Conductor.instance.getMeasureTimeInMs(Math.floor(Conductor.instance.getTimeInMeasures(scrollPositionInMs)));
// var currentMeasurePos = currentMeasureTime < 0 ? 0 : Conductor.instance.getTimeInSteps(currentMeasureTime) * GRID_SIZE;
measureTicks.y = gridTiledSprite?.y;
}
/**

View file

@ -1,29 +1,63 @@
package funkin.ui.debug.charting.components;
#if FEATURE_CHART_EDITOR
import flixel.addons.display.FlxTiledSprite;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxRect;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.graphics.FunkinSprite;
import openfl.display.BitmapData;
import openfl.geom.Rectangle;
/**
* Handles the display of the measure ticks and numbers on the left side.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorMeasureTicks extends FlxTypedSpriteGroup<FlxSprite>
{
/**
* The owning ChartEditorState.
*/
var chartEditorState:ChartEditorState;
var measureTicksSprite:FlxSprite;
var measureNumbers:Array<FlxText> = [];
/**
* The measure ticks underneath the numbers.
*/
var measureTicksSprite:FlxTiledSprite = new FlxTiledSprite(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE * 16);
public var measureLengthsInPixels:Array<Int> = [];
/**
* The numbers that display the current measure number.
* This is a group so we can kill and recycle its members.
*/
var measureNumbers:FlxTypedSpriteGroup<FlxText> = new FlxTypedSpriteGroup<FlxText>();
/**
* The horizontal bars over the grid at each measure tick.
*/
var measureDividers:FlxTypedSpriteGroup<FlxSprite> = new FlxTypedSpriteGroup<FlxSprite>();
/**
* The positions of each measure tick, in pixels, relative to the start of the song.
*/
var measurePositions:Array<Float> = [];
/**
* A map of the
* @param value
* @return Float
*/
override function set_y(value:Float):Float
{
var result = super.set_y(value);
// Don't update if the value hasn't changed.
if (this.y == value) return value;
updateMeasureNumber();
super.set_y(value);
return result;
updateMeasureNumbers();
return this.y;
}
public function new(chartEditorState:ChartEditorState)
@ -32,59 +66,182 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup<FlxSprite>
this.chartEditorState = chartEditorState;
measureTicksSprite = new FlxSprite(0, 0);
add(measureTicksSprite);
add(measureNumbers);
add(measureDividers);
for (i in 0...5)
{
var measureNumber = new FlxText(0, 0, ChartEditorState.GRID_SIZE, "1");
measureNumber.setFormat(Paths.font('vcr.ttf'), 20, FlxColor.WHITE);
measureNumber.borderStyle = FlxTextBorderStyle.OUTLINE;
measureNumber.borderColor = FlxColor.BLACK;
add(measureNumber);
measureNumbers.push(measureNumber);
}
// Need these two lines or the ticks don't render before loading a chart!
chartEditorState.updateMeasureTicks(true);
reloadTickBitmap();
}
public function reloadTickBitmap():Void
{
measureTicksSprite.loadGraphic(chartEditorState.measureTickBitmap);
}
public function setClipRect(rect:Null<FlxRect>):Void
{
measureTicksSprite.clipRect = rect;
buildMeasureTicksSprite();
updateMeasureNumbers(true);
}
/**
* Update all 5 measure numbers, since that's the most we can really see at a time, even if barely.
* Please excuse the horror you're about to witness.
* Welp, you gotta go back in commits to see it now.
* Set the overall height of the measure ticks.
* @param height The desired height in pixels.
*/
function updateMeasureNumber()
public function setHeight(height:Float):Void
{
if (measureLengthsInPixels.length == 0 || measureLengthsInPixels == null) return;
measureTicksSprite.height = height;
}
var currentMeasure:Int = Math.floor(Conductor.instance?.getTimeInMeasures(chartEditorState.scrollPositionInMs));
for (i in 0...measureNumbers.length)
public function updateTheme():Void
{
buildMeasureTicksSprite();
updateMeasureNumbers(true);
}
function buildMeasureTicksSprite():Void
{
var backingColor:FlxColor = switch (chartEditorState.currentTheme)
{
var measureNumber:FlxText = measureNumbers[i];
if (measureNumber == null) continue;
case Light: ChartEditorThemeHandler.MEASTURE_TICKS_BACKING_COLOR_LIGHT;
case Dark: ChartEditorThemeHandler.MEASTURE_TICKS_BACKING_COLOR_DARK;
default: ChartEditorThemeHandler.MEASTURE_TICKS_BACKING_COLOR_LIGHT;
};
var dividerColor:FlxColor = switch (chartEditorState.currentTheme)
{
case Light: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_LIGHT;
case Dark: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_DARK;
default: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_LIGHT;
};
var measureNumberPosition = measureLengthsInPixels[i];
measureNumber.y = this.y + measureNumberPosition;
// TODO: This does NOT account for time signature, and always assumes 4/4!
// Better to have the little lines not line up than be required to redraw the image every frame,
// but we need to fix this eventually.
var stepsPerMeasure:Int = Constants.STEPS_PER_BEAT * 4;
// Show the measure number only if it isn't beneath the end of the note grid.
// Using measureNumber + 1 because the cut-off bar at the bottom is technically a bar, but it looks bad if a measure number shows up there.
var fixedMeasureNumberValue = currentMeasure + i + 1;
if (fixedMeasureNumberValue < Math.ceil(Conductor.instance?.getTimeInMeasures(chartEditorState.songLengthInMs)))
measureNumber.text = '${fixedMeasureNumberValue}';
// Start the bitmap with the basic gray color.
var measureTickBitmap = new BitmapData(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE * 16, true, backingColor);
// Draw the measure ticks at the top and bottom.
measureTickBitmap.fillRect(new Rectangle(0, 0, ChartEditorState.GRID_SIZE, ChartEditorThemeHandler.MEASURE_TICKS_MEASURE_WIDTH / 2), dividerColor);
var bottomTickY:Float = measureTickBitmap.height - (ChartEditorThemeHandler.MEASURE_TICKS_MEASURE_WIDTH / 2);
measureTickBitmap.fillRect(new Rectangle(0, bottomTickY, ChartEditorState.GRID_SIZE, ChartEditorThemeHandler.MEASURE_TICKS_MEASURE_WIDTH / 2),
dividerColor);
// Draw the beat ticks and dividers, and step ticks. No need for two seperate loops thankfully.
for (i in 1...stepsPerMeasure)
{
if ((i % Constants.STEPS_PER_BEAT) == 0) // If we're on a beat, draw a beat tick and divider.
{
var beatTickY:Float = ChartEditorState.GRID_SIZE * i - (ChartEditorThemeHandler.MEASURE_TICKS_BEAT_WIDTH / 2);
var beatTickLength:Float = ChartEditorState.GRID_SIZE * 2 / 3;
measureTickBitmap.fillRect(new Rectangle(0, beatTickY, beatTickLength, ChartEditorThemeHandler.MEASURE_TICKS_BEAT_WIDTH), dividerColor);
}
else
measureNumber.text = '';
{
// Draw a step tick.
var stepTickY:Float = ChartEditorState.GRID_SIZE * i - (ChartEditorThemeHandler.MEASURE_TICKS_STEP_WIDTH / 2);
var stepTickLength:Float = ChartEditorState.GRID_SIZE * 1 / 3;
measureTickBitmap.fillRect(new Rectangle(0, stepTickY, stepTickLength, ChartEditorThemeHandler.MEASURE_TICKS_STEP_WIDTH), dividerColor);
}
}
// Finally, set the sprite to use the image.
measureTicksSprite.loadGraphic(measureTickBitmap);
// Destroy these so they get rebuilt with the right theme later.
measureNumbers.forEach(function(measureNumber:FlxText) {
measureNumber.destroy();
});
measureNumbers.clear();
// Destroy these so they get rebuilt with the right theme later.
measureDividers.forEach(function(measureDivider:FlxSprite) {
measureDivider.destroy();
});
measureDividers.clear();
}
// The last measure number we updated the ticks on.
var previousMeasure:Int = 0;
function updateMeasureNumbers(force:Bool = false):Void
{
if (chartEditorState == null || Conductor.instance == null) return;
// Get the time at the top of the screen, in measures, rounded down.
// This is the earliest measure we'll need to display a tick for.
var currentMeasure:Int = Math.floor(Conductor.instance.getTimeInMeasures(chartEditorState.scrollPositionInMs));
if (previousMeasure == currentMeasure && !force) return;
if (currentMeasure < 0) currentMeasure = previousMeasure = 0;
// Remove existing measure numbers.
measureNumbers.forEachAlive(function(measureNumber:FlxText) {
measureNumber.kill();
});
measureDividers.forEachAlive(function(measureDivider:FlxSprite) {
measureDivider.kill();
});
final ARBITRARY_LIMIT = 5;
for (i in 0...ARBITRARY_LIMIT)
{
var targetMeasure:Int = currentMeasure + i - 1;
if (targetMeasure < 0) continue;
// TODO: This math is kinda awkward but DOES account for time signatures,
// might want some cleanup though? Maybe add a `getMeasureTimeInSteps` method?
var measureTimeInMs:Float = Conductor.instance.getMeasureTimeInMs(targetMeasure);
var measureTimeInSteps:Float = Conductor.instance.getTimeInSteps(measureTimeInMs);
var measureTimeInPixels:Float = measureTimeInSteps * ChartEditorState.GRID_SIZE;
var relativeMeasureTimeInPixels:Float = measureTimeInPixels + this.y;
final SCREEN_PADDING:Float = ChartEditorState.GRID_SIZE / 2;
// Above the visible area, keep going.
if (relativeMeasureTimeInPixels < 0 - SCREEN_PADDING)
{
continue;
}
// Below the visible area, quit it.
if (relativeMeasureTimeInPixels > FlxG.height + SCREEN_PADDING)
{
break;
}
// Else, display a number.
// Reuse an existing number. If we need a new number, create one with makeMeasureNumber().
final REVIVE:Bool = true;
var measureNumber = measureNumbers.recycle(makeMeasureNumber, false, REVIVE);
trace('Placing measure number. $relativeMeasureTimeInPixels -> $targetMeasure');
// Measures are base ZERO gah!
final OFFSET = 2;
measureNumber.text = '${targetMeasure + 1}';
measureNumber.y = relativeMeasureTimeInPixels + OFFSET;
measureNumber.x = this.x;
// Place a measure divider too.
var measureDivider = measureDividers.recycle(makeMeasureDivider, false, REVIVE);
measureDivider.y = relativeMeasureTimeInPixels - (ChartEditorThemeHandler.MEASURE_TICKS_MEASURE_WIDTH / 2);
measureDivider.x = this.x + (measureTicksSprite.width);
}
}
function makeMeasureNumber():FlxText
{
var measureNumber = new FlxText(0, 0, ChartEditorState.GRID_SIZE, "1");
measureNumber.setFormat(Paths.font('vcr.ttf'), 20, FlxColor.WHITE);
measureNumber.borderStyle = FlxTextBorderStyle.OUTLINE;
measureNumber.borderColor = FlxColor.BLACK;
return measureNumber;
}
function makeMeasureDivider():FlxSprite
{
var dividerColor:FlxColor = switch (chartEditorState.currentTheme)
{
case Light: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_LIGHT;
case Dark: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_DARK;
default: ChartEditorThemeHandler.GRID_MEASURE_DIVIDER_COLOR_LIGHT;
};
var measureDivider = new FunkinSprite().makeSolidColor(ChartEditorState.GRID_SIZE * ChartEditorThemeHandler.TOTAL_COLUMN_COUNT,
ChartEditorThemeHandler.MEASURE_TICKS_MEASURE_WIDTH, dividerColor);
return measureDivider;
}
}
#end

View file

@ -25,35 +25,37 @@ class ChartEditorThemeHandler
static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF361E60;
// 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;
public static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6;
public 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 = 0xFFF8F8F8;
static final GRID_COLOR_2_DARK:FlxColor = 0xFF202020;
public static final GRID_COLOR_2_LIGHT:FlxColor = 0xFFF8F8F8;
public static final GRID_COLOR_2_DARK:FlxColor = 0xFF202020;
// Color 3 of the grid pattern. Borders the other colors.
static final GRID_COLOR_3_LIGHT:FlxColor = 0xFFD9D5D5;
static final GRID_COLOR_3_DARK:FlxColor = 0xFF262A2A;
public static final GRID_COLOR_3_LIGHT:FlxColor = 0xFFD9D5D5;
public static final GRID_COLOR_3_DARK:FlxColor = 0xFF262A2A;
// Vertical divider between characters.
static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
public static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
public static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
// Horizontal divider between measures.
static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
public static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111;
public static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4;
static final GRID_MEASURE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
// Horizontal divider between beats.
static final GRID_BEAT_DIVIDER_COLOR_LIGHT:FlxColor = 0xFFC1C1C1;
static final GRID_BEAT_DIVIDER_COLOR_DARK:FlxColor = 0xFF848484;
static final GRID_BEAT_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH;
public static final MEASTURE_TICKS_BACKING_COLOR_LIGHT:FlxColor = 0xFFC1C1C1;
public static final MEASTURE_TICKS_BACKING_COLOR_DARK:FlxColor = 0xFF484848;
// 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;
/**
* The width of the opaque border around the square highlighting selected notes.
*/
public static final SELECTION_SQUARE_BORDER_WIDTH:Int = 1;
// Fill on the square highlighting selected notes.
@ -65,6 +67,11 @@ class ChartEditorThemeHandler
static final PLAYHEAD_BLOCK_BORDER_COLOR:FlxColor = 0xFF9D0011;
static final PLAYHEAD_BLOCK_FILL_COLOR:FlxColor = 0xFFBD0231;
// Lines on the measure ticks.
public static final MEASURE_TICKS_MEASURE_WIDTH:Int = 6;
public static final MEASURE_TICKS_BEAT_WIDTH:Int = 4;
public static final MEASURE_TICKS_STEP_WIDTH:Int = 2;
// Border on the square over the note preview.
static final NOTE_PREVIEW_VIEWPORT_BORDER_COLOR_LIGHT = 0xFFF8A657;
static final NOTE_PREVIEW_VIEWPORT_BORDER_COLOR_DARK = 0xFFF8A657;
@ -73,10 +80,7 @@ class ChartEditorThemeHandler
static final NOTE_PREVIEW_VIEWPORT_FILL_COLOR_LIGHT = 0x80F8A657;
static final NOTE_PREVIEW_VIEWPORT_FILL_COLOR_DARK = 0x80F8A657;
static final TOTAL_COLUMN_COUNT:Int = ChartEditorState.STRUMLINE_SIZE * 2 + 1;
// Used for checking in updateMeasureTicks() so we don't have to redraw it everytime if the current measure is the same as before.
static var previousMeasure:Int = -69;
public static final TOTAL_COLUMN_COUNT:Int = ChartEditorState.STRUMLINE_SIZE * 2 + 1;
/**
* When the theme is changed, this function updates all of the UI elements to match the new theme.
@ -86,10 +90,10 @@ class ChartEditorThemeHandler
{
updateBackground(state);
updateGridBitmap(state);
updateMeasureTicks(state);
updateOffsetTicks(state);
updateSelectionSquare(state);
updateNotePreview(state);
updateMeasureTicks(state);
}
/**
@ -127,6 +131,13 @@ class ChartEditorThemeHandler
default: GRID_COLOR_2_LIGHT;
};
var dividerColor:FlxColor = switch (state.currentTheme)
{
case Light: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
case Dark: GRID_STRUMLINE_DIVIDER_COLOR_DARK;
default: GRID_STRUMLINE_DIVIDER_COLOR_LIGHT;
}
// Draw the base grid.
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
@ -161,7 +172,7 @@ class ChartEditorThemeHandler
ChartEditorState.GRID_SELECTION_BORDER_WIDTH),
selectionBorderColor);
// Selection border at left.
// Selection border at far left.
state.gridBitmap.fillRect(new Rectangle(-(ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0, ChartEditorState.GRID_SELECTION_BORDER_WIDTH,
state.gridBitmap.height),
selectionBorderColor);
@ -169,12 +180,14 @@ class ChartEditorThemeHandler
// Selection borders vertically along the middle.
for (i in 1...TOTAL_COLUMN_COUNT)
{
var isStrumlineColumn:Bool = (i % ChartEditorState.STRUMLINE_SIZE == 0) && (i > 0);
state.gridBitmap.fillRect(new Rectangle((ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0,
ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height),
selectionBorderColor);
isStrumlineColumn ? dividerColor : selectionBorderColor);
}
// Selection border at right.
// Selection border at far right.
state.gridBitmap.fillRect(new Rectangle(state.gridBitmap.width - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0,
ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height),
selectionBorderColor);
@ -186,115 +199,6 @@ class ChartEditorThemeHandler
// Else, gridTiledSprite will be built later.
}
/**
* Draw the measure ticks left of the grid. This also includes the horizontal beat and measure dividers as well as the vertical strumline dividers.
*/
public static function updateMeasureTicks(state:ChartEditorState, force:Bool = false):Void
{
var measureTickWidth:Int = 6;
var beatTickWidth:Int = 4;
var stepTickWidth:Int = 2;
var measuresToCheck:Int = 5;
var currentMeasure:Int = Math.floor(Conductor.instance.getTimeInMeasures(state.scrollPositionInMs));
if (previousMeasure == currentMeasure && !force) return;
if (currentMeasure < 0) currentMeasure = previousMeasure = 0;
// Make the bitmap.
var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE);
var linesWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT);
state.measureTickBitmap = new BitmapData(ticksWidth + linesWidth, 6144, true, 0x00FFFFFF);
// Draw the vertical grey sidebar.
state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, 6144), GRID_BEAT_DIVIDER_COLOR_DARK);
// Then draw the ticks and dividers themselves.
var totalTicksHeight:Int = 0;
var measureLengthsInPixels:Array<Int> = [];
for (measure in 0...measuresToCheck)
{
var currentTimeInMs:Float = Conductor.instance.getMeasureTimeInMs(currentMeasure + measure)
+ 10; // Manual adjustment as it can get the wrong time change without this.
var currentTimeChange:SongTimeChange = Conductor.instance.getTimeChange(currentTimeInMs);
var stepsPerMeasure:Int = Constants.STEPS_PER_BEAT * currentTimeChange.timeSignatureNum;
var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * stepsPerMeasure);
// Draw horizontal 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.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight, linesWidth, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
// Divider at bottom
var dividerLineBY:Float = ticksHeight - (GRID_MEASURE_DIVIDER_WIDTH / 2);
state.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight + dividerLineBY, linesWidth, GRID_MEASURE_DIVIDER_WIDTH / 2),
gridMeasureDividerColor);
// Measure ticks.
state.measureTickBitmap.fillRect(new Rectangle(0, totalTicksHeight, ticksWidth, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
var bottomTickY:Float = ticksHeight - (measureTickWidth / 2);
state.measureTickBitmap.fillRect(new Rectangle(0, totalTicksHeight + bottomTickY, ticksWidth, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
// Draw horizontal dividers between the beats, this is done inside the following loop.
var gridBeatDividerColor:FlxColor = switch (state.currentTheme)
{
case Light: GRID_BEAT_DIVIDER_COLOR_LIGHT;
case Dark: GRID_BEAT_DIVIDER_COLOR_DARK;
default: GRID_BEAT_DIVIDER_COLOR_LIGHT;
};
// Draw the beat ticks and dividers, and step ticks. No need for two seperate loops thankfully.
for (i in 1...stepsPerMeasure)
{
if ((i % Constants.STEPS_PER_BEAT) == 0) // If we're on a beat, draw a beat tick and divider.
{
var beatTickY:Float = totalTicksHeight + ChartEditorState.GRID_SIZE * i - (beatTickWidth / 2);
var beatTickLength:Float = ticksWidth * 2 / 3;
state.measureTickBitmap.fillRect(new Rectangle(0, beatTickY, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
// Horizontal divider.
state.measureTickBitmap.fillRect(new Rectangle(ticksWidth, totalTicksHeight + (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2),
linesWidth, GRID_BEAT_DIVIDER_WIDTH),
gridBeatDividerColor);
}
else // Else, draw a step tick.
{
var stepTickY:Float = totalTicksHeight + ChartEditorState.GRID_SIZE * i - (stepTickWidth / 2);
var stepTickLength:Float = ticksWidth * 1 / 3;
state.measureTickBitmap.fillRect(new Rectangle(0, stepTickY, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
}
}
measureLengthsInPixels.push(totalTicksHeight);
totalTicksHeight += ticksHeight;
}
previousMeasure = currentMeasure;
// Finally, draw vertical 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:Float = ticksWidth + ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
state.measureTickBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.measureTickBitmap.height), gridStrumlineDividerColor);
// Divider at 2 * (Strumline Size)
var dividerLineBX:Float = ticksWidth + ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2);
state.measureTickBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.measureTickBitmap.height), gridStrumlineDividerColor);
if (state.measureTicks != null)
{
state.measureTicks.measureLengthsInPixels = measureLengthsInPixels;
state.measureTicks.reloadTickBitmap();
}
}
/**
* Horizontal offset ticks.
*/
@ -306,7 +210,7 @@ class ChartEditorThemeHandler
var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); // 10 minor ticks wide.
var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares tall.
state.offsetTickBitmap = new BitmapData(ticksWidth, ticksHeight, true);
state.offsetTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK);
state.offsetTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), 0xFFC4C4C4);
// Draw the major ticks.
var leftTickX:Float = 0;
@ -420,6 +324,11 @@ class ChartEditorThemeHandler
}
}
static function updateMeasureTicks(state:ChartEditorState):Void
{
state.measureTicks?.updateTheme();
}
public static function buildPlayheadBlock():FlxSprite
{
var playheadBlock:FlxSprite = new FlxSprite();

View file

@ -1,125 +1,95 @@
package funkin.ui.freeplay;
import flixel.FlxObject;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxPoint;
import flixel.text.FlxText;
import flixel.util.FlxSort;
import flixel.FlxSprite;
import flixel.util.FlxDestroyUtil;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
// its kinda like marqeee html lol!
@:nullSafety
class BGScrollingText extends FlxSpriteGroup
class BGScrollingText extends FlxText
{
var grpTexts:FlxTypedSpriteGroup<FlxSprite>;
var sourceText:FlxText;
var _textPositions:Array<FlxPoint> = [];
var _positionCache:FlxPoint = FlxPoint.get();
public var widthShit:Float = FlxG.width;
public var placementOffset:Float = 20;
public var speed:Float = 1;
public var size(default, set):Int = 48;
public var funnyColor(default, set):FlxColor = 0xFFFFFFFF;
@:deprecated("Use color instead")
public var funnyColor(get, set):FlxColor;
function get_funnyColor():FlxColor
return color;
function set_funnyColor(c:FlxColor):FlxColor
{
return color = c;
}
public function new(x:Float, y:Float, text:String, widthShit:Float = 100, ?bold:Bool = false, ?size:Int = 48)
{
super(x, y);
grpTexts = new FlxTypedSpriteGroup<FlxSprite>();
super(x, y, 0, text, size);
_positionCache = FlxPoint.get(x, y);
font = "5by7";
this.bold = bold ?? false;
this.widthShit = widthShit;
// Only keep one FlxText graphic at a time for batching
sourceText = new FlxText(0, 0, 0, text, size ?? this.size);
sourceText.font = "5by7";
sourceText.bold = bold ?? false;
@:privateAccess
sourceText.regenGraphic();
regenGraphic();
var needed:Int = Math.ceil(widthShit / sourceText.frameWidth) + 1;
var needed:Int = Math.ceil(widthShit / frameWidth) + 1;
for (i in 0...needed)
{
var coolText = new FlxSprite((i * sourceText.frameWidth) + (i * 20), 0);
grpTexts.add(coolText);
}
if (size != null) this.size = size;
add(grpTexts);
}
function reloadGraphics()
{
if (grpTexts != null)
{
@:privateAccess
sourceText.regenGraphic();
grpTexts.forEach(function(txt:FlxSprite) {
txt.loadGraphic(sourceText.graphic);
txt.updateHitbox();
});
_textPositions.push(FlxPoint.get((i * frameWidth) + (i * 20), 0));
}
}
function set_size(value:Int):Int
override public function update(elapsed:Float):Void
{
sourceText.size = value;
reloadGraphics();
this.size = value;
return value;
}
function set_funnyColor(value:FlxColor):FlxColor
{
sourceText.color = value;
reloadGraphics();
this.funnyColor = value;
return value;
}
override public function update(elapsed:Float)
{
for (txt in grpTexts.group)
{
if (txt == null) continue;
txt.x -= 1 * (speed * (elapsed / (1 / 60)));
if (speed > 0)
{
if (txt.x < -txt.frameWidth)
{
txt.x = grpTexts.group.members[grpTexts.length - 1].x + grpTexts.group.members[grpTexts.length - 1].frameWidth + placementOffset;
sortTextShit();
}
}
else
{
if (txt.x > txt.frameWidth * 2)
{
txt.x = grpTexts.group.members[0].x - grpTexts.group.members[0].frameWidth - placementOffset;
sortTextShit();
}
}
}
super.update(elapsed);
for (txtPosition in _textPositions)
{
if (txtPosition == null) continue;
txtPosition.x -= 1 * (speed * (elapsed / (1 / 60)));
if (speed > 0) // Going left
{
if (txtPosition.x < -frameWidth)
{
txtPosition.x = _textPositions[_textPositions.length - 1].x + frameWidth + placementOffset;
sortTextShit();
}
}
else // Going right
{
if (txtPosition.x > frameWidth * 2)
{
txtPosition.x = _textPositions[0].x - frameWidth - placementOffset;
sortTextShit();
}
}
}
}
override public function draw():Void
{
_positionCache.set(x, y);
for (position in _textPositions)
{
setPosition(_positionCache.x + position.x, _positionCache.y + position.y);
super.draw();
}
setPosition(_positionCache.x, _positionCache.y);
}
function sortTextShit():Void
{
grpTexts.sort(function(Order:Int, Obj1:FlxObject, Obj2:FlxObject) {
return FlxSort.byValues(Order, Obj1.x, Obj2.x);
_textPositions.sort(function(Obj1:FlxPoint, Obj2:FlxPoint) {
return FlxSort.byValues(FlxSort.ASCENDING, Obj1.x, Obj2.x);
});
}
override function destroy():Void
{
super.destroy();
sourceText = FlxDestroyUtil.destroy(sourceText);
}
}

View file

@ -131,27 +131,27 @@ class NewCharacterCard extends BackingCard
darkBg = new FlxSprite(0, 0).loadGraphic(bitmap);
add(darkBg);
friendFoe.funnyColor = 0xFF139376;
friendFoe.color = 0xFF139376;
friendFoe.speed = -4;
add(friendFoe);
newUnlock1.funnyColor = 0xFF99BDF2;
newUnlock1.color = 0xFF99BDF2;
newUnlock1.speed = 2;
add(newUnlock1);
waiting.funnyColor = 0xFF40EA84;
waiting.color = 0xFF40EA84;
waiting.speed = -2;
add(waiting);
newUnlock2.funnyColor = 0xFF99BDF2;
newUnlock2.color = 0xFF99BDF2;
newUnlock2.speed = 2;
add(newUnlock2);
friendFoe2.funnyColor = 0xFF139376;
friendFoe2.color = 0xFF139376;
friendFoe2.speed = -4;
add(friendFoe2);
newUnlock3.funnyColor = 0xFF99BDF2;
newUnlock3.color = 0xFF99BDF2;
newUnlock3.speed = 2;
add(newUnlock3);