diff --git a/FEATURES.md b/FEATURES.md index caa854171..ae8daf07e 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -79,4 +79,5 @@ _**QOL = Quality of Life**_ - Features not found in other editors! - Every single state & substate can be modified via HScript (`data/states/StateName.hx`) - **Instances launched via `lime test windows` will automatically use assets from source.** - \ No newline at end of file +- Modcharting features powered by [FunkinModchart](https://lib.haxe.org/p/funkin-modchart/). + diff --git a/README.md b/README.md index 2d95c17b2..d5ba590c9 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,5 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish - Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support - Credits to Smokey555 for the backup Animate Atlas to spritesheet code - Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration) +- Credits to [TheoDev](https://github.com/TheoDevelops) for [FunkinModchart](https://lib.haxe.org/p/funkin-modchart/). ***(library used for modcharting features)*** diff --git a/libs.xml b/libs.xml index 570b451c2..8903b8399 100644 --- a/libs.xml +++ b/libs.xml @@ -13,6 +13,7 @@ + diff --git a/project.xml b/project.xml index 382ed9f65..4cf4523eb 100644 --- a/project.xml +++ b/project.xml @@ -159,6 +159,9 @@ + + + @@ -195,6 +198,14 @@ +
+ + + + + +
+
diff --git a/source/funkin/game/Note.hx b/source/funkin/game/Note.hx index 9b06fc819..dc909f0e3 100644 --- a/source/funkin/game/Note.hx +++ b/source/funkin/game/Note.hx @@ -53,6 +53,13 @@ class Note extends FlxSprite */ public var nextSustain:Note; + /** + * The parent of the sustain. + * + * If this note is not sustain, this will be null. + */ + public var sustainParent:Null; + /** * Name of the splash. */ @@ -123,6 +130,10 @@ class Note extends FlxSprite } } + // work around to set the `sustainParent` + if (isSustainNote) + sustainParent = prevNote.isSustainNote ? prevNote.sustainParent : prevNote; + x += 50; // MAKE SURE ITS DEFINITELY OFF SCREEN? y -= 2000; diff --git a/source/funkin/game/Strum.hx b/source/funkin/game/Strum.hx index cbce2da40..ffd34b714 100644 --- a/source/funkin/game/Strum.hx +++ b/source/funkin/game/Strum.hx @@ -11,6 +11,11 @@ class Strum extends FlxSprite { */ public var animSuffix:String = ""; + /** + * This strum's StrumLine + */ + public var strumLine:StrumLine; + public var cpu = false; // Unused public var lastHit:Float = -5000; diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index 1b0780ddd..5bb2b7340 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -307,6 +307,7 @@ class StrumLine extends FlxTypedGroup { animPrefix = strumAnimPrefix[i % strumAnimPrefix.length]; var babyArrow:Strum = new Strum(startingPos.x + ((Note.swagWidth * strumScale) * i), startingPos.y); babyArrow.ID = i; + babyArrow.strumLine = this; if(data.scrollSpeed != null) babyArrow.scrollSpeed = data.scrollSpeed; diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index a9bf72ac1..b7b74876d 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -35,6 +35,9 @@ class Options public static var songOffset:Float = 0; public static var framerate:Int = 120; public static var gpuOnlyBitmaps:Bool = #if (mac || web) false #else true #end; // causes issues on mac and web + #if MODCHARTING_FEATURES + public static var modchartHoldSubdivisions:Int = 4; + #end public static var lastLoadedMod:String = null; diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx index 306621362..68d732cd2 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -24,6 +24,13 @@ class OptionsMenu extends TreeMenu { desc: 'Change Appearance options such as Flashing menus...', state: AppearanceOptions }, + #if MODCHARTING_FEATURES + { + name: 'Modchart Settings >', + desc: 'Customize your modcharting experience...', + state: ModchartingOptions + }, + #end { name: 'Miscellaneous >', desc: 'Use this menu to reset save data or engine settings.', diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx index 926213431..a6a458393 100644 --- a/source/funkin/options/categories/AppearanceOptions.hx +++ b/source/funkin/options/categories/AppearanceOptions.hx @@ -56,4 +56,4 @@ class AppearanceOptions extends OptionsScreen { else FlxG.updateFramerate = FlxG.drawFramerate = Std.int(change); } -} \ No newline at end of file +} diff --git a/source/funkin/options/categories/ModchartingOptions.hx b/source/funkin/options/categories/ModchartingOptions.hx new file mode 100644 index 000000000..e5850198f --- /dev/null +++ b/source/funkin/options/categories/ModchartingOptions.hx @@ -0,0 +1,17 @@ +package funkin.options.categories; + +#if MODCHARTING_FEATURES +class ModchartingOptions extends OptionsScreen { + public override function new() { + super("Modcharting Options", "Customize your modcharting experience."); + add(new NumOption( + "Hold Subdivisions", + "Softens the tail/hold/sustain of the arrows by subdividing it, giving them greater quality. By higher the subdivisions number is, performance will be affected.", + 1, // minimum + 128, // maximum + 1, // change + "modchartHoldSubdivisions" // save name + )); // callback + } +} +#end \ No newline at end of file diff --git a/source/modchart/standalone/adapters/codename/Codename.hx b/source/modchart/standalone/adapters/codename/Codename.hx new file mode 100644 index 000000000..40ca6b30d --- /dev/null +++ b/source/modchart/standalone/adapters/codename/Codename.hx @@ -0,0 +1,163 @@ +package modchart.standalone.adapters.codename; + +import flixel.FlxCamera; +import flixel.FlxSprite; +import funkin.backend.system.Conductor; +import funkin.game.Note; +import funkin.game.PlayState; +import funkin.game.Strum; +import funkin.options.Options; +import modchart.standalone.IAdapter; + +class Codename implements IAdapter { + private var beatCrochet:Float = 0; + + public function onModchartingInitialization() { + beatCrochet = Conductor.crochet; + } + + public function isTapNote(sprite:FlxSprite) { + return sprite is Note; + } + + // Song related + public function getSongPosition():Float { + return Conductor.songPosition; + } + + public function getCurrentBeat():Float { + return Conductor.curBeatFloat; + } + + public function getStaticCrochet():Float { + return beatCrochet; + } + + public function getBeatFromStep(step:Float):Float { + return step * Conductor.stepsPerBeat; + } + + public function arrowHit(arrow:FlxSprite) { + if (arrow is Note) { + final note:Note = cast arrow; + return note.wasGoodHit; + } + return false; + } + + public function isHoldEnd(arrow:FlxSprite) { + if (arrow is Note) { + final note:Note = cast arrow; + return note.nextSustain == null; + } + return false; + } + + public function getLaneFromArrow(arrow:FlxSprite) { + if (arrow is Note) { + final note:Note = cast arrow; + return note.strumID; + } else if (arrow is Strum) { + final strum:Strum = cast arrow; + return strum.ID; + } + return 0; + } + + public function getPlayerFromArrow(arrow:FlxSprite) { + if (arrow is Note) { + final note:Note = cast arrow; + return note.strumLine.ID; + } else if (arrow is Strum) { + final strum:Strum = cast arrow; + return strum.strumLine.ID; + } + + return 0; + } + + public function getHoldParentTime(arrow:FlxSprite) { + final note:Note = cast arrow; + return note.sustainParent.strumTime; + } + + // im so fucking sorry for those conditionals + public function getKeyCount(?player:Int = 0):Int { + return PlayState.instance != null + && PlayState.instance.strumLines != null + && PlayState.instance.strumLines.members != null + && PlayState.instance.strumLines.members[player] != null + && PlayState.instance.strumLines.members[player].members != null ? PlayState.instance.strumLines.members[player].members.length : 4; + } + + public function getPlayerCount():Int { + return PlayState.instance != null && PlayState.instance.strumLines != null ? PlayState.instance.strumLines.length : 2; + } + + public function getTimeFromArrow(arrow:FlxSprite) { + if (arrow is Note) { + final note:Note = cast arrow; + return note.strumTime; + } + + return 0; + } + + public function getHoldSubdivisions():Int + return Options.modchartHoldSubdivisions; + + public function getDownscroll():Bool + return Options.downscroll; + + public function getDefaultReceptorX(lane:Int, player:Int):Float { + @:privateAccess + return PlayState.instance.strumLines.members[player].members[lane].x; + } + + public function getDefaultReceptorY(lane:Int, player:Int):Float { + @:privateAccess + return PlayState.instance.strumLines.members[player].members[lane].y; + } + + public function getArrowCamera():Array + return [PlayState.instance.camHUD]; + + public function getCurrentScrollSpeed():Float { + return PlayState.instance.scrollSpeed; + } + + public function getArrowItems() { + var drawMembers:Array>> = []; + var strumLineMembers = PlayState.instance.strumLines.members; + + for (i in 0...strumLineMembers.length) { + final sl = strumLineMembers[i]; + + if (!sl.visible) + continue; + + // setup list + drawMembers[i] = [ + cast sl.members.copy(), + [], + [] + ]; + + // preallocating first + var st = 0; + var nt = 0; + sl.notes.forEachAlive((spr) -> { + spr.isSustainNote ? st++ : nt++; + }); + + drawMembers[i][1].resize(nt); + drawMembers[i][2].resize(st); + + var si = 0; + var ni = 0; + sl.notes.forEachAlive((spr) -> drawMembers[i][spr.isSustainNote ? 2 : 1][spr.isSustainNote ? si++ : ni++] = spr); + } + + return drawMembers; + } +}