mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-12-08 04:58:48 +00:00
Compare commits
17 commits
2b9d3733f9
...
34a60c4c82
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a60c4c82 | ||
|
|
758f712eb5 | ||
|
|
01029817c0 | ||
|
|
8a31e12d10 | ||
|
|
90ab04caa1 | ||
|
|
ba7f89b9a2 | ||
|
|
ee36cbbbcf | ||
|
|
d74b8fb4ca | ||
|
|
8fa622e147 | ||
|
|
a12950d5a7 | ||
|
|
71bc847698 | ||
|
|
a560bf51a9 | ||
|
|
866e5aa008 | ||
|
|
3c213ad45c | ||
|
|
a0b933ce8f | ||
|
|
5c861bc07e | ||
|
|
a2ccb5d44a |
112
.vscode/settings.json
vendored
112
.vscode/settings.json
vendored
|
|
@ -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
2
assets
|
|
@ -1 +1 @@
|
|||
Subproject commit 1374c558b577ce64e205cf838e2ee6f0311a7a7c
|
||||
Subproject commit 1a961f111381eb3bfc452166c4e4b5a18b409781
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,8 @@ class FunkinAction extends FlxActionDigital
|
|||
public var namePressed(default, null):Null<String>;
|
||||
public var nameReleased(default, null):Null<String>;
|
||||
|
||||
var cache:Map<String, {timestamp:Int, value:Bool}> = [];
|
||||
|
||||
public function new(?name:String = "", ?namePressed:String, ?nameReleased:String)
|
||||
{
|
||||
super(name);
|
||||
|
|
@ -1386,7 +1388,13 @@ class FunkinAction extends FlxActionDigital
|
|||
public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool
|
||||
{
|
||||
// Make sure we only update the inputs once per frame.
|
||||
if (_timestamp == FlxG.game.ticks) return triggered; // run no more than once per frame
|
||||
var key = '${filterTrigger}:${filterDevice}';
|
||||
var cacheEntry = cache.get(key);
|
||||
|
||||
if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks)
|
||||
{
|
||||
return cacheEntry.value;
|
||||
}
|
||||
|
||||
_x = null;
|
||||
_y = null;
|
||||
|
|
@ -1428,6 +1436,8 @@ class FunkinAction extends FlxActionDigital
|
|||
}
|
||||
}
|
||||
|
||||
cache.set(key, {timestamp: FlxG.game.ticks, value: triggered});
|
||||
|
||||
return triggered;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,11 +373,6 @@ class PolymodHandler
|
|||
// `lime.system.System`
|
||||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
||||
// `lime.utils.Assets`
|
||||
// Literally just has a private `resolveClass` function for some reason?
|
||||
Polymod.blacklistImport('lime.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.Lib');
|
||||
Polymod.blacklistImport('openfl.system.ApplicationDomain');
|
||||
Polymod.blacklistImport('openfl.net.SharedObject');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -56,6 +59,7 @@ class CharSelectCursors extends FlxTypedSpriteContainer<FunkinSprite>
|
|||
add(cursorDenied);
|
||||
|
||||
scrollFactor.set();
|
||||
directAlpha = true;
|
||||
}
|
||||
|
||||
public function confirm():Void
|
||||
|
|
@ -86,6 +90,32 @@ 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.x = intendedPosition.x;
|
||||
main.y = intendedPosition.y;
|
||||
|
||||
lightBlue.x = main.x;
|
||||
lightBlue.y = main.y;
|
||||
|
||||
darkBlue.x = intendedPosition.x;
|
||||
darkBlue.y = intendedPosition.y;
|
||||
|
||||
cursorConfirmed.x = main.x - 2;
|
||||
cursorConfirmed.y = main.y - 4;
|
||||
|
||||
cursorDenied.x = main.x - 2;
|
||||
cursorDenied.y = main.y - 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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -415,7 +415,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);
|
||||
|
|
@ -722,6 +721,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 +736,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 +771,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 +803,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 +890,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 +1119,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 +1135,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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,41 @@
|
|||
package funkin.util;
|
||||
|
||||
import Type.ValueType;
|
||||
|
||||
/**
|
||||
* Provides sanitized and blacklisted access to haxe's Reflection functions.
|
||||
* Used for sandboxing in scripts.
|
||||
*/
|
||||
@:nullSafety
|
||||
@SuppressWarnings("checkstyle:VarTypeHint")
|
||||
@:build(funkin.util.macro.BlacklistClassMacro.build(
|
||||
{
|
||||
classes: ["Reflect", "Type"],
|
||||
aliases:
|
||||
{
|
||||
"compare": ["compareValues"],
|
||||
"copy": ["copyAnonymousFieldsOf"],
|
||||
"deleteField": ["deleteAnonymousField", "delete"],
|
||||
"field": ["getAnonymousField", "getField"],
|
||||
"fields": ["getAnonymousFieldsOf", "getFieldsOf"],
|
||||
"hasField": ["hasAnonymousField"],
|
||||
"setField": ["setAnonymousField"]
|
||||
},
|
||||
customWrapList: [
|
||||
"compare",
|
||||
"compareMethods",
|
||||
"copy",
|
||||
"enumEq",
|
||||
"deleteField",
|
||||
"fields",
|
||||
"getClassFields",
|
||||
"getClassName",
|
||||
"getEnumName",
|
||||
"getInstanceFields",
|
||||
"isEnumValue",
|
||||
"isFunction",
|
||||
"isObject",
|
||||
"setField",
|
||||
"setProperty"
|
||||
]
|
||||
}))
|
||||
class ReflectUtil
|
||||
{
|
||||
/**
|
||||
|
|
@ -20,24 +48,16 @@ class ReflectUtil
|
|||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function callMethod(obj:Any, name:String, args:Array<Any>):Any
|
||||
{
|
||||
throw "Function Reflect.callMethod is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function callMethod(obj:Any, name:String, args:Array<Any>):Any
|
||||
/**
|
||||
* Compares two objects by value.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param valueA First value to compare
|
||||
* @param valueB Second value to compare
|
||||
* @return Int indicating relative order of values
|
||||
*/
|
||||
public static function compare(valueA:Any, valueB:Any):Int
|
||||
{
|
||||
return compareValues(valueA, valueB);
|
||||
}
|
||||
|
||||
// public static function compare(valueA:Any, valueB:Any):Int
|
||||
/**
|
||||
* Compares two values and returns an integer indicating their relative order.
|
||||
* Returns:
|
||||
|
|
@ -45,94 +65,76 @@ class ReflectUtil
|
|||
* - 0 if valueA == valueB
|
||||
* - 1 if valueA > valueB
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param valueA First value to compare
|
||||
* @param valueB Second value to compare
|
||||
* @return An integer indicating relative order of values
|
||||
*/
|
||||
public static function compareValues(valueA:Any, valueB:Any):Int
|
||||
{
|
||||
return Reflect.compare(valueA, valueB);
|
||||
}
|
||||
|
||||
// public static function compareValues(valueA:Any, valueB:Any):Int
|
||||
/**
|
||||
* Compare the two Function objects to determine whether they are the same.
|
||||
* @param functionA A method closure to compare.
|
||||
* @param functionB A method closure to compare.
|
||||
* @return Whether functionA and functionB are equal.
|
||||
*/
|
||||
public static function compareMethods(functionA:Any, functionB:Any):Bool
|
||||
{
|
||||
return Reflect.compareMethods(functionA, functionB);
|
||||
}
|
||||
|
||||
// public static function compareMethods(functionA:Any, functionB:Any):Bool
|
||||
/**
|
||||
* Copies the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to copy.
|
||||
* @return An independent clone of that object.
|
||||
*/
|
||||
public static function copy(obj:Any):Null<Any>
|
||||
{
|
||||
return copyAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function copy(obj:Any):Null<Any>
|
||||
/**
|
||||
* Copies the anonymous structure to a new object.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to copy.
|
||||
* @return An independent clone of the structure.
|
||||
*/
|
||||
public static function copyAnonymousFieldsOf(obj:Any):Null<Any>
|
||||
{
|
||||
return Reflect.copy(obj);
|
||||
}
|
||||
|
||||
// public static function copyAnonymousFieldsOf(obj:Any):Null<Any>
|
||||
/**
|
||||
* Delete the field of a given name from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function delete(obj:Any, name:String):Bool
|
||||
{
|
||||
return deleteAnonymousField(obj, name);
|
||||
}
|
||||
|
||||
// public static function delete(obj:Any, name:String):Bool
|
||||
/**
|
||||
* Delete the field of a given name from an anonymous structure.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function deleteAnonymousField(obj:Any, name:String):Bool
|
||||
{
|
||||
return Reflect.deleteField(obj, name);
|
||||
}
|
||||
|
||||
// public static function deleteAnonymousField(obj:Any, name:String):Bool
|
||||
/**
|
||||
* Retrive the value of a given field (by name) from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function field(obj:Any, name:String):Any
|
||||
{
|
||||
return getAnonymousField(obj, name);
|
||||
}
|
||||
|
||||
// public static function field(obj:Any, name:String):Any
|
||||
/**
|
||||
* Retrive the value of a given field (by name) from an object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to delete the field from.
|
||||
* @param name The name of the field to delete.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
public static function getField(obj:Any, name:String):Any
|
||||
{
|
||||
return getAnonymousField(obj, name);
|
||||
}
|
||||
// public static function getField(obj:Any, name:String):Any
|
||||
|
||||
/**
|
||||
* Retrieve the value of the field of the given name from an anonymous structure.
|
||||
|
|
@ -141,7 +143,8 @@ class ReflectUtil
|
|||
* @return The resulting field value.
|
||||
* @throws error If the field is blacklisted.
|
||||
*/
|
||||
public static function getAnonymousField(obj:Any, name:String):Any
|
||||
@:blacklistOverride
|
||||
public static function field(obj:Any, name:String):Any
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
{
|
||||
|
|
@ -154,34 +157,29 @@ class ReflectUtil
|
|||
/**
|
||||
* Get a list of fields available on the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function fields(obj:Any):Array<String>
|
||||
{
|
||||
return getAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function fields(obj:Any):Array<String>
|
||||
/**
|
||||
* Get a list of fields available on the given object.
|
||||
* Only guaranteed to work on anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function getFieldsOf(obj:Any):Array<String>
|
||||
{
|
||||
return getAnonymousFieldsOf(obj);
|
||||
}
|
||||
|
||||
// public static function getFieldsOf(obj:Any):Array<String>
|
||||
/**
|
||||
* Get a list of fields available on the given anonymous structure.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @return A list of fields on that object.
|
||||
*/
|
||||
public static function getAnonymousFieldsOf(obj:Any):Array<String>
|
||||
{
|
||||
return Reflect.fields(obj);
|
||||
}
|
||||
// public static function getAnonymousFieldsOf(obj:Any):Array<String>
|
||||
|
||||
/**
|
||||
* Get the value of the given property on a given object.
|
||||
|
|
@ -192,6 +190,7 @@ class ReflectUtil
|
|||
* @return The value of the field.
|
||||
* @throws error If the field is blacklisted.
|
||||
*/
|
||||
@:blacklistOverride
|
||||
public static function getProperty(obj:Any, name:String):Any
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
|
|
@ -205,14 +204,13 @@ class ReflectUtil
|
|||
/**
|
||||
* Determine whether the given object has the given field.
|
||||
* Only guaranteed to work for anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to query.
|
||||
* @param name The field name to query.
|
||||
* @return Whether the field exists.
|
||||
*/
|
||||
public static function hasField(obj:Any, name:String):Bool
|
||||
{
|
||||
return hasAnonymousField(obj, name);
|
||||
}
|
||||
// public static function hasField(obj:Any, name:String):Bool
|
||||
|
||||
/**
|
||||
* Determine whether the given anonymous structure has the given field.
|
||||
|
|
@ -220,7 +218,8 @@ class ReflectUtil
|
|||
* @param name The field name to query.
|
||||
* @return Whether the field exists.
|
||||
*/
|
||||
public static function hasAnonymousField(obj:Any, name:String):Bool
|
||||
@:blacklistOverride
|
||||
public static function hasField(obj:Any, name:String):Bool
|
||||
{
|
||||
if (FIELD_NAME_BLACKLIST.contains(name))
|
||||
{
|
||||
|
|
@ -232,128 +231,89 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Determine whether the given input is an enum value.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is an enum value.
|
||||
*/
|
||||
public static function isEnumValue(value:Any):Bool
|
||||
{
|
||||
return Reflect.isEnumValue(value);
|
||||
}
|
||||
|
||||
// public static function isEnumValue(value:Any):Bool
|
||||
/**
|
||||
* Determine whether the given input is a callable function.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is a function.
|
||||
*/
|
||||
public static function isFunction(value:Any):Bool
|
||||
{
|
||||
return Reflect.isFunction(value);
|
||||
}
|
||||
|
||||
// public static function isFunction(value:Any):Bool
|
||||
/**
|
||||
* Determine whether the given input is an object.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param value The input to evaluate.
|
||||
* @return Whether `value` is an object.
|
||||
*/
|
||||
public static function isObject(value:Any):Bool
|
||||
{
|
||||
return Reflect.isObject(value);
|
||||
}
|
||||
|
||||
// public static function isObject(value:Any):Bool
|
||||
/**
|
||||
* Set the value of a specific field on an object.
|
||||
* Only guaranteed to work for anonymous structures.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setField(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return setAnonymousField(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setField(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* Set the value of a specific field on an anonymous structure.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setAnonymousField(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return Reflect.setField(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setAnonymousField(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* Set the value of a specific field on an object.
|
||||
* Accounts for property fields with getters and setters.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param obj The object to modify.
|
||||
* @param name The field to modify.
|
||||
* @param value The new value to apply.
|
||||
*/
|
||||
public static function setProperty(obj:Any, name:String, value:Any):Void
|
||||
{
|
||||
return Reflect.setProperty(obj, name, value);
|
||||
}
|
||||
|
||||
// public static function setProperty(obj:Any, name:String, value:Any):Void
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
// public static function createEmptyInstance(cls:Class<Any>):Any
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
// public static function createInstance(cls:Class<Any>, args:Array<Any>):Any
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function createEmptyInstance(cls:Class<Any>):Any
|
||||
{
|
||||
throw "Function Type.createEmptyInstance is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function resolveEnum(name:String):Enum<Any>
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function createInstance(cls:Class<Any>, args:Array<Any>):Any
|
||||
{
|
||||
throw "Function Type.createInstance is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function resolveClass(name:String):Class<Any>
|
||||
{
|
||||
throw "Function Type.resolveClass is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function resolveEnum(name:String):Enum<Any>
|
||||
{
|
||||
throw "Function Type.resolveEnum is blacklisted.";
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is not allowed to be used by scripts.
|
||||
* @throws error When called by a script.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:FieldDocComment")
|
||||
public static function typeof(value:Any):ValueType
|
||||
{
|
||||
throw "Function Type.typeof is blacklisted.";
|
||||
}
|
||||
|
||||
// public static function typeof(value:Any):ValueType
|
||||
/**
|
||||
* Get a list of the static class fields on the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class object to query.
|
||||
* @return A list of class field names.
|
||||
*/
|
||||
public static function getClassFields(cls:Class<Any>):Array<String>
|
||||
{
|
||||
return Type.getClassFields(cls);
|
||||
}
|
||||
// public static function getClassFields(cls:Class<Any>):Array<String>
|
||||
|
||||
/**
|
||||
* Get a list of the static class fields on the class of the given object.
|
||||
|
|
@ -371,13 +331,12 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Get a list of all the fields on instances of the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class object to query.
|
||||
* @return A list of object field names.
|
||||
*/
|
||||
public static function getInstanceFields(cls:Class<Any>):Array<String>
|
||||
{
|
||||
return Type.getInstanceFields(cls);
|
||||
}
|
||||
// public static function getInstanceFields(cls:Class<Any>):Array<String>
|
||||
|
||||
/**
|
||||
* Get a list of all the fields on instances of the class of the given object.
|
||||
|
|
@ -395,13 +354,12 @@ class ReflectUtil
|
|||
|
||||
/**
|
||||
* Get the string name of the given class.
|
||||
*
|
||||
* The actual function exists and is generated at build time.
|
||||
* @param cls The class to query.
|
||||
* @return The name of the given class.
|
||||
*/
|
||||
public static function getClassName(cls:Class<Any>):String
|
||||
{
|
||||
return Type.getClassName(cls);
|
||||
}
|
||||
// public static function getClassName(cls:Class<Any>):String
|
||||
|
||||
/**
|
||||
* Get the string name of the class of the given object.
|
||||
|
|
|
|||
295
source/funkin/util/macro/BlacklistClassMacro.hx
Normal file
295
source/funkin/util/macro/BlacklistClassMacro.hx
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
package funkin.util.macro;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Expr.Field;
|
||||
import haxe.macro.Type;
|
||||
import haxe.DynamicAccess;
|
||||
|
||||
using haxe.macro.TypeTools;
|
||||
using haxe.macro.ComplexTypeTools;
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
|
||||
enum abstract WrapMode(String) from String to String
|
||||
{
|
||||
var Blacklist;
|
||||
var Whitelist;
|
||||
}
|
||||
|
||||
typedef WrapperParams =
|
||||
{
|
||||
/**
|
||||
* Classes to generate functions for.
|
||||
*/
|
||||
var classes:Array<String>;
|
||||
|
||||
/**
|
||||
* Aliases to functions, for instance `{ "getAnonymousField": ["field"] }` generates a `field` function that calls `getAnonymousField`.
|
||||
* It works in both ways, so you can generate aliases in the class that point to the ones in `classes` and vice-versa.
|
||||
* This should be strictly structured as `{ fieldName: Array<String> }`, failure to comply will result in undefined behaviour.
|
||||
*/
|
||||
@:optional
|
||||
var aliases:{};
|
||||
|
||||
/**
|
||||
* Functions that should be wrapped based on the value of `customWrapMode`.
|
||||
* If a function is part of an alias or the ignore list, this will have no effect on it.
|
||||
*/
|
||||
@:optional
|
||||
var customWrapList:Array<String>;
|
||||
|
||||
/**
|
||||
* Defines how fields inside and outside of `customWrapList` are wrapped.
|
||||
* If it's `Whitelist` fields outside the list are blacklisted by default.
|
||||
*
|
||||
* @default `Whitelist`
|
||||
*/
|
||||
@:optional
|
||||
var customWrapMode:WrapMode;
|
||||
|
||||
/**
|
||||
* Functions in `classes` that the macro should not generate.
|
||||
* If a function is part of an alias, this will have no effect on it.
|
||||
*/
|
||||
@:optional
|
||||
var ignoreList:Array<String>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fields that wrap functions from the provided classes in a way that
|
||||
* they'll throw an error if accessed, or call the original function if whitelisted.
|
||||
* It is best to be used with classes with only static fields. Private fields and variables are always ignored.
|
||||
*
|
||||
* You can add your own sandboxed implementations of the fields and make aliases to them (see `BlacklistParams.aliases`).
|
||||
* Note that if the field already exists in `BlacklistParams.classes` you should add `@:blacklistOverride` to it.
|
||||
*/
|
||||
class BlacklistClassMacro
|
||||
{
|
||||
/**
|
||||
* Documentation used by blacklisted functions.
|
||||
*/
|
||||
static final BLACKLISTED_FUNCTION_DOC:String = "This function is not allowed to be used by scripts.\n@throws error When called by a script.";
|
||||
|
||||
static var buildFields:Array<Field>;
|
||||
static var processedFieldNames:Array<String> = [];
|
||||
|
||||
static inline function containsField(fieldName:String):Bool
|
||||
{
|
||||
return buildFields.exists(f -> f.name == fieldName);
|
||||
}
|
||||
|
||||
static inline function getField(fieldName:String):Null<Field>
|
||||
{
|
||||
return buildFields.find(f -> f.name == fieldName);
|
||||
}
|
||||
|
||||
static function build(params:WrapperParams):Array<Field>
|
||||
{
|
||||
final classes:Array<ClassType> = [for (c in params.classes) MacroUtil.getClassType(c)];
|
||||
if (classes.length == 0) Context.fatalError('Invalid class amount, no classes were provided.', Context.currentPos());
|
||||
|
||||
buildFields = Context.getBuildFields();
|
||||
var generatedFields:Array<Field> = [];
|
||||
|
||||
params.customWrapList ??= [];
|
||||
params.customWrapMode ??= Whitelist;
|
||||
|
||||
// NOTE: As much as I wish these could be a map seems like Haxe is unable to parse them as part of the metadata.
|
||||
final aliases:DynamicAccess<Array<String>> = cast params.aliases;
|
||||
var fieldsToSkip:Array<String> = params.ignoreList?.copy() ?? [];
|
||||
var pendingFieldsToWrap:Array<String> = [];
|
||||
|
||||
if (aliases != null)
|
||||
{
|
||||
generatedFields = generateAliases(aliases, pendingFieldsToWrap);
|
||||
}
|
||||
|
||||
for (c in classes)
|
||||
{
|
||||
for (field in c.statics.get())
|
||||
{
|
||||
if (!field.isPublic || fieldsToSkip.contains(field.name) || ~/^(get|set)_/.match(field.name)) continue;
|
||||
|
||||
if (containsField(field.name))
|
||||
{
|
||||
if (!getField(field.name).meta.exists(m -> m.name == ':blacklistOverride'))
|
||||
{
|
||||
// 'reportError' doesn't abort compilation, so it allows us to see all the duplicate fields!
|
||||
Context.reportError('Tried to generate "${field.name}" but it already exists in the class.\n'
|
||||
+ 'Add @:blacklistOverride or add it to "ignoreList" to ignore.',
|
||||
getField(field.name).pos);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final blacklisted:Bool = (params.customWrapMode == Whitelist) != params.customWrapList.contains(field.name);
|
||||
final wrapper:Null<Field> = generateWrapperField(field.name, field, c.name, blacklisted);
|
||||
if (wrapper == null) continue; // Not a function
|
||||
|
||||
generatedFields.push(wrapper);
|
||||
// TODO: When this happens should it make the field whitelisted (or vice-versa)?
|
||||
if (pendingFieldsToWrap.contains(field.name))
|
||||
{
|
||||
for (alias in aliases.get(field.name))
|
||||
{
|
||||
generatedFields.push(generateWrapperField(alias, wrapper));
|
||||
pendingFieldsToWrap.remove(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (f in pendingFieldsToWrap)
|
||||
{
|
||||
Context.reportError('Tried to generate alias fields for "$f" but it does not exist.', Context.currentPos());
|
||||
}
|
||||
|
||||
return buildFields.concat(generatedFields);
|
||||
}
|
||||
|
||||
static function generateAliases(aliases:DynamicAccess<Array<String>>, ?unresolvedAliases:Array<String>):Array<Field>
|
||||
{
|
||||
var result:Array<Field> = [];
|
||||
|
||||
for (field => aliasFields in aliases)
|
||||
{
|
||||
if (aliasFields.length == 0) Context.warning('No alias fields specified to be generated for "$field"', Context.currentPos());
|
||||
|
||||
final wrappedField:Null<Field> = getField(field);
|
||||
if (wrappedField == null && unresolvedAliases != null)
|
||||
{
|
||||
// Field might be on the provided classes, put it on queue.
|
||||
unresolvedAliases.push(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (aliasName in aliasFields)
|
||||
{
|
||||
if (containsField(aliasName))
|
||||
{
|
||||
Context.error('Tried to generate "${aliasName}" alias but it already exists in the class.', getField(aliasName).pos);
|
||||
}
|
||||
|
||||
final wrapper:Null<Field> = generateWrapperField(aliasName, wrappedField);
|
||||
if (wrapper != null)
|
||||
{
|
||||
result.push(wrapper);
|
||||
processedFieldNames.push(aliasName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.error('Could not generate alias for field "$field"; it may not be a function.', wrappedField.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function generateWrapperField(fieldName:String, wrappedField:Dynamic, ?className:String, blacklist:Bool = false):Null<Field>
|
||||
{
|
||||
final pack:Array<String> = [wrappedField.name];
|
||||
if (className != null) pack.unshift(className);
|
||||
|
||||
function getWrapperExpr(args:Array<{name:String}>, ?retType:ComplexType):Expr
|
||||
{
|
||||
return if (blacklist)
|
||||
{
|
||||
macro throw $v{'Function ${pack.join('.')} is blacklisted.'};
|
||||
}
|
||||
else
|
||||
{
|
||||
final params:Array<Expr> = [for (a in args) macro $i{a.name}];
|
||||
retType.toString() == 'StdTypes.Void' ? macro $p{pack}($a{params}) : macro return $p{pack}($a{params});
|
||||
}
|
||||
}
|
||||
|
||||
var wrapperKind:Null<FieldType>;
|
||||
if (wrappedField.kind is FieldType)
|
||||
{
|
||||
wrapperKind = switch (wrappedField.kind)
|
||||
{
|
||||
case FFun(f):
|
||||
final wrapFunc:Function = Reflect.copy(f);
|
||||
wrapFunc.expr = getWrapperExpr(wrapFunc.args, wrapFunc.ret);
|
||||
FFun(wrapFunc);
|
||||
default:
|
||||
Context.error('Blacklist Macro: Making wrappers for anything other than functions is not supported.', wrappedField.pos);
|
||||
}
|
||||
}
|
||||
else if (wrappedField.expr() != null)
|
||||
{
|
||||
switch (wrappedField.expr().expr)
|
||||
{
|
||||
case TFunction(tfunc):
|
||||
final args:Array<FunctionArg> = [
|
||||
for (a in tfunc.args)
|
||||
{
|
||||
name: a.v.name,
|
||||
value: a.value != null ? Context.getTypedExpr(a.value) : null,
|
||||
type: a.v.t.toComplexType()
|
||||
}
|
||||
];
|
||||
wrapperKind = FFun(
|
||||
{
|
||||
args: args,
|
||||
params: getParamDecls(wrappedField.params),
|
||||
ret: tfunc.t.toComplexType(),
|
||||
expr: getWrapperExpr(args, tfunc.t.toComplexType()),
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some targets have core types with externs as functions and those don't have a TypedExpr.
|
||||
// We follow its type once to get rid of any lazy type
|
||||
switch (wrappedField.type.follow(true))
|
||||
{
|
||||
case TFun(args, ret):
|
||||
wrapperKind = FFun(
|
||||
{
|
||||
args: [for (a in args) {name: a.name, opt: a.opt, type: a.t.toComplexType()}],
|
||||
params: getParamDecls(wrappedField.params),
|
||||
ret: ret.toComplexType(),
|
||||
expr: getWrapperExpr(args, ret.toComplexType())
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final access = [APublic, AStatic];
|
||||
if (wrapperKind.match(FFun(_)))
|
||||
{
|
||||
access.push(AInline);
|
||||
}
|
||||
|
||||
return {
|
||||
name: fieldName,
|
||||
pos: wrappedField.pos,
|
||||
doc: blacklist ? BLACKLISTED_FUNCTION_DOC : wrappedField.doc,
|
||||
access: access,
|
||||
kind: wrapperKind
|
||||
};
|
||||
}
|
||||
|
||||
static function getParamDecls(params:Array<TypeParameter>):Array<TypeParamDecl>
|
||||
{
|
||||
final result:Array<TypeParamDecl> = [];
|
||||
for (p in params)
|
||||
{
|
||||
switch (p.t.getClass()?.kind)
|
||||
{
|
||||
case KTypeParameter(constraints):
|
||||
result.push({name: p.name, constraints: [for (c in constraints) c.toComplexType()]});
|
||||
default:
|
||||
Context.error("Provided type parameters are not of the KTypeParameter kind, this shouldn't happen!", Context.currentPos());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#end
|
||||
Loading…
Reference in a new issue