├── github └── imagotipo.png ├── .gitignore ├── modchart ├── engine │ ├── modifiers │ │ ├── ScriptedModifier.hx │ │ ├── list │ │ │ ├── SchmovinTipsy.hx │ │ │ ├── CenterRotate.hx │ │ │ ├── false_paradise │ │ │ │ ├── Vibrate.hx │ │ │ │ ├── Wiggle.hx │ │ │ │ ├── CounterClockWise.hx │ │ │ │ ├── Spiral.hx │ │ │ │ ├── SchmovinArrowShape.hx │ │ │ │ └── EyeShape.hx │ │ │ ├── SchmovinDrunk.hx │ │ │ ├── SawTooth.hx │ │ │ ├── OpponentSwap.hx │ │ │ ├── ZigZag.hx │ │ │ ├── LocalRotate.hx │ │ │ ├── SchmovinTornado.hx │ │ │ ├── Invert.hx │ │ │ ├── Bounce.hx │ │ │ ├── FieldRotate.hx │ │ │ ├── Square.hx │ │ │ ├── Skew.hx │ │ │ ├── ArrowShape.hx │ │ │ ├── Tipsy.hx │ │ │ ├── Infinite.hx │ │ │ ├── Transform.hx │ │ │ ├── Rotate.hx │ │ │ ├── Drunk.hx │ │ │ ├── Boost.hx │ │ │ ├── Scale.hx │ │ │ ├── Confusion.hx │ │ │ ├── Zoom.hx │ │ │ ├── Tornado.hx │ │ │ ├── ReceptorScroll.hx │ │ │ ├── Radionic.hx │ │ │ ├── PathModifier.hx │ │ │ ├── Drugged.hx │ │ │ ├── Beat.hx │ │ │ ├── Stealth.hx │ │ │ ├── Reverse.hx │ │ │ └── Bumpy.hx │ │ ├── Modifier.hx │ │ ├── DynamicModifier.hx │ │ └── ModifierGroup.hx │ ├── events │ │ ├── types │ │ │ ├── import.hx │ │ │ ├── SetEvent.hx │ │ │ ├── RepeaterEvent.hx │ │ │ ├── AddEvent.hx │ │ │ └── EaseEvent.hx │ │ ├── EventType.hx │ │ ├── Event.hx │ │ └── EventManager.hx │ └── PlayField.hx ├── backend │ ├── core │ │ ├── ModAlias.hx │ │ ├── ModifierOutput.hx │ │ ├── Node.hx │ │ ├── VisualParameters.hx │ │ ├── RotationOrder.hx │ │ ├── ModifierParameters.hx │ │ ├── ArrowData.hx │ │ └── PercentArray.hx │ ├── graphics │ │ ├── import.hx │ │ ├── ModchartRenderer.hx │ │ ├── renderers │ │ │ ├── ModchartPathRenderer.hx │ │ │ └── ModchartArrowRenderer.hx │ │ └── ModchartCamera3D.hx │ ├── standalone │ │ ├── Adapter.hx │ │ ├── IAdapter.hx │ │ └── adapters │ │ │ ├── pslice │ │ │ └── Pslice.hx │ │ │ ├── fpsplus │ │ │ └── Fpsplus.hx │ │ │ ├── psych │ │ │ └── Psych.hx │ │ │ └── codename │ │ │ └── Codename.hx │ ├── macros │ │ ├── CompiledClassList.hx │ │ ├── ModifiersMacro.hx │ │ └── Macro.macro.hx │ ├── math │ │ ├── Quaternion.hx │ │ ├── ModchartPerspective.hx │ │ └── Vector3.hx │ └── util │ │ └── ModchartUtil.hx ├── import.hx ├── Config.hx └── Manager.hx ├── extraParams.hxml ├── .vscode └── settings.json ├── haxelib.json ├── include.xml ├── SUPPORT.md ├── TODO.md ├── assets └── modchart │ ├── arrowShape.csv │ └── eyeShape.csv ├── DOC.md ├── README.md └── CHANGELOG.md /github/imagotipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theoo-h/FunkinModchart/HEAD/github/imagotipo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | # currently on dev 4 | modchart/backend/environments/* 5 | modchart/backend/IEnvironment.hx 6 | 7 | documentation/* -------------------------------------------------------------------------------- /modchart/engine/modifiers/ScriptedModifier.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers; 2 | 3 | class ScriptedModifier extends DynamicModifier {} 4 | -------------------------------------------------------------------------------- /modchart/engine/events/types/import.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events.types; 2 | 3 | #if !macro 4 | import modchart.engine.events.Event; 5 | #end 6 | -------------------------------------------------------------------------------- /modchart/backend/core/ModAlias.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | @:publicFields 4 | @:structInit 5 | final class ModAlias { 6 | public var parent:String; 7 | public var alias:String; 8 | } 9 | -------------------------------------------------------------------------------- /modchart/backend/core/ModifierOutput.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | #if !openfl_debug 4 | @:fileXml('tags="haxe,release"') 5 | @:noDebug 6 | #end 7 | @:structInit 8 | @:publicFields 9 | class ModifierOutput { 10 | var pos:Vector3; 11 | var visuals:VisualParameters; 12 | } 13 | -------------------------------------------------------------------------------- /modchart/engine/events/EventType.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events; 2 | 3 | enum abstract EventType(String) from String to String { 4 | public final EMPTY = 'empty'; 5 | public final EASE = 'ease'; 6 | public final ADD = 'add'; 7 | public final SET = 'set'; 8 | public final REPEATER = 'repeater'; 9 | } 10 | -------------------------------------------------------------------------------- /modchart/backend/core/Node.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | @:publicFields 4 | @:structInit 5 | final class Node { 6 | public var input:Array = []; 7 | public var output:Array = []; 8 | public var func:NodeFunction = (_, o) -> _; 9 | } 10 | 11 | typedef NodeFunction = (Array, Int) -> Array; 12 | -------------------------------------------------------------------------------- /extraParams.hxml: -------------------------------------------------------------------------------- 1 | --macro addMetadata('@:build(modchart.backend.macros.Macro.buildFlxCamera())', 'flixel.FlxCamera') 2 | --macro addMetadata('@:build(modchart.backend.macros.Macro.buildFlxDrawTrianglesItem())', 'flixel.graphics.tile.FlxDrawTrianglesItem') 3 | --macro addMetadata('@:build(modchart.backend.macros.Macro.addModchartStorage())', 'flixel.FlxBasic') -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[haxe]": { 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "always" 6 | } 7 | }, 8 | "haxe.configurations": [ 9 | { 10 | "label": "windows", 11 | "args": [ 12 | "-lib", 13 | "flixel", 14 | "-lib", 15 | "openfl", 16 | "-lib", 17 | "lime", 18 | ] 19 | } 20 | ], 21 | } -------------------------------------------------------------------------------- /modchart/engine/events/types/SetEvent.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events.types; 2 | 3 | class SetEvent extends Event { 4 | public function new(mod:String, beat:Float, target:Float, player:Int, parent:EventManager) { 5 | this.name = mod; 6 | this.target = target; 7 | this.player = player; 8 | 9 | super(beat, (_) -> { 10 | setModPercent(mod, target, player); 11 | }, parent); 12 | type = SET; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "funkin-modchart", 3 | "license": "Apache", 4 | "description": "An modcharting plugin for Friday Night Funkin'", 5 | "version": "1.2.4", 6 | "releasenote": "More stable release.", 7 | "contributors": ["TheoDev"], 8 | "tags": ["fnf", "friday-night-funkin"], 9 | "dependencies": { 10 | "flixel": "" 11 | }, 12 | "url": "https://github.com/TheoDevelops/FunkinModchart" 13 | } -------------------------------------------------------------------------------- /modchart/backend/core/VisualParameters.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | @:publicFields 4 | @:structInit 5 | final class VisualParameters { 6 | var scaleX:Float = 1; 7 | var scaleY:Float = 1; 8 | var alpha:Float = 1; 9 | var glow:Float = 0; 10 | var glowR:Float = 1; 11 | var glowG:Float = 1; 12 | var glowB:Float = 1; 13 | var angleX:Float = 0; 14 | var angleY:Float = 0; 15 | var angleZ:Float = 0; 16 | var skewX:Float = 0; 17 | var skewY:Float = 0; 18 | } 19 | -------------------------------------------------------------------------------- /include.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /modchart/backend/core/RotationOrder.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | enum abstract RotationOrder(String) from String to String { 4 | final X_Y_Z = "x_y_z"; 5 | final X_Z_Y = "x_z_y"; 6 | final Y_X_Z = "y_x_z"; 7 | final Y_Z_X = "y_z_x"; 8 | final Z_X_Y = "z_x_y"; 9 | final Z_Y_X = "z_y_x"; 10 | 11 | final X_Y_X = "x_y_x"; 12 | final X_Z_X = "x_z_x"; 13 | final Y_X_Y = "y_x_y"; 14 | final Y_Z_Y = "y_z_y"; 15 | final Z_X_Z = "z_x_z"; 16 | final Z_Y_Z = "z_y_z"; 17 | } 18 | -------------------------------------------------------------------------------- /modchart/import.hx: -------------------------------------------------------------------------------- 1 | #if !macro 2 | import flixel.math.FlxMath; 3 | import flixel.tweens.FlxEase; 4 | import modchart.Config; 5 | import modchart.Manager; 6 | import modchart.backend.core.*; 7 | import modchart.backend.graphics.*; 8 | import modchart.backend.math.*; 9 | import modchart.backend.standalone.Adapter; 10 | import modchart.backend.util.ModchartUtil; 11 | import modchart.engine.*; 12 | import modchart.engine.events.*; 13 | import modchart.engine.modifiers.*; 14 | import openfl.geom.Vector3D; 15 | #end 16 | -------------------------------------------------------------------------------- /modchart/backend/core/ModifierParameters.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | @:publicFields 4 | @:structInit 5 | final class ModifierParameters { 6 | var songTime:Float; 7 | var hitTime:Float; 8 | var distance:Float; 9 | var curBeat:Float; 10 | 11 | var lane:Int = 0; 12 | var player:Int = 0; 13 | var isTapArrow:Bool = false; 14 | 15 | public function toString() { 16 | return 'ModifierParameters(songTime: $songTime, hitTime: $hitTime, distance: $distance, curBeat: $curBeat, lane: $lane, player: $player)'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/SchmovinTipsy.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class SchmovinTipsy extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | curPos.y += sin(params.curBeat / 4 * Math.PI + params.lane) * ARROW_SIZEDIV2 * getPercent('schmovinTipsy', params.player); 9 | return curPos; 10 | } 11 | 12 | override public function shouldRun(params:ModifierParameters):Bool 13 | return true; 14 | } 15 | -------------------------------------------------------------------------------- /modchart/backend/core/ArrowData.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | @:structInit 4 | final class ArrowData { 5 | public var hitTime:Float = 0; 6 | public var distance:Float = 0; 7 | 8 | public var lane:Int = 0; 9 | public var player:Int = 0; 10 | 11 | public var hitten:Bool = false; 12 | public var isTapArrow:Bool = false; 13 | 14 | private var __holdSubdivisionOffset:Float = .0; 15 | 16 | public function toString() { 17 | return 'ModifierParameters(hitTime: $hitTime, distance: $distance, lane: $lane, player: $player, hitten: $hitten, isTapArrow: $isTapArrow)'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modchart/engine/events/types/RepeaterEvent.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events.types; 2 | 3 | class RepeaterEvent extends Event { 4 | var end:Float; 5 | 6 | public function new(beat:Float, length:Float, callback:Event->Void, parent:EventManager) { 7 | super(beat, callback, parent); 8 | 9 | end = beat + length; 10 | type = REPEATER; 11 | } 12 | 13 | override function update(curBeat:Float):Void { 14 | if (fired) 15 | return; 16 | 17 | if (curBeat < end) { 18 | callback(this); 19 | fired = false; 20 | } else if (curBeat >= end) { 21 | fired = true; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/CenterRotate.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class CenterRotate extends Rotate { 9 | override public function getOrigin(curPos:Vector3, params:ModifierParameters):Vector3 { 10 | return new Vector3(FlxG.width * 0.5, HEIGHT * 0.5); 11 | } 12 | 13 | override public function getRotateName():String 14 | return 'centerRotate'; 15 | 16 | override public function shouldRun(params:ModifierParameters):Bool 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/Vibrate.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Vibrate extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | var vib = getPercent('vibrate', params.player); 9 | curPos.x += (Math.random() - 0.5) * vib * 20; 10 | curPos.y += (Math.random() - 0.5) * vib * 20; 11 | 12 | return curPos; 13 | } 14 | 15 | override public function shouldRun(params:ModifierParameters):Bool 16 | return getPercent('vibrate', params.player) != 0; 17 | } 18 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Current supported FNF engines/frameworks 2 | 3 | ## Codename Engine 4 | - 0.1.0 (Beta) (Misses some stuff) 5 | - 1.0 6 | 7 | - `FM_ENGINE`: "CODENAME" 8 | - `FM_ENGINE_VERSION`: "" 9 | ## Psych Engine 10 | - 1.0 (also 1.0b) (Note splash Support !) 11 | - 0.7.x 12 | - 0.6.x (WARNING: Due to the flixel version (5.1.0 and below), color transforms doesn't work) 13 | 14 | - `FM_ENGINE`: "PSYCH" 15 | - `FM_ENGINE_VERSION`: "1.0" for 1.0 and 1.0b, "0.7" for 0.7.x, "0.6" for 0.6.x 16 | ## PSlice Engine 17 | - `FM_ENGINE`: "PSLICE" 18 | - `FM_ENGINE_VERSION`: "1.0" (always put 1.0) 19 | ## FPS Plus 20 | - 6.x 21 | 22 | - `FM_ENGINE`: "FPSPLUS" 23 | - `FM_ENGINE_VERSION`: "" -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/SchmovinDrunk.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class SchmovinDrunk extends Modifier { 7 | final thtdiv = 1 / 222; 8 | 9 | override public function render(curPos:Vector3, params:ModifierParameters) { 10 | var phaseShift = params.lane * 0.5 + (params.distance * thtdiv) * Math.PI; 11 | curPos.x += sin(params.curBeat * .25 * Math.PI + phaseShift) * ARROW_SIZEDIV2 * getPercent('schmovinDrunk', params.player); 12 | 13 | return curPos; 14 | } 15 | 16 | override public function shouldRun(params:ModifierParameters):Bool 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/SawTooth.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class SawTooth extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | var player = params.player; 9 | final period = 1 + getPercent("sawtoothPeriod", player); 10 | curPos.x += (getPercent('sawtooth', 11 | player) * ARROW_SIZE) * ((0.5 / period * params.distance) / ARROW_SIZE - Math.floor((0.5 / period * params.distance) / ARROW_SIZE)); 12 | 13 | return curPos; 14 | } 15 | 16 | override public function shouldRun(params:ModifierParameters):Bool 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /modchart/backend/graphics/import.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.graphics; 2 | 3 | #if !macro 4 | import flixel.FlxCamera; 5 | import flixel.FlxG; 6 | import flixel.FlxSprite; 7 | import flixel.graphics.tile.FlxDrawTrianglesItem.DrawData; 8 | import flixel.math.FlxAngle; 9 | import flixel.math.FlxMath; 10 | import flixel.math.FlxMatrix; 11 | import haxe.ds.Vector as NativeVector; 12 | import modchart.backend.core.ModifierOutput; 13 | import modchart.backend.graphics.ModchartRenderer.FMDrawInstruction; 14 | import modchart.backend.graphics.ModchartRenderer; 15 | import openfl.Vector; 16 | import openfl.display.GraphicsPathCommand; 17 | import openfl.display.Shape; 18 | import openfl.geom.ColorTransform; 19 | import openfl.geom.Matrix; 20 | #end 21 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/OpponentSwap.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.util.ModchartUtil; 6 | 7 | class OpponentSwap extends Modifier { 8 | override public function render(curPos:Vector3, params:ModifierParameters) { 9 | final player = params.player; 10 | final perc = getPercent('opponentSwap', player); 11 | 12 | if (perc == 0) 13 | return curPos; 14 | 15 | var distX = WIDTH * .5; 16 | curPos.x -= distX * ModchartUtil.sign((player + 1) * 2 - 3) * perc; 17 | return curPos; 18 | } 19 | 20 | override public function shouldRun(params:ModifierParameters):Bool 21 | return true; 22 | } 23 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/ZigZag.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class ZigZag extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | final zigzag = getPercent('zigZag', params.player); 9 | 10 | if (zigzag == 0) 11 | return curPos; 12 | 13 | var theta = -params.distance / ARROW_SIZE * Math.PI; 14 | var outRelative = Math.acos(cos(theta + Math.PI / 2)) / Math.PI * 2 - 1; 15 | 16 | curPos.x += outRelative * ARROW_SIZEDIV2 * zigzag; 17 | 18 | return curPos; 19 | } 20 | 21 | override public function shouldRun(params:ModifierParameters):Bool 22 | return true; 23 | } 24 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/LocalRotate.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class LocalRotate extends Rotate { 9 | override public function getOrigin(curPos:Vector3, params:ModifierParameters):Vector3 { 10 | var fixedLane = Math.round(getKeyCount(params.player) * .5); 11 | return new Vector3(getReceptorX(fixedLane, params.player), getReceptorY(fixedLane, params.player)); 12 | } 13 | 14 | override public function getRotateName():String 15 | return 'localRotate'; 16 | 17 | override public function shouldRun(params:ModifierParameters):Bool 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/Wiggle.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import flixel.math.FlxAngle; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | 7 | class Wiggle extends Modifier { 8 | override public function render(curPos:Vector3, params:ModifierParameters) { 9 | var wiggle = getPercent('wiggle', params.player); 10 | curPos.x += sin(params.curBeat) * wiggle * 20; 11 | curPos.y += sin(params.curBeat + 1) * wiggle * 20; 12 | 13 | setPercent('rotateZ', (sin(params.curBeat) * 0.2 * wiggle) * FlxAngle.TO_DEG); 14 | 15 | return curPos; 16 | } 17 | 18 | override public function shouldRun(params:ModifierParameters):Bool 19 | return getPercent('wiggle', params.player) != 0; 20 | } 21 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/SchmovinTornado.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class SchmovinTornado extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | final tord = getPercent('schmovinTornado', params.player); 9 | 10 | if (tord == 0) 11 | return curPos; 12 | final columnShift = params.lane * Math.PI / 3; 13 | final strumNegator = (-cos(-columnShift) + 1) / 2 * ARROW_SIZE * 3; 14 | curPos.x += ((-cos((params.distance / 135) - columnShift) + 1) / 2 * ARROW_SIZE * 3 - strumNegator) * tord; 15 | 16 | return curPos; 17 | } 18 | 19 | override public function shouldRun(params:ModifierParameters):Bool 20 | return true; 21 | } 22 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Invert.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Invert extends Modifier { 7 | var invID = 0; 8 | var flpID = 0; 9 | 10 | public function new(pf) { 11 | super(pf); 12 | 13 | invID = findID('invert'); 14 | flpID = findID('flip'); 15 | } 16 | 17 | override public function render(curPos:Vector3, params:ModifierParameters) { 18 | final player = params.player; 19 | final invert = -(params.lane % 2 - 0.5) * 2; 20 | final flip = (params.lane - 1.5) * -2; 21 | 22 | curPos.x += ARROW_SIZE * (invert * getUnsafe(invID, player) + flip * getUnsafe(flpID, player)); 23 | 24 | return curPos; 25 | } 26 | 27 | override public function shouldRun(params:ModifierParameters):Bool 28 | return true; 29 | } 30 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### Event Manager 2 | - [X] Fix ease and set overlapping 3 | 4 | ### ModifierGroup 5 | - [ ] Optimize percent management system as much as it can (working on it) 6 | - activeMods var 7 | - percentsBackup var 8 | 9 | ### ModchartEnvironment (new class) 10 | - [ ] Concept: Be able to use function formats from other frameworks (such as Andromeda's ModManager/Troll Engine or Zoro's Modcharting Tools or even the Mirin Template (maybe)). This would make it easier to port modcharts already made for those frameworks to FunkinModchart, or also for those who are already familiar with those frameworks and would like to make a modchart in FunkinModchart. (working on it) 11 | 12 | ### ModchartRenderer 13 | - Switch to a custom Graphics instance, 14 | so we can copy an use it as a actor 15 | frame (also would be possible to 16 | transform the entire playfield). -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Bounce.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Bounce extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | var player = params.player; 9 | var speed = getPercent('bounceSpeed', player); 10 | var offset = getPercent('bounceOffset', player); 11 | 12 | var bounce = Math.abs(sin((params.curBeat + offset) * (1 + speed) * Math.PI)) * ARROW_SIZE; 13 | 14 | curPos.x += bounce * getPercent('bounceX', player); 15 | curPos.y += bounce * (getPercent('bounce', player) + getPercent('bounceY', player)); 16 | curPos.z += bounce * getPercent('bounceZ', player); 17 | 18 | return curPos; 19 | } 20 | 21 | override public function shouldRun(params:ModifierParameters):Bool 22 | return true; 23 | } 24 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/FieldRotate.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class FieldRotate extends Rotate { 9 | override public function getOrigin(curPos:Vector3, params:ModifierParameters):Vector3 { 10 | var x:Float = (WIDTH * 0.5) - ARROW_SIZE - 54 + ARROW_SIZE * 1.5; 11 | switch (params.player) { 12 | case 0: 13 | x -= WIDTH * 0.5 - ARROW_SIZE * 2 - 100; 14 | case 1: 15 | x += WIDTH * 0.5 - ARROW_SIZE * 2 - 100; 16 | } 17 | x -= 56; 18 | 19 | return new Vector3(x, HEIGHT * 0.5); 20 | } 21 | 22 | override public function getRotateName():String 23 | return 'fieldRotate'; 24 | 25 | override public function shouldRun(params:ModifierParameters):Bool 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Square.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Square extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | var player = params.player; 9 | final squarep = getPercent('square', player); 10 | 11 | if (squarep == 0) 12 | return curPos; 13 | 14 | final offset = getPercent("squareOffset", player); 15 | final period = getPercent("squarePeriod", player); 16 | final amp = (Math.PI * (params.distance + offset) / (ARROW_SIZE + (period * ARROW_SIZE))); 17 | 18 | curPos.x += squarep * square(amp); 19 | 20 | return curPos; 21 | } 22 | 23 | function square(angle:Float):Float { 24 | var fAngle = angle % (Math.PI * 2); 25 | return fAngle >= Math.PI ? -1.0 : 1.0; 26 | } 27 | 28 | override public function shouldRun(params:ModifierParameters):Bool 29 | return true; 30 | } 31 | -------------------------------------------------------------------------------- /modchart/backend/standalone/Adapter.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone; 2 | 3 | import haxe.macro.Compiler; 4 | 5 | class Adapter { 6 | public static var instance:IAdapter; 7 | private static var ENGINE_NAME:String = Compiler.getDefine("FM_ENGINE"); 8 | 9 | public static function init() { 10 | if (instance != null) 11 | return; 12 | 13 | final possibleClientName = ENGINE_NAME.substr(0, 1).toUpperCase() + ENGINE_NAME.substr(1).toLowerCase(); 14 | final adapter = Type.createInstance(Type.resolveClass('modchart.backend.standalone.adapters.${ENGINE_NAME.toLowerCase()}.' + possibleClientName), []); 15 | 16 | #if FM_VERBOSE 17 | trace('[FunkinModchart Verbose] Finding possible adapter from "modchart.backend.standalone.adapters.${ENGINE_NAME.toLowerCase()}.${possibleClientName}"'); 18 | #end 19 | 20 | if (adapter == null) 21 | throw 'Adapter not found for $ENGINE_NAME'; 22 | 23 | #if FM_VERBOSE 24 | trace('[FunkinModchart Verbose] Found Adapter!'); 25 | #end 26 | 27 | instance = adapter; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Skew.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.core.VisualParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class Skew extends Modifier { 9 | var xID = 0; 10 | var yID = 0; 11 | 12 | public function new(pf) { 13 | super(pf); 14 | 15 | xID = findID('skewX'); 16 | yID = findID('skewY'); 17 | } 18 | 19 | override public function visuals(data:VisualParameters, params:ModifierParameters):VisualParameters { 20 | final receptorName = Std.string(params.lane); 21 | final player = params.player; 22 | 23 | final x = getUnsafe(xID, player) + getPercent('skewX' + receptorName, player); 24 | final y = getUnsafe(yID, player) + getPercent('skewY' + receptorName, player); 25 | 26 | data.skewX += x; 27 | data.skewY += y; 28 | 29 | return data; 30 | } 31 | 32 | override public function shouldRun(params:ModifierParameters):Bool 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/ArrowShape.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.engine.PlayField; 6 | import modchart.engine.modifiers.list.PathModifier.PathNode; 7 | 8 | class ArrowShape extends PathModifier { 9 | public function new(pf:PlayField) { 10 | var path:Array = []; 11 | 12 | for (line in ModchartUtil.coolTextFile('modchart/arrowShape.csv')) { 13 | var coords = line.split(';'); 14 | path.push({ 15 | x: Std.parseFloat(coords[0]) * 200, 16 | y: Std.parseFloat(coords[1]) * 200, 17 | z: Std.parseFloat(coords[2]) * 200 18 | }); 19 | } 20 | 21 | super(pf, path); 22 | 23 | pathOffset.setTo(WIDTH * 0.5, HEIGHT * 0.5 + 280, 0); 24 | } 25 | 26 | override function render(pos:Vector3, params:ModifierParameters) { 27 | var perc = getPercent('arrowshape', params.player); 28 | 29 | if (perc == 0) 30 | return pos; 31 | 32 | pathOffset.z = pos.z; 33 | return computePath(pos, params, perc); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Tipsy.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Tipsy extends Modifier { 7 | override public function render(curPos:Vector3, params:ModifierParameters) { 8 | var player = params.player; 9 | 10 | var xVal = getPercent('tipsyX', player); 11 | var yVal = getPercent('tipsy', player) + getPercent('tipsyY', player); 12 | var zVal = getPercent('tipsyZ', player); 13 | 14 | if (xVal == 0 && yVal == 0 && zVal == 0) 15 | return curPos; 16 | 17 | var speed = getPercent('tipsySpeed', player); 18 | var offset = getPercent('tipsyOffset', player); 19 | 20 | var tipsy = (cos((params.songTime * 0.001 * ((speed * 1.2) + 1.2) + params.lane * ((offset * 1.8) + 1.8))) * ARROW_SIZE * .4); 21 | 22 | var tipAddition = new Vector3(xVal, yVal, zVal); 23 | tipAddition.scaleBy(tipsy); 24 | 25 | return curPos.add(tipAddition); 26 | } 27 | 28 | override public function shouldRun(params:ModifierParameters):Bool 29 | return true; 30 | } 31 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Infinite.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class Infinite extends Modifier { 9 | override function render(pos:Vector3, params:ModifierParameters) { 10 | var perc = getPercent('infinite', params.player); 11 | 12 | if (perc == 0) 13 | return pos; 14 | 15 | var infinite = new Vector3(); 16 | 17 | // alternate the angles 18 | var rat = params.lane % 2 == 0 ? 1 : -1; 19 | // adding 45° so arrow hit position is at center 20 | var fTime = (-params.distance * Math.PI * 0.001) + rat * Math.PI / 2; 21 | // used for make the curve 22 | final invTransf = (2 / (3 - cos(fTime * 2))); 23 | 24 | // apply the scroll 25 | infinite.setTo(WIDTH * .5 + invTransf * cos(fTime) * 580, HEIGHT * .5 + invTransf * (sin(fTime * 2) * .5) * 750, 0); 26 | 27 | return ModchartUtil.lerpVector3D(pos, infinite, perc); 28 | } 29 | 30 | override public function shouldRun(params:ModifierParameters):Bool 31 | return true; 32 | } 33 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/CounterClockWise.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.util.ModchartUtil; 6 | 7 | class CounterClockWise extends Modifier { 8 | override public function render(curPos:Vector3, params:ModifierParameters) { 9 | var strumTime = params.songTime + params.distance; 10 | var centerX = WIDTH * .5; 11 | var centerY = HEIGHT * .5; 12 | var radiusOffset = ARROW_SIZE * (params.lane - 1.5); 13 | 14 | var crochet = Adapter.instance.getCurrentCrochet(); 15 | 16 | var radius = 200 + radiusOffset * cos(strumTime / crochet * .25 / 16 * Math.PI); 17 | var outX = centerX + cos(strumTime / crochet / 4 * Math.PI) * radius; 18 | var outY = centerY + sin(strumTime / crochet / 4 * Math.PI) * radius; 19 | 20 | return ModchartUtil.lerpVector3D(curPos, new Vector3(outX, outY, 0, 0), getPercent('counterClockWise', params.player)); 21 | } 22 | 23 | override public function shouldRun(params:ModifierParameters):Bool 24 | return getPercent('counterclockwise', params.player) != 0; 25 | } 26 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/Spiral.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.util.ModchartUtil; 6 | 7 | class Spiral extends Modifier { 8 | override public function render(curPos:Vector3, params:ModifierParameters) { 9 | var player = params.player; 10 | var PI = Math.PI; 11 | var centerX = WIDTH * .5; 12 | var centerY = HEIGHT * .5; 13 | var radiusOffset = -params.distance * .25; 14 | var crochet = Adapter.instance.getCurrentCrochet(); 15 | var radius = radiusOffset + getPercent('spiralDist', player) * params.lane; 16 | var outX = centerX + cos(-params.distance / crochet * PI + params.curBeat * (PI * .25)) * radius; 17 | var outY = centerY + sin(-params.distance / crochet * PI - params.curBeat * (PI * .25)) * radius; 18 | 19 | return ModchartUtil.lerpVector3D(curPos, new Vector3(outX, outY, radius / (centerY * 4) - 1, 0), getPercent('spiral', player)); 20 | } 21 | 22 | override public function shouldRun(params:ModifierParameters):Bool 23 | return getPercent('spiral', params.player) != 0; 24 | } 25 | -------------------------------------------------------------------------------- /modchart/engine/events/types/AddEvent.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events.types; 2 | 3 | import flixel.math.FlxMath; 4 | import flixel.tweens.FlxEase.EaseFunction; 5 | import flixel.tweens.FlxEase; 6 | 7 | class AddEvent extends EaseEvent { 8 | public var addAmount:Float = 0.; 9 | 10 | public function new(mod:String, beat:Float, len:Float, addition:Float, ease:EaseFunction, player:Int, parent:EventManager) { 11 | super(mod, beat, len, addAmount = addition, ease, player, parent); 12 | 13 | type = ADD; 14 | } 15 | 16 | override function update(curBeat:Float) { 17 | if (fired) 18 | return; 19 | 20 | if (curBeat < endBeat) { 21 | if (entryPerc == null) 22 | entryPerc = ModchartUtil.findEntryFrom(this); 23 | 24 | var progress = (curBeat - startBeat) / (endBeat - startBeat); 25 | // maybe we should make it use bound? 26 | var out = FlxMath.lerp(entryPerc, entryPerc + addAmount, ease(progress)); 27 | setModPercent(name, out, player); 28 | fired = false; 29 | } else if (curBeat >= endBeat) { 30 | fired = true; 31 | 32 | // we're using the ease function bc it may dont return 1 33 | setModPercent(name, entryPerc + ease(1) * addAmount, player); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Transform.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Transform extends Modifier { 7 | var xID = 0; 8 | var yID = 0; 9 | var zID = 0; 10 | 11 | var xOID = 0; 12 | var yOID = 0; 13 | var zOID = 0; 14 | 15 | public function new(pf) { 16 | super(pf); 17 | 18 | xID = findID('x'); 19 | yID = findID('y'); 20 | zID = findID('z'); 21 | 22 | xOID = findID('xoffset'); 23 | yOID = findID('yoffset'); 24 | zOID = findID('zoffset'); 25 | } 26 | 27 | override public function render(curPos:Vector3, params:ModifierParameters) { 28 | var receptorName = Std.string(params.lane); 29 | var player = params.player; 30 | 31 | curPos.x += getUnsafe(xID, player) + getUnsafe(xOID, player) + getPercent('x' + receptorName, player); 32 | curPos.y += getUnsafe(yID, player) + getUnsafe(yOID, player) + getPercent('y' + receptorName, player); 33 | curPos.z += getUnsafe(zID, player) + getUnsafe(zOID, player) + getPercent('z' + receptorName, player); 34 | 35 | return curPos; 36 | } 37 | 38 | override public function shouldRun(params:ModifierParameters):Bool 39 | return true; 40 | } 41 | -------------------------------------------------------------------------------- /modchart/backend/core/PercentArray.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.core; 2 | 3 | import haxe.ds.Vector; 4 | 5 | // basicly 2d vector with string hashing 6 | // used to store modifier values 7 | #if !openfl_debug 8 | @:fileXml('tags="haxe,release"') 9 | @:noDebug 10 | #end 11 | final class PercentArray { 12 | private var vector:Vector>; 13 | 14 | public function new() { 15 | vector = new Vector>(Std.int(Math.pow(2, 16))); // preallocate by max 16-bit integer 16 | } 17 | 18 | // hash the key to a 16-bit integer 19 | @:noDebug @:noCompletion inline private function __hashKey(key:String):Int { 20 | var hash:Int = 0; 21 | var len = key.length; 22 | for (i in 0...len) { 23 | hash = hash * 31 + StringTools.unsafeCodeAt(key, i); 24 | } 25 | 26 | return hash & 0xFFFF; // 16-bit hash 27 | } 28 | 29 | @:noDebug 30 | inline public function set(key:String, value:Vector):Void 31 | setUnsafe(__hashKey(key), value); 32 | 33 | @:noDebug 34 | public function get(key:String):Vector 35 | return getUnsafe(__hashKey(key)); 36 | 37 | @:noDebug 38 | inline public function getUnsafe(id:Int):Vector 39 | return vector.get(id); 40 | 41 | @:noDebug 42 | inline public function setUnsafe(id:Int, value:Vector):Void 43 | vector.set(id, value); 44 | } 45 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Rotate.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class Rotate extends Modifier { 9 | override public function render(curPos:Vector3, params:ModifierParameters) { 10 | var rotateName = getRotateName(); 11 | var player = params.player; 12 | 13 | var angleX = getPercent(rotateName + 'X', player); 14 | var angleY = getPercent(rotateName + 'Y', player); 15 | var angleZ = getPercent(rotateName + 'Z', player); 16 | 17 | // does angleY work here if angleX and angleZ are disabled? - ye 18 | if (angleX == 0 && angleY == 0 && angleZ == 0) 19 | return curPos; 20 | 21 | final origin:Vector3 = getOrigin(curPos, params); 22 | curPos = ModchartUtil.rotate3DVector(curPos -= origin, angleX, angleY, angleZ); 23 | curPos += origin; 24 | return curPos; 25 | } 26 | 27 | public function getOrigin(curPos:Vector3, params:ModifierParameters):Vector3 { 28 | var fixedLane = Math.round(getKeyCount(params.player) * .5); 29 | return new Vector3(getReceptorX(fixedLane, params.player), FlxG.height / 2); 30 | } 31 | 32 | public function getRotateName():String 33 | return 'rotate'; 34 | 35 | override public function shouldRun(params:ModifierParameters):Bool 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /modchart/engine/events/Event.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events; 2 | 3 | import modchart.Manager; 4 | import modchart.engine.events.EventType; 5 | 6 | class Event { 7 | public var name:String; 8 | public var target:Float; 9 | 10 | public var type:EventType = EMPTY; 11 | 12 | public var beat:Float; 13 | public var player:Int; 14 | 15 | public var prev:Event = null; 16 | 17 | public var callback:Event->Void; 18 | public var parent:EventManager; 19 | 20 | private var mercy:Bool = false; 21 | 22 | public var fired:Bool = false; 23 | 24 | public var active:Bool = false; 25 | 26 | public function new(beat:Float, callback:Event->Void, parent:EventManager, ?mercy:Bool = false) { 27 | this.beat = beat; 28 | this.callback = callback; 29 | this.mercy = mercy; 30 | 31 | this.parent = parent; 32 | } 33 | 34 | public function update(curBeat:Float) { 35 | if (curBeat >= beat && callback != null) { 36 | callback(this); 37 | 38 | fired = !mercy; 39 | 40 | if (fired) 41 | callback = null; 42 | } 43 | } 44 | 45 | public function create() {} 46 | 47 | public inline function setModPercent(name, value, player) { 48 | parent.pf.setPercent(name, value, player); 49 | } 50 | 51 | public inline function getModPercent(name, player):Float { 52 | return parent.pf.getPercent(name, player); 53 | } 54 | 55 | inline public function getType():EventType { 56 | return type; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Drunk.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | class Drunk extends Modifier { 4 | var dID:Int; 5 | 6 | var dXID:Int; 7 | var dYID:Int; 8 | var dZID:Int; 9 | 10 | var dS:Int; 11 | var dP:Int; 12 | var dO:Int; 13 | 14 | public function new(pf) { 15 | super(pf); 16 | 17 | dID = findID('drunk'); 18 | 19 | dXID = findID('drunkx'); 20 | dYID = findID('drunky'); 21 | dZID = findID('drunkz'); 22 | 23 | dS = findID('drunkSpeed'); 24 | dP = findID('drunkPeriod'); 25 | dO = findID('drunkOffset'); 26 | } 27 | 28 | override public function render(curPos:Vector3, params:ModifierParameters) { 29 | var player = params.player; 30 | 31 | var xVal = getUnsafe(dID, player) + getUnsafe(dXID, player); 32 | var yVal = getUnsafe(dYID, player); 33 | var zVal = getUnsafe(dZID, player); 34 | 35 | if (xVal == 0 && yVal == 0 && zVal == 0) 36 | return curPos; 37 | 38 | var speed = getUnsafe(dS, player); 39 | var period = getUnsafe(dP, player); 40 | var offset = getUnsafe(dO, player); 41 | 42 | var shift = params.songTime * 0.001 * (1 + speed) + params.lane * ((offset * 0.2) + 0.2) + params.distance * ((period * 10) + 10) / HEIGHT; 43 | var drunk = (cos(shift) * ARROW_SIZE * 0.5); 44 | 45 | curPos.x += drunk * xVal; 46 | curPos.y += drunk * yVal; 47 | curPos.z += drunk * zVal; 48 | 49 | return curPos; 50 | } 51 | 52 | override public function shouldRun(params:ModifierParameters):Bool 53 | return true; 54 | } 55 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Boost.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import flixel.tweens.FlxEase; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | import modchart.backend.util.ModchartUtil; 8 | 9 | class Boost extends Modifier { 10 | public function new(pf) { 11 | super(pf); 12 | 13 | setPercent('waveMult', 1, -1); 14 | } 15 | 16 | static final DIV38 = 1 / 38; 17 | 18 | override public function render(curPos:Vector3, params:ModifierParameters) { 19 | var player = params.player; 20 | var lane = Std.string(params.lane); 21 | 22 | final boost = (getPercent('boost', params.player) + getPercent('boost' + lane, params.player)); 23 | final brake = (getPercent('brake', params.player) + getPercent('brake' + lane, params.player)); 24 | final wave = (getPercent('wave', params.player) + getPercent('wave' + lane, params.player)); 25 | 26 | if (boost != 0 || brake != 0) { 27 | final scale = HEIGHT * getPercent('boostScale', player); 28 | final shift = params.distance * 1.5 / ((params.distance + (scale) / 1.2) / scale); 29 | curPos.y += ModchartUtil.clamp((boost - brake) * (shift - params.distance), -600, 600); 30 | } 31 | if (wave != 0) 32 | curPos.y += (-wave * 100) * sin(params.distance * DIV38 * getPercent('waveMult', player) * 0.2); 33 | 34 | return curPos; 35 | } 36 | 37 | override public function shouldRun(params:ModifierParameters):Bool 38 | return true; 39 | } 40 | -------------------------------------------------------------------------------- /modchart/engine/events/types/EaseEvent.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events.types; 2 | 3 | import flixel.math.FlxMath; 4 | import flixel.tweens.FlxEase.EaseFunction; 5 | import flixel.tweens.FlxEase; 6 | 7 | class EaseEvent extends Event { 8 | public var startBeat:Float; 9 | public var endBeat:Float; 10 | 11 | public var beatLength:Float; 12 | public var ease:EaseFunction; 13 | 14 | public function new(mod:String, beat:Float, len:Float, target:Float, ease:EaseFunction, player:Int, parent:EventManager) { 15 | this.name = mod; 16 | this.player = player; 17 | 18 | this.startBeat = beat; 19 | this.endBeat = beat + len; 20 | this.beatLength = len; 21 | this.ease = ease != null ? ease : FlxEase.linear; 22 | 23 | this.target = target; 24 | 25 | super(beat, (_) -> {}, parent, true); 26 | 27 | type = EASE; 28 | } 29 | 30 | var entryPerc:Null = null; 31 | 32 | override function update(curBeat:Float) { 33 | if (fired) 34 | return; 35 | 36 | if (curBeat < endBeat) { 37 | if (entryPerc == null) 38 | entryPerc = ModchartUtil.findEntryFrom(this); 39 | 40 | var progress = (curBeat - startBeat) / (endBeat - startBeat); 41 | // maybe we should make it use bound? 42 | var out = FlxMath.lerp(entryPerc, target, ease(progress)); 43 | setModPercent(name, out, player); 44 | fired = false; 45 | } else if (curBeat >= endBeat) { 46 | fired = true; 47 | 48 | // we're using the ease function bc it may dont return 1 49 | setModPercent(name, ease(1) * target, player); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Scale.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.core.VisualParameters; 6 | 7 | class Scale extends Modifier { 8 | public function new(pf) { 9 | super(pf); 10 | 11 | setPercent('scale', 1, -1); 12 | setPercent('scaleX', 1, -1); 13 | setPercent('scaleY', 1, -1); 14 | } 15 | 16 | private inline function applyScale(vis:VisualParameters, params:ModifierParameters, axis:String, realAxis:String) { 17 | var receptorName = Std.string(params.lane); 18 | var player = params.player; 19 | 20 | var scale = 1.; 21 | // Scale 22 | scale *= getPercent('scale' + axis, player) + getPercent('scale' + axis + receptorName, player); 23 | scale *= 1 - (getPercent('tiny' + axis, player) + getPercent('tiny' + axis + receptorName, player)) * 0.5; 24 | 25 | switch (realAxis) { 26 | case 'x': 27 | vis.scaleX *= scale; 28 | case 'y': 29 | vis.scaleY *= scale; 30 | default: 31 | vis.scaleX *= scale; 32 | vis.scaleY *= scale; 33 | } 34 | } 35 | 36 | override public function visuals(data:VisualParameters, params:ModifierParameters) { 37 | var player = params.player; 38 | var receptorName = Std.string(params.lane); 39 | 40 | applyScale(data, params, '', ''); 41 | applyScale(data, params, 'x', 'x'); 42 | applyScale(data, params, 'y', 'y'); 43 | 44 | data.scaleX *= 1; 45 | data.scaleY *= 1; 46 | 47 | return data; 48 | } 49 | 50 | override public function shouldRun(params:ModifierParameters):Bool 51 | return true; 52 | } 53 | -------------------------------------------------------------------------------- /assets/modchart/arrowShape.csv: -------------------------------------------------------------------------------- 1 | -0.04362231492996216;-0.1374407708644867;0.0;1.0 2 | 0.23566856980323792;-0.18850013613700867;0.0;1.0 3 | 0.5062824487686157;-0.3825250566005707;0.0;1.0 4 | 0.7447052597999573;-0.6928699016571045;0.0;1.0 5 | 1.0204252004623413;-1.0911319255828857;0.0;1.0 6 | 1.1940264701843262;-1.3974874019622803;0.0;1.0 7 | 1.23487389087677;-1.5710887908935547;0.0;1.0 8 | 1.1940264701843262;-1.6732072830200195;0.0;1.0 9 | 0.9693658351898193;-1.8310518264770508;0.0;1.0 10 | 0.7929407358169556;-1.953332543373108;0.0;1.0 11 | 0.7117342352867126;-1.911144733428955;0.0;1.0 12 | 0.45396822690963745;-1.4432940483093262;0.0;1.0 13 | 0.3747375011444092;-1.4767067432403564;0.0;1.0 14 | 0.35861876606941223;-2.490757465362549;0.0;1.0 15 | 0.29408058524131775;-2.61061429977417;0.0;1.0 16 | 0.16500422358512878;-2.693591833114624;0.0;1.0 17 | -0.32364219427108765;-2.693591833114624;0.0;1.0 18 | -0.4066198766231537;-2.6382737159729004;0.0;1.0 19 | -0.4434988498687744;-2.5276365280151367;0.0;1.0 20 | -0.37570804357528687;-1.470541000366211;0.0;1.0 21 | -0.4814251661300659;-1.4611036777496338;0.0;1.0 22 | -0.8300443291664124;-1.9243381023406982;0.0;1.0 23 | -0.9308675527572632;-1.9808123111724854;0.0;1.0 24 | -1.0170416831970215;-1.92009437084198;0.0;1.0 25 | -1.2422187328338623;-1.6377930641174316;0.0;1.0 26 | -1.3053934574127197;-1.5345635414123535;0.0;1.0 27 | -1.3060877323150635;-1.4263033866882324;0.0;1.0 28 | -1.151364803314209;-1.2120769023895264;0.0;1.0 29 | -0.4995841681957245;-0.3723134696483612;0.0;1.0 30 | -0.3770420253276825;-0.2701948583126068;0.0;1.0 31 | -0.1932288110256195;-0.17828842997550964;0.0;1.0 32 | -0.07663846015930176;-0.14884309470653534;0.0;1.0 33 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Confusion.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.core.VisualParameters; 6 | 7 | class Confusion extends Modifier { 8 | private inline function applyConfusion(vis:VisualParameters, params:ModifierParameters, axis:String, realAxis:String) { 9 | final receptorName = Std.string(params.lane); 10 | final player = params.player; 11 | 12 | var angle = 0.; 13 | // real confusion 14 | angle -= (params.curBeat * (getPercent('confusion' + axis, player) + getPercent('confusion' + axis + receptorName, player))) % 360; 15 | // offset 16 | angle += getPercent('confusionOffset' + axis, player) + getPercent('confusionOffset' + axis + receptorName, player); 17 | // dizzy mods 18 | var cName = switch (realAxis) { 19 | case 'x': 'roll'; 20 | case 'y': 'twirl'; 21 | default: 'dizzy'; 22 | }; 23 | angle += getPercent(cName, player) * (params.distance * 0.1 * (1 + getPercent('${cName}Speed', player))); 24 | 25 | switch (realAxis) { 26 | case 'x': 27 | vis.angleX += angle; 28 | case 'y': 29 | vis.angleY += angle; 30 | case 'z': 31 | vis.angleZ += angle; 32 | } 33 | } 34 | 35 | override public function visuals(data:VisualParameters, params:ModifierParameters) { 36 | applyConfusion(data, params, '', 'z'); 37 | applyConfusion(data, params, 'x', 'x'); 38 | applyConfusion(data, params, 'y', 'y'); 39 | applyConfusion(data, params, 'z', 'z'); 40 | 41 | return data; 42 | } 43 | 44 | override public function shouldRun(params:ModifierParameters):Bool 45 | return true; 46 | } 47 | -------------------------------------------------------------------------------- /modchart/backend/graphics/ModchartRenderer.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.graphics; 2 | 3 | import flixel.FlxBasic; 4 | import flixel.FlxCamera; 5 | import flixel.util.FlxSort; 6 | 7 | @:publicFields 8 | @:structInit 9 | class FMDrawInstruction { 10 | var item:FlxSprite; 11 | var vertices:openfl.Vector; 12 | var uvt:openfl.Vector; 13 | var indices:openfl.Vector; 14 | var colorData:Array; 15 | 16 | var extra:Array; 17 | var mappedExtra:Map; 18 | 19 | public function new() {} 20 | } 21 | 22 | class ModchartRenderer extends FlxBasic { 23 | private var instance:Null; 24 | private var queue:NativeVector; 25 | private var count:Int = 0; 26 | private var postCount:Int = 0; 27 | 28 | private var projection(get, never):ModchartPerspective; 29 | 30 | function get_projection() 31 | return instance.projection; 32 | 33 | public function new(instance:PlayField) { 34 | super(); 35 | 36 | this.instance = instance; 37 | } 38 | 39 | // Renderer-side 40 | public function prepare(item:T) {} 41 | 42 | public function shift():Void {} 43 | 44 | public function dispose() {} 45 | 46 | // Built-in functions 47 | public function preallocate(length:Int) { 48 | queue = new NativeVector(length); 49 | count = postCount = 0; 50 | } 51 | 52 | public function sort() { 53 | if (queue == null || queue.length <= 0) 54 | return; 55 | queue.sort((a, b) -> { 56 | if (a == null || b == null) 57 | return 0; 58 | return FlxSort.byValues(FlxSort.DESCENDING, a.item._z, b.item._z); 59 | }); 60 | } 61 | 62 | // public function render(times:Null):Void {} 63 | } 64 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Zoom.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | 5 | class Zoom extends Modifier { 6 | var __curPercent:Null = -1; 7 | var __localPercent:Null = -1; 8 | 9 | override public function render(curPos:Vector3, params:ModifierParameters) { 10 | updatePercent(params); 11 | 12 | // center zoom 13 | if (__curPercent != 1) 14 | curPos = __applyZoom(curPos, new Vector3(FlxG.width * .5, FlxG.height * .5), __curPercent); 15 | if (__localPercent != 1) 16 | curPos = __applyZoom(curPos, new Vector3(getReceptorX(Math.round(getKeyCount(params.player) * .5), params.player), FlxG.height * .5), 17 | __localPercent); 18 | return curPos; 19 | } 20 | 21 | inline function __applyZoom(pos:Vector3, origin:Vector3, amount:Float) { 22 | var diff = pos.subtract(origin); 23 | diff.scaleBy(amount); 24 | return diff.add(origin); 25 | } 26 | 27 | override public function visuals(data:VisualParameters, params:ModifierParameters):VisualParameters { 28 | if (__curPercent == null) 29 | updatePercent(params); 30 | 31 | data.scaleX = data.scaleX * (__curPercent * __localPercent); 32 | data.scaleY = data.scaleY * (__curPercent * __localPercent); 33 | 34 | __curPercent = __localPercent = null; 35 | 36 | return data; 37 | } 38 | 39 | inline function updatePercent(params:ModifierParameters) { 40 | __curPercent = 1 + ((-getPercent('zoom', params.player) + getPercent('mini', params.player)) * 0.5); 41 | __localPercent = 1 + ((-getPercent('localZoom', params.player) + getPercent('localMini', params.player)) * 0.5); 42 | } 43 | 44 | override public function shouldRun(params:ModifierParameters):Bool 45 | return true; 46 | } 47 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Tornado.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | class Tornado extends Modifier { 9 | // math from open itg 10 | // hmm, looks familiar.... isnt this invert sine? 11 | override public function render(pos:Vector3, params:ModifierParameters) { 12 | var tornado = getPercent('tornado', params.player); 13 | 14 | if (tornado == 0) 15 | return pos; 16 | 17 | var keyCount = getKeyCount(); 18 | var bWideField = keyCount > 4; 19 | var iTornadoWidth = bWideField ? 4 : 3; 20 | 21 | var iColNum = params.lane; 22 | var iStartCol = iColNum - iTornadoWidth; 23 | var iEndCol = iColNum + iTornadoWidth; 24 | iStartCol = Math.round(ModchartUtil.clamp(iStartCol, 0, keyCount)); 25 | iEndCol = Math.round(ModchartUtil.clamp(iEndCol, 0, keyCount)); 26 | 27 | var fXOffset = ((ARROW_SIZE * 1.5) - (ARROW_SIZE * params.lane)); 28 | 29 | var fMinX = -fXOffset; 30 | var fMaxX = fXOffset; 31 | 32 | final fRealPixelOffset = fXOffset; 33 | var fPositionBetween = scale(fRealPixelOffset, fMinX, fMaxX, -1, 1); 34 | 35 | var fRads = Math.acos(fPositionBetween); 36 | fRads += (params.distance * 0.8) * 6 / HEIGHT; 37 | 38 | final fAdjustedPixelOffset = scale(cos(fRads), -1, 1, fMinX, fMaxX); 39 | 40 | pos.x -= (fAdjustedPixelOffset - fRealPixelOffset) * tornado; 41 | 42 | return pos; 43 | } 44 | 45 | inline function scale(x:Float, l1:Float, h1:Float, l2:Float, h2:Float) { 46 | return (x - l1) * (h2 - l2) / (h1 - l1) + l2; 47 | } 48 | 49 | override public function shouldRun(params:ModifierParameters):Bool 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/ReceptorScroll.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import flixel.math.FlxMath; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | import modchart.backend.core.VisualParameters; 8 | import modchart.backend.util.ModchartUtil; 9 | 10 | class ReceptorScroll extends Modifier { 11 | override public function render(curPos:Vector3, params:ModifierParameters) { 12 | final perc = getPercent('receptorScroll', params.player); 13 | 14 | if (perc == 0) 15 | return curPos; 16 | 17 | final moveSpeed = Adapter.instance.getCurrentCrochet() * 4; 18 | 19 | var diff = -params.distance; 20 | var songTime = Adapter.instance.getSongPosition(); 21 | var vDiff = -(diff - songTime) / moveSpeed; 22 | var reversed = Math.floor(vDiff) % 2 == 0; 23 | 24 | var startY = curPos.y; 25 | var revPerc = reversed ? 1 - vDiff % 1 : vDiff % 1; 26 | // haha perc 30 27 | var upscrollOffset = 50; 28 | var downscrollOffset = HEIGHT - 150; 29 | 30 | var endY = upscrollOffset + ((downscrollOffset - ARROW_SIZEDIV2) * revPerc) + ARROW_SIZEDIV2; 31 | 32 | curPos.y = FlxMath.lerp(startY, endY, perc); 33 | return curPos; 34 | } 35 | 36 | override public function visuals(data:VisualParameters, params:ModifierParameters):VisualParameters { 37 | final perc = getPercent('receptorScroll', params.player); 38 | if (perc == 0) 39 | return data; 40 | 41 | var bar = params.songTime / (Adapter.instance.getCurrentCrochet() * .25); 42 | var hitTime = params.distance; 43 | 44 | data.alpha = FlxMath.bound((1400 - hitTime) / 200, 0, 0.3) * perc; 45 | if ((params.distance + params.songTime) < Math.floor(bar + 1) * Adapter.instance.getCurrentCrochet() * 4) 46 | data.alpha = 1; 47 | 48 | return data; 49 | } 50 | 51 | override public function shouldRun(params:ModifierParameters):Bool 52 | return true; 53 | } 54 | -------------------------------------------------------------------------------- /modchart/backend/macros/CompiledClassList.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.macros; 2 | 3 | import haxe.rtti.Meta; 4 | 5 | /** 6 | * A complement to `ClassMacro`. See `ClassMacro` for more information. 7 | */ 8 | class CompiledClassList { 9 | static var classLists:Map>>; 10 | 11 | /** 12 | * Class lists are injected into this class's metadata during the typing phase. 13 | * This function extracts the metadata, at runtime, and stores it in `classLists`. 14 | */ 15 | static function init():Void { 16 | classLists = []; 17 | 18 | // Meta.getType returns Dynamic>. 19 | var metaData = Meta.getType(CompiledClassList); 20 | 21 | if (metaData.classLists != null) { 22 | for (list in metaData.classLists) { 23 | var data:Array = cast list; 24 | 25 | // First element is the list ID. 26 | var id:String = cast data[0]; 27 | 28 | // All other elements are class types. 29 | var classes:List> = new List(); 30 | for (i in 1...data.length) { 31 | var className:String = cast data[i]; 32 | // var classType:Class = cast data[i]; 33 | var classType:Class = cast Type.resolveClass(className); 34 | classes.push(classType); 35 | } 36 | 37 | classLists.set(id, classes); 38 | } 39 | } else { 40 | throw "Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project."; 41 | } 42 | } 43 | 44 | public static function get(request:String):List> { 45 | if (classLists == null) 46 | init(); 47 | 48 | if (!classLists.exists(request)) { 49 | trace('[WARNING] Class list $request not properly generated. Please debug the build macro.'); 50 | classLists.set(request, new List()); // Make the error only appear once. 51 | } 52 | 53 | return classLists.get(request); 54 | } 55 | 56 | public static inline function getTyped(request:String, type:Class):List> { 57 | return cast get(request); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Radionic.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | import modchart.backend.core.VisualParameters; 6 | import modchart.backend.util.ModchartUtil; 7 | 8 | // Circular motion based on the lane. 9 | // Naming this `Radionic` since it seems like a Radionic Graphic. 10 | // Inspired by `The Poenix NotITG Modchart` at 0:35 11 | // Warning!: This should be AFTER regular modifiers (drunk, beat, transform, etc) and BEFORE rotation modifiers. 12 | class Radionic extends Modifier { 13 | override public function render(pos:Vector3, params:ModifierParameters) { 14 | final perc = getPercent('radionic', params.player); 15 | 16 | if (perc == 0) 17 | return pos; 18 | 19 | final reverse = pf.modifiers.modifiers.get('reverse'); 20 | 21 | final angle = ((1 / Adapter.instance.getCurrentCrochet()) * ((params.songTime + params.distance) * Math.PI * .25) + (Math.PI * params.player)); 22 | final offsetX = pos.x - getReceptorX(params.lane, params.player); 23 | final offsetY = reverse != null ? (pos.y - reverse.render(pos, params).y) : 0; 24 | 25 | final circf = ARROW_SIZE + params.lane * ARROW_SIZE; 26 | 27 | final sinAng = sin(angle); 28 | final cosAng = cos(angle); 29 | 30 | final radionicVec = new Vector3(); 31 | 32 | radionicVec.x = WIDTH * 0.5 + ((sinAng * offsetY + cosAng * (circf + offsetX)) * 0.7) * 1.125; 33 | radionicVec.y = HEIGHT * 0.5 + ((cosAng * offsetY + sinAng * (circf + offsetX)) * 0.7) * 0.875; 34 | radionicVec.z = pos.z; 35 | 36 | return pos.interpolate(radionicVec, perc, pos); 37 | } 38 | 39 | override public function visuals(data:VisualParameters, params:ModifierParameters):VisualParameters { 40 | final perc = getPercent('radionic', params.player); 41 | final amount = 0.6; 42 | 43 | data.scaleX = perc * (data.scaleY = 1 + amount - FlxEase.cubeOut((params.curBeat - Math.floor(params.curBeat))) * amount); 44 | data.glow = perc * (-(amount - FlxEase.cubeOut((params.curBeat - Math.floor(params.curBeat))) * amount) * 2); 45 | 46 | return data; 47 | } 48 | 49 | override public function shouldRun(params:ModifierParameters):Bool 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/PathModifier.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import haxe.ds.Vector; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | import modchart.backend.util.ModchartUtil; 8 | import modchart.engine.PlayField; 9 | 10 | /** 11 | * Manages path-based transformations for arrows. 12 | * 13 | * This modifier interpolates arrow positions along a predefined path, 14 | * allowing smooth transitions and animations. 15 | * 16 | * @author TheoDev 17 | */ 18 | class PathModifier extends Modifier { 19 | private var __path:Vector; 20 | private var __pathBound(default, set):Float; 21 | 22 | private var __boundDiv:Float; 23 | 24 | function set___pathBound(value:Float):Float { 25 | __boundDiv = 1 / value; 26 | return __pathBound = value; 27 | } 28 | 29 | public var pathOffset:Vector3 = new Vector3(); 30 | 31 | public function new(pf:PlayField, path:Array) { 32 | super(pf); 33 | 34 | __pathBound = 1500; 35 | loadPath(path); 36 | } 37 | 38 | public function loadPath(newPath:Array) { 39 | __path = Vector.fromArrayCopy(newPath); 40 | } 41 | 42 | public function computePath(pos:Vector3, params:ModifierParameters, percent:Float) { 43 | final __path_length = __path.length; 44 | if (__path_length <= 0) 45 | return pos; 46 | if (__path_length == 1) { 47 | final pathNode = __path[0]; 48 | return new Vector3(pathNode.x, pathNode.y, pathNode.z); 49 | } 50 | 51 | final nodeProgress = (__path_length - 1) * Math.min(__pathBound, params.distance) * __boundDiv; 52 | final thisNodeIndex = Math.floor(nodeProgress); 53 | final nextNodeIndex = FlxMath.minInt(thisNodeIndex + 1, __path_length - 1); 54 | final nextNodeRatio = nodeProgress - thisNodeIndex; 55 | 56 | final thisNode = __path[thisNodeIndex]; 57 | final nextNode = __path[nextNodeIndex]; 58 | 59 | return pos.interpolate(new Vector3(FlxMath.lerp(thisNode.x, nextNode.x, nextNodeRatio), FlxMath.lerp(thisNode.y, nextNode.y, nextNodeRatio), 60 | FlxMath.lerp(thisNode.z, nextNode.z, nextNodeRatio)).add(pathOffset), 61 | percent, pos); 62 | } 63 | 64 | override public function shouldRun(params:ModifierParameters):Bool 65 | return true; 66 | } 67 | 68 | @:structInit 69 | class PathNode { 70 | public var x:Float; 71 | public var y:Float; 72 | public var z:Float; 73 | } 74 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Drugged.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import modchart.backend.core.ArrowData; 5 | import modchart.backend.core.ModifierParameters; 6 | import modchart.backend.core.VisualParameters; 7 | import modchart.backend.util.ModchartUtil; 8 | 9 | class Drugged extends Modifier { 10 | override public function render(curPos:Vector3, params:ModifierParameters) { 11 | var amplitude = 1.; 12 | var frequency = 1.; 13 | 14 | var x = (params.distance * 0.009) + (params.lane * 0.125); 15 | var y = 0.; 16 | y = sin(x * frequency); 17 | var t = 0.01 * (-Adapter.instance.getSongPosition() * 0.0025 * 130.0); 18 | y += sin(x * frequency * 2.1 + t) * 4.5; 19 | y += sin(x * frequency * 1.72 + t * 1.121) * 4.0; 20 | y += sin(x * frequency * 2.221 + t * 0.437) * 5.0; 21 | y += sin(x * frequency * 3.1122 + t * 4.269) * 2.5; 22 | y *= amplitude * 0.06; 23 | 24 | curPos.x += y * getPercent('drugged', params.player) * ARROW_SIZE * 0.8; 25 | 26 | return curPos; 27 | } 28 | 29 | override public function visuals(visuals:VisualParameters, params:ModifierParameters) { 30 | var drug = getPercent('drugged', params.player); 31 | 32 | var amplitude = 1.; 33 | var frequency = 1.; 34 | 35 | var x = (params.distance * 0.025) + (params.lane * 0.3); 36 | var y = 0.; 37 | y = sin(x * frequency); 38 | var t = 0.01 * (-Adapter.instance.getSongPosition() * 0.005 * 130.0); 39 | y += sin(x * frequency * 2.1 + t) * 4.5; 40 | y += sin(x * frequency * 1.72 + t * 1.121) * 4.0; 41 | y += sin(x * frequency * 2.221 + t * 0.437) * 5.0; 42 | y += sin(x * frequency * 3.1122 + t * 4.269) * 2.5; 43 | y *= amplitude * 0.06; 44 | 45 | y = -FlxMath.bound(y, -1, 1); 46 | 47 | var squishX = 1 + FlxMath.bound(y, -1, 0) * -1 * 0.6; 48 | var squishY = 1 + FlxMath.bound(y, 0, 1) * 0.6; 49 | 50 | visuals.scaleX *= squishX * drug; 51 | visuals.scaleY *= squishY * drug; 52 | 53 | var preproduct = Math.asin(y); 54 | // var cosdY = cos(preproduct); 55 | 56 | visuals.glow = y * -.7; 57 | visuals.glowR -= 0.5 + sin(preproduct * 1.4) * .5; 58 | visuals.glowG += 0.4 + cos(preproduct * 0.5) * .6; 59 | visuals.glowB -= 0.2 + tan(preproduct) * .8; 60 | 61 | return visuals; 62 | 63 | // curPos.x += y * getPercent('drugged', params.player); 64 | } 65 | 66 | override public function shouldRun(params:ModifierParameters):Bool 67 | return true; 68 | } 69 | -------------------------------------------------------------------------------- /modchart/backend/math/Quaternion.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.math; 2 | 3 | import modchart.backend.util.ModchartUtil; 4 | 5 | #if !openfl_debug 6 | @:fileXml('tags="haxe,release"') 7 | @:noDebug 8 | #end 9 | @:publicFields 10 | final class Quaternion { 11 | var x:Float; 12 | var y:Float; 13 | var z:Float; 14 | var w:Float; 15 | 16 | // This could be inline, to make local quaternions abstracted away 17 | function new(x:Float, y:Float, z:Float, w:Float) { 18 | this.x = x; 19 | this.y = y; 20 | this.z = z; 21 | this.w = w; 22 | } 23 | 24 | @:pure @:noDebug 25 | inline function multiply(q:Quaternion):Quaternion { 26 | return new Quaternion(w * q.x 27 | + x * q.w 28 | + y * q.z 29 | - z * q.y, w * q.y 30 | - x * q.z 31 | + y * q.w 32 | + z * q.x, w * q.z 33 | + x * q.y 34 | - y * q.x 35 | + z * q.w, 36 | w * q.w 37 | - x * q.x 38 | - y * q.y 39 | - z * q.z); 40 | } 41 | 42 | @:pure @:noDebug 43 | inline function multiplyInPlace(q:Quaternion):Void { 44 | var x = this.x; 45 | var y = this.y; 46 | var z = this.z; 47 | var w = this.w; 48 | 49 | this.x = w * q.x + x * q.w + y * q.z - z * q.y; 50 | this.y = w * q.y - x * q.z + y * q.w + z * q.x; 51 | this.z = w * q.z + x * q.y - y * q.x + z * q.w; 52 | this.w = w * q.w - x * q.x - y * q.y - z * q.z; 53 | } 54 | 55 | @:pure @:noDebug 56 | inline function multiplyInVector(v:Vector3):Quaternion { 57 | final vx = v.x, vy = v.y, vz = v.z, w = this.w; 58 | 59 | // @formatter:off 60 | return new Quaternion( 61 | w * vx + y * vz - z * vy, 62 | w * vy - x * vz + z * vx, 63 | w * vz + x * vy - y * vx, 64 | -x * vx - y * vy - z * vz 65 | ); 66 | // @formatter:on 67 | } 68 | 69 | @:pure @:noDebug 70 | inline function multiplyInv(q:Quaternion):Vector3 { 71 | final qw = q.w, qx = q.x, qy = q.y, qz = q.z; 72 | final nx = -x, ny = -y, nz = -z, w = this.w; 73 | 74 | // @formatter:off 75 | return new Vector3( 76 | qw * nx + qx * w + qy * nz - qz * ny, 77 | qw * ny - qx * nz + qy * w + qz * nx, 78 | qw * nz + qx * ny - qy * nx + qz * w 79 | ); 80 | // @formatter:on 81 | } 82 | 83 | @:pure @:noDebug 84 | inline function rotateVector(v:Vector3):Vector3 { 85 | var qVec = multiplyInVector(v); 86 | return multiplyInv(qVec); 87 | } 88 | 89 | static function fromAxisAngle(axis:Vector3, angleRad:Float):Quaternion { 90 | var sinHalfAngle = ModchartUtil.sin(angleRad * .5); 91 | var cosHalfAngle = ModchartUtil.cos(angleRad * .5); 92 | return new Quaternion(axis.x * sinHalfAngle, axis.y * sinHalfAngle, axis.z * sinHalfAngle, cosHalfAngle); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/SchmovinArrowShape.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import flixel.math.FlxMath; 4 | 5 | private class TimeVector extends Vector3D { 6 | public var startDist = 0.0; 7 | public var endDist = 0.0; 8 | public var next:TimeVector; 9 | } 10 | 11 | class SchmovinArrowShape extends Modifier { 12 | var _path:List; 13 | var _pathDistance:Float = 0; 14 | 15 | static inline var SCALE:Float = 200; 16 | 17 | function CalculatePathDistances(path:List) { 18 | var iterator = path.iterator(); 19 | var last = iterator.next(); 20 | last.startDist = 0; 21 | var dist = 0.0; 22 | var iteratorHasNext = iterator.hasNext; 23 | var iteratorNext = iterator.next; 24 | while (iteratorHasNext()) { 25 | var current = iteratorNext(); 26 | var differential = current.subtract(last); 27 | dist += differential.length; 28 | current.startDist = dist; 29 | last.next = current; 30 | last.endDist = current.startDist; 31 | last = current; 32 | } 33 | return dist; 34 | } 35 | 36 | function GetPointAlongPath(distance:Float):Null { 37 | for (vec in _path) { 38 | if (FlxMath.inBounds(distance, vec.startDist, vec.endDist) && vec.next != null) { 39 | var ratio = (distance - vec.startDist) / vec.next.subtract(vec).length; 40 | return ModchartUtil.lerpVector3D(vec, vec.next, ratio); 41 | } 42 | } 43 | return _path.first(); 44 | } 45 | 46 | function LoadPath():List { 47 | var file = ModchartUtil.coolTextFile('modchart/arrowShape.csv'); 48 | var path = new List(); 49 | for (line in file) { 50 | var coords = line.split(';'); 51 | var vec = new TimeVector(Std.parseFloat(coords[0]), Std.parseFloat(coords[1]), Std.parseFloat(coords[2]), Std.parseFloat(coords[3])); 52 | vec.scaleBy(SCALE); 53 | path.add(vec); 54 | } 55 | _pathDistance = CalculatePathDistances(path); 56 | return path; 57 | } 58 | 59 | override public function render(curPos:Vector3, params:ModifierParameters) { 60 | if (_path == null) 61 | _path = LoadPath(); 62 | 63 | final perc = getPercent('schmovinArrowShape', params.player); 64 | 65 | if (perc == 0) 66 | return curPos; 67 | 68 | var path = GetPointAlongPath(params.distance / 1500.0 * _pathDistance); 69 | 70 | return ModchartUtil.lerpVector3D(curPos, 71 | path.add(new Vector3(WIDTH * .5, HEIGHT * .5 + 280, params.lane * getPercent('schmovinArrowShapeOffset', params.player) + curPos.z)), perc); 72 | } 73 | 74 | override public function shouldRun(params:ModifierParameters):Bool 75 | return true; 76 | } 77 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Beat.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.math.FlxMath; 4 | import flixel.tweens.FlxEase; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | 8 | class Beat extends Modifier { 9 | public function new(pf) { 10 | super(pf); 11 | 12 | for (x in ['x', 'y', 'z', '']) 13 | setPercent('beat${x}Speed', 1); 14 | } 15 | 16 | static final fAccelTime:Float = 0.2; 17 | static final fTotalTime:Float = 0.5; 18 | 19 | static final zeroOneFactor = 1 / fAccelTime; 20 | static final oneZeroFactor = 1 / fTotalTime; 21 | 22 | @:dox(hide) 23 | @:noCompletion private inline function beatMath(params:ModifierParameters, offset:Float, mult:Float, speed:Float):Float { 24 | var fBeat = ((params.curBeat * speed) + offset) + fAccelTime; 25 | 26 | if (fBeat <= 0) 27 | return 0; 28 | 29 | final bEvenBeat = Std.int(fBeat) % 2 != 0; 30 | fBeat = (fBeat % 1 + 1) % 1; 31 | 32 | if (fBeat >= fTotalTime) 33 | return 0; 34 | var fAmount:Float; 35 | 36 | if (fBeat < fAccelTime) { 37 | fAmount = Math.pow(fBeat * zeroOneFactor, 2); 38 | } else { 39 | final fcBeat = fBeat * oneZeroFactor; 40 | fAmount = (1 - fcBeat) * (1 + fcBeat); 41 | } 42 | 43 | if (bEvenBeat) 44 | fAmount *= -1; 45 | 46 | return 20 * fAmount * cos(params.distance * 0.01 * mult); 47 | } 48 | 49 | @:dox(hide) 50 | @:noCompletion private inline function computeBeat(curPos:Vector3, params:ModifierParameters, axis:String, realAxis:String) { 51 | final receptorName = Std.string(params.lane); 52 | final player = params.player; 53 | 54 | final amount = getPercent('beat' + axis, player) + getPercent('beat' + axis + receptorName, player); 55 | 56 | if (amount == 0) 57 | return curPos; 58 | 59 | final speed = 1 * getPercent('beat' + axis + 'Speed', player); 60 | final offset = getPercent('beat' + axis + 'Offset', player); 61 | final mult = getPercent('beat' + axis + 'Mult', player); 62 | 63 | var shift = beatMath(params, offset, 1 + mult, speed) * amount; 64 | 65 | switch (realAxis) { 66 | case 'x': 67 | curPos.x += shift; 68 | case 'y': 69 | curPos.y += shift; 70 | case 'z': 71 | curPos.z += shift; 72 | } 73 | 74 | return curPos; 75 | } 76 | 77 | override public function render(curPos:Vector3, params:ModifierParameters) { 78 | computeBeat(curPos, params, '', 'x'); 79 | computeBeat(curPos, params, 'x', 'x'); 80 | computeBeat(curPos, params, 'y', 'y'); 81 | computeBeat(curPos, params, 'z', 'z'); 82 | 83 | return curPos; 84 | } 85 | 86 | override public function shouldRun(params:ModifierParameters):Bool 87 | return true; 88 | } 89 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/false_paradise/EyeShape.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list.false_paradise; 2 | 3 | import flixel.math.FlxMath; 4 | import haxe.ds.Vector; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | import modchart.backend.util.ModchartUtil; 8 | 9 | private class TimeVector extends Vector3D { 10 | public var startDist = 0.0; 11 | public var endDist = 0.0; 12 | public var next:TimeVector; 13 | } 14 | 15 | class EyeShape extends Modifier { 16 | var _path:Vector; 17 | var _pathDistance:Float = 0; 18 | 19 | var SCALE:Float = 600; 20 | 21 | function getDistancesOf(path:Vector) { 22 | var index:Int = 0; 23 | var last = path[index]; 24 | last.startDist = 0; 25 | var dist:Float = 0; 26 | 27 | while (index < path.length) { 28 | final current = path[index]; 29 | final diff = current.subtract(last); 30 | 31 | current.startDist = (dist += diff.length); 32 | last.next = current; 33 | last.endDist = current.startDist; 34 | last = current; 35 | 36 | index++; 37 | } 38 | return dist; 39 | } 40 | 41 | function getPositionAt(distance:Float):Null { 42 | for (i in 0..._path.length) { 43 | final vec = _path[i]; 44 | 45 | if (FlxMath.inBounds(distance, vec.startDist, vec.endDist) && vec.next != null) { 46 | var ratio = (distance - vec.startDist) / vec.next.subtract(vec).length; 47 | return ModchartUtil.lerpVector3D(vec, vec.next, ratio); 48 | } 49 | } 50 | return _path[0]; 51 | } 52 | 53 | function loadPath():Vector { 54 | var pathArray:Array = []; 55 | 56 | for (node in ModchartUtil.coolTextFile('modchart/eyeShape.csv')) { 57 | final coords = node.split(';'); 58 | pathArray.push(new TimeVector(Std.parseFloat(coords[0]) * SCALE, Std.parseFloat(coords[1]) * SCALE, Std.parseFloat(coords[2]) * SCALE, 59 | Std.parseFloat(coords[3]) * SCALE)); 60 | } 61 | 62 | var pathIterable = Vector.fromArrayCopy(pathArray); 63 | pathArray.resize(0); 64 | 65 | _pathDistance = getDistancesOf(pathIterable); 66 | return pathIterable; 67 | } 68 | 69 | override public function render(curPos:Vector3, params:ModifierParameters) { 70 | if (_path == null) 71 | _path = loadPath(); 72 | 73 | final perc = getPercent('eyeShape', params.player); 74 | 75 | if (perc == 0) 76 | return curPos; 77 | 78 | var path = getPositionAt(params.distance / 2000.0 * _pathDistance); 79 | 80 | return ModchartUtil.lerpVector3D(curPos, path.add(new Vector3(WIDTH * .5 - 264 - 272, HEIGHT * .5 + 280 - 260)), perc); 81 | } 82 | 83 | override public function shouldRun(params:ModifierParameters):Bool 84 | return true; 85 | } 86 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Stealth.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import flixel.math.FlxMath; 5 | import modchart.backend.core.ArrowData; 6 | import modchart.backend.core.ModifierParameters; 7 | import modchart.backend.core.VisualParameters; 8 | import modchart.backend.util.ModchartUtil; 9 | 10 | class Stealth extends Modifier { 11 | public function new(pf) { 12 | super(pf); 13 | 14 | setPercent('alpha', 1, -1); 15 | 16 | setPercent('suddenStart', 5, -1); 17 | setPercent('suddenEnd', 3, -1); 18 | setPercent('suddenGlow', 1, -1); 19 | 20 | setPercent('hiddenStart', 5, -1); 21 | setPercent('hiddenEnd', 3, -1); 22 | setPercent('hiddenGlow', 1, -1); 23 | } 24 | 25 | private inline function computeSudden(data:VisualParameters, params:ModifierParameters) { 26 | final player = params.player; 27 | 28 | final sudden = getPercent('sudden', player); 29 | 30 | if (sudden == 0) 31 | return; 32 | 33 | final start = getPercent('suddenStart', player) * 100; 34 | final end = getPercent('suddenEnd', player) * 100; 35 | final glow = getPercent('suddenGlow', player); 36 | 37 | final alpha = FlxMath.remapToRange(FlxMath.bound(params.distance, end, start), end, start, 1, 0); 38 | 39 | if (glow != 0) 40 | data.glow += Math.max(0, (1 - alpha) * sudden * 2) * glow; 41 | data.alpha *= alpha * sudden; 42 | } 43 | 44 | private inline function computeHidden(data:VisualParameters, params:ModifierParameters) { 45 | final player = params.player; 46 | 47 | final hidden = getPercent('hidden', player); 48 | 49 | if (hidden == 0) 50 | return; 51 | 52 | final start = getPercent('hiddenStart', player) * 100; 53 | final end = getPercent('hiddenEnd', player) * 100; 54 | final glow = getPercent('hiddenGlow', player); 55 | 56 | final alpha = FlxMath.remapToRange(FlxMath.bound(params.distance, end, start), end, start, 0, 1); 57 | 58 | if (glow != 0) 59 | data.glow += Math.max(0, (1 - alpha) * hidden * 2) * glow; 60 | data.alpha *= alpha * hidden; 61 | } 62 | 63 | override public function visuals(data:VisualParameters, params:ModifierParameters) { 64 | final player = params.player; 65 | final lane = params.lane; 66 | 67 | final vMod = params.isTapArrow ? 'stealth' : 'dark'; 68 | final visibility = getPercent(vMod, player) + getPercent(vMod + Std.string(lane), player); 69 | data.alpha = ((getPercent('alpha', player) + getPercent('alpha' + Std.string(lane), player)) * (1 - ((Math.max(0.5, visibility) - 0.5) * 2))); 70 | data.glow += visibility * 2; 71 | 72 | // sudden & hidden 73 | if (params.isTapArrow) // non receptor 74 | { 75 | computeSudden(data, params); 76 | computeHidden(data, params); 77 | } 78 | 79 | return data; 80 | } 81 | 82 | override public function shouldRun(params:ModifierParameters):Bool 83 | return true; 84 | } 85 | -------------------------------------------------------------------------------- /modchart/engine/events/EventManager.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.events; 2 | 3 | import haxe.ds.StringMap; 4 | import haxe.ds.Vector; 5 | import modchart.backend.util.ModchartUtil; 6 | import modchart.engine.PlayField; 7 | import modchart.events.types.*; 8 | 9 | #if !openfl_debug 10 | @:fileXml('tags="haxe,release"') 11 | @:noDebug 12 | #end 13 | @:allow(modchart.engine.events.Event) 14 | class EventManager { 15 | private var table:StringMap>> = new StringMap(); 16 | private var eventList:Vector = new Vector(256); 17 | private var eventCount:Int = 0; 18 | 19 | private var pf:PlayField; 20 | 21 | public function new(pf:PlayField) { 22 | this.pf = pf; 23 | } 24 | 25 | public function add(event:Event) { 26 | if (event.name != null) { 27 | final lwr = event.name.toLowerCase(); 28 | var player = event.player; 29 | 30 | var entry = table.get(lwr); 31 | if (entry == null) { 32 | entry = []; 33 | table.set(lwr, entry); 34 | } 35 | if (entry[player] == null) { 36 | entry[player] = new Vector(256); 37 | event.prev = null; 38 | } else { 39 | var len = getVectorLength(entry[player]); 40 | if (len > 0) 41 | event.prev = entry[player][len - 1]; 42 | } 43 | insertSorted(entry[player], event); 44 | } 45 | 46 | insertSorted(eventList, event, true); 47 | eventCount++; 48 | } 49 | 50 | public function update(curBeat:Float) { 51 | for (i in 0...eventCount) { 52 | var ev = eventList[i]; 53 | if (ev.beat >= curBeat) { 54 | ev.active = false; 55 | for (j in i...eventCount) 56 | eventList[j].active = false; 57 | break; 58 | } 59 | ev.active = true; 60 | ev.update(curBeat); 61 | } 62 | } 63 | 64 | public function getLastEventBefore(event:Event):Event { 65 | return event.prev; 66 | } 67 | 68 | private function insertSorted(vec:Vector, event:Event, resize:Bool = false) { 69 | var len = getVectorLength(vec); 70 | if (len >= vec.length) { 71 | if (!resize) 72 | return; 73 | var newVec = new Vector(vec.length + 64); 74 | 75 | Vector.blit(vec, 0, newVec, 0, vec.length); 76 | vec = newVec; 77 | // only applies to main list 78 | eventList = vec; 79 | } 80 | 81 | // insert already sorted 82 | var pos = len; 83 | while (pos > 0 && cmpBeat(event, vec[pos - 1]) < 0) { 84 | vec[pos] = vec[pos - 1]; 85 | pos--; 86 | } 87 | vec[pos] = event; 88 | } 89 | 90 | private inline function cmpBeat(a:Event, b:Event):Int { 91 | return a.beat < b.beat ? -1 : (a.beat > b.beat ? 1 : 0); 92 | } 93 | 94 | private inline function getVectorLength(vec:Vector):Int { 95 | var len = vec.length; 96 | for (i in 0...len) { 97 | if (vec[i] == null) { 98 | len = i; 99 | break; 100 | } 101 | } 102 | return len; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/Modifier.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers; 2 | 3 | import flixel.FlxG; 4 | import flixel.math.FlxMath; 5 | import modchart.Manager; 6 | import modchart.backend.core.ArrowData; 7 | import modchart.backend.core.ModifierParameters; 8 | import modchart.backend.core.VisualParameters; 9 | import modchart.engine.PlayField; 10 | 11 | using StringTools; 12 | 13 | #if !openfl_debug 14 | @:fileXml('tags="haxe,release"') 15 | @:noDebug 16 | #end 17 | class Modifier { 18 | private var pf:PlayField; 19 | 20 | public function new(pf:PlayField) { 21 | this.pf = pf; 22 | } 23 | 24 | public function render(curPos:Vector3, params:ModifierParameters) { 25 | return curPos; 26 | } 27 | 28 | public function visuals(data:VisualParameters, params:ModifierParameters):VisualParameters { 29 | return data; 30 | } 31 | 32 | public function shouldRun(params:ModifierParameters):Bool 33 | return false; 34 | 35 | public inline function findID(name:String):Int { 36 | @:privateAccess return pf.modifiers.percents.__hashKey(name.toLowerCase()); 37 | } 38 | 39 | public inline function getUnsafe(id:Int, player:Int) 40 | return @:privateAccess pf.modifiers.__getUnsafe(id, player); 41 | 42 | public inline function setUnsafe(id:Int, value:Float, player:Int = -1) 43 | return @:privateAccess pf.modifiers.__setUnsafe(id, value, player); 44 | 45 | public inline function setPercent(name:String, value:Float, player:Int = -1) { 46 | pf.setPercent(name, value, player); 47 | } 48 | 49 | public inline function getPercent(name:String, player:Int):Float { 50 | return pf.getPercent(name, player); 51 | } 52 | 53 | private inline function getKeyCount(player:Int = 0):Int { 54 | return Adapter.instance.getKeyCount(); 55 | } 56 | 57 | private inline function getPlayerCount():Int { 58 | return Adapter.instance.getPlayerCount(); 59 | } 60 | 61 | // Helpers Functions 62 | private inline function getScrollSpeed():Float 63 | return Adapter.instance.getCurrentScrollSpeed(); 64 | 65 | public inline function getReceptorY(lane:Int, player:Int) 66 | return Adapter.instance.getDefaultReceptorY(lane, player); 67 | 68 | public inline function getReceptorX(lane:Int, player:Int) 69 | return Adapter.instance.getDefaultReceptorX(lane, player); 70 | 71 | private var WIDTH:Float = FlxG.width; 72 | private var HEIGHT:Float = FlxG.height; 73 | private var ARROW_SIZE(get, never):Float; 74 | private var ARROW_SIZEDIV2(get, never):Float; 75 | 76 | private inline function get_ARROW_SIZE():Float 77 | return Manager.ARROW_SIZE; 78 | 79 | private inline function get_ARROW_SIZEDIV2():Float 80 | return Manager.ARROW_SIZEDIV2; 81 | 82 | private inline function sin(rad:Float):Float 83 | return ModchartUtil.sin(rad); 84 | 85 | private inline function cos(rad:Float):Float 86 | return ModchartUtil.cos(rad); 87 | 88 | private inline function tan(rad:Float):Float 89 | return ModchartUtil.tan(rad); 90 | 91 | public function toString():String { 92 | var classn:String = Type.getClassName(Type.getClass(this)); 93 | classn = classn.substring(classn.lastIndexOf('.') + 1); 94 | return 'Modifier[$classn]'; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /modchart/backend/standalone/IAdapter.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone; 2 | 3 | import flixel.FlxCamera; 4 | import flixel.FlxSprite; 5 | import haxe.ds.Vector; 6 | 7 | interface IAdapter { 8 | public function onModchartingInitialization():Void; 9 | 10 | // Song-related stuff 11 | public function getSongPosition():Float; // Current song position 12 | // public function getCrochet():Float // Current beat crochet 13 | public function getCurrentBeat():Float; // Current beat 14 | public function getCurrentCrochet():Float; // Current beat 15 | public function getCurrentScrollSpeed():Float; // Current arrow scroll speed 16 | public function getBeatFromStep(step:Float):Float; 17 | 18 | // Arrow-related stuff 19 | public function getDefaultReceptorX(lane:Int, player:Int):Float; // Get default strum x position 20 | public function getDefaultReceptorY(lane:Int, player:Int):Float; // Get default strum y position 21 | public function getTimeFromArrow(arrow:FlxSprite):Float; // Get strum time for arrow 22 | public function isTapNote(sprite:FlxSprite):Bool; // If the sprite is an arrow, return true, if it is an lane/strum, return false 23 | public function isHoldEnd(sprite:FlxSprite):Bool; // If its the hold end 24 | public function arrowHit(sprite:FlxSprite):Bool; // If the arrow was hitted 25 | public function getHoldParentTime(sprite:FlxSprite):Float; 26 | 27 | /** 28 | * Get the individual hold fragment length. 29 | * 30 | * On most FNF engines, holds divided into fragments/tiles, 31 | * each of them has a length of a step, so in this case, this 32 | * function should return the length of a step. 33 | * 34 | * Also on other FNF engines, the holds uses one single fragment 35 | * (two actually, ond for the body and other for the end), 36 | * so in that case, this should return the full hold length in ms. 37 | * @param sprite : The hold arrow 38 | * @return Float 39 | */ 40 | public function getHoldLength(sprite:FlxSprite):Float; 41 | 42 | public function getLaneFromArrow(sprite:FlxSprite):Int; // Get lane/note data from arrow 43 | public function getPlayerFromArrow(sprite:FlxSprite):Int; // Get player from arrow 44 | 45 | public function getKeyCount(?player:Int):Int; // Get total key count from specific player (4 for almost every engine) 46 | public function getPlayerCount():Int; // Get total player count (2 for almost every engine) 47 | 48 | // Get cameras to render the arrows (camHUD for almost every engine) 49 | public function getArrowCamera():Array; 50 | 51 | // Options section 52 | public function getHoldSubdivisions(item:FlxSprite):Int; // Hold resolution 53 | public function getDownscroll():Bool; // Get if it is downscroll 54 | 55 | /** 56 | * Get the every arrow/lane indexed by player. 57 | * Example: 58 | * [ 59 | * [ // Player 0 60 | * [strum1, strum2...], 61 | * [arrow1, arrow2...], 62 | * [hold1, hold2....], 63 | * [splash1, splash2....] 64 | * ], 65 | * [ // Player 2 66 | * [strum1, strum2...], 67 | * [arrow1, arrow2...], 68 | * [hold1, hold2....], 69 | * [splash1, splash2....] 70 | * ] 71 | * ] 72 | * @return Array>> 73 | */ 74 | public function getArrowItems():Array>>; 75 | } 76 | -------------------------------------------------------------------------------- /DOC.md: -------------------------------------------------------------------------------- 1 | ## Modcharting Functions 2 | ```haxe 3 | /* `instance` = the FunkinModchart Manager instance. */ 4 | 5 | /* Modifiers Section */ 6 | /* 7 | * Search a modifier by `mod` and adds it. 8 | * 9 | * mod:String The modifier name string 10 | * field:Int The playfield number (-1 by default) 11 | */ 12 | instance.addModifier(mod, field); 13 | /* 14 | * Adds or rewrites the percent of `mod`and sets it to `value` 15 | * 16 | * mod:String The modifier name string 17 | * value:Float The value to be assigned to the modifier. 18 | * field:Int The playfield number (-1 by default) 19 | */ 20 | instance.setPercent(mod, value, field); 21 | /* 22 | * Returns the percent of `mod` 23 | * 24 | * mod:String The modifier name string 25 | * field:Int The playfield number (-1 by default) 26 | * 27 | * returns: Float 28 | */ 29 | instance.getPercent(mod, field); 30 | /* 31 | * Registers a new modifier in the name of `modN` 32 | * 33 | * modN:String The modifier name string 34 | * mod:Modifier The custom modifier class instance. 35 | */ 36 | instance.registerModifier(modN, mod); 37 | 38 | /* Events Section */ 39 | /* 40 | * Adds or rewrites the percentage of `mod` and sets it to `value` 41 | when the specified beat is reached. 42 | * 43 | * mod:String The modifier name string 44 | * beat:Float The beat number where the event will be executed. 45 | * value:Float The value to be assigned to the modifier. 46 | * player:Int The player/strumline number (-1 by default) 47 | * field:Int The playfield number (-1 by default) 48 | */ 49 | instance.set(mod, beat, value, player, field); 50 | /* 51 | * Tweens the percentage of `mod` from its current value to `value` 52 | over the specified duration, using the provided easing function. 53 | * 54 | * mod:String The modifier name string 55 | * beat:Float The beat number where the event will be executed. 56 | * length:Float The tween duration in beats. 57 | * ease:F->F The ease function (Float to Float) 58 | * value:Float The value to be assigned to the modifier. 59 | * player:Int The player/strumline number (-1 by default) 60 | * field:Int The playfield number (-1 by default) 61 | */ 62 | instance.ease(mod, beat, length, value, ease, player, field); 63 | /* 64 | * Execute the callback function when the specified beat is reached. 65 | 66 | * beat:Float The beat number where the event will be executed. 67 | * func:V->V The modifier name string 68 | * field:Int The playfield number (-1 by default) 69 | */ 70 | instance.callback(beat, func, field); 71 | /* 72 | * Repeats the execution of the callback function for the specified duration, 73 | starting at the given beat. 74 | * 75 | * beat:Float The beat number where the event will be executed. 76 | * length:Float The repeater duration in beats. 77 | * func:V->V The modifier name string 78 | * field:Int The playfield number (-1 by default) 79 | */ 80 | instance.repeater(beat, length, func, field); 81 | /* 82 | * Adds a custom event. 83 | * 84 | * event:Event The custom event to be added. 85 | * field:Int The playfield number (-1 by default) 86 | */ 87 | instance.addEvent(event, field); 88 | 89 | /* Playfield Section */ 90 | /* 91 | * Adds a new playfield. 92 | * 93 | * WARNING: If you add a playfield after adding modifiers, you will have to add them again to the new playfield. 94 | */ 95 | instance.addPlayfield(); 96 | ``` -------------------------------------------------------------------------------- /modchart/backend/math/ModchartPerspective.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.math; 2 | 3 | import flixel.FlxG; 4 | 5 | /** 6 | * Represents a perspective projection for modcharts. 7 | * 8 | * This class provides a basic perspective transformation based on OpenGL principles. 9 | * It allows transforming 3D world coordinates into 2D screen space, taking into account 10 | * field of view (FOV), aspect ratio, and depth scaling. 11 | * 12 | * Based on OpenGL tutorial: 13 | * @see https://ogldev.org/www/tutorial12/tutorial12.html 14 | */ 15 | #if !openfl_debug 16 | @:fileXml('tags="haxe,release"') 17 | @:noDebug 18 | #end 19 | final class ModchartPerspective { 20 | /** 21 | * Distance to the near clipping plane. 22 | * Objects closer than this distance will not be rendered. 23 | */ 24 | public var near(default, set):Float = 0; 25 | 26 | /** 27 | * Distance to the far clipping plane. 28 | * Objects farther than this distance will not be rendered. 29 | */ 30 | public var far(default, set):Float = 1; 31 | 32 | /** 33 | * Field of View (FOV) in radians. 34 | * Defines the extent of the observable world projected onto the screen. 35 | * 36 | * **NOTE:** This value defaults to 90 degrees (PI / 2). 37 | */ 38 | public var fov(default, set):Float; 39 | 40 | /** 41 | * Distance range between the near and far clipping planes. 42 | * Calculated as `near - far`. 43 | */ 44 | public var range(get, never):Float; 45 | 46 | /** 47 | * Internal projection components. 48 | */ 49 | private var __tanHalfFov:Float = 0; 50 | 51 | private var __depthRange:Float = 1; 52 | private var __depthScale:Float = 1; 53 | private var __depthOffset:Float = 0; 54 | 55 | public function new() { 56 | fov = Math.PI / 2; 57 | updateProperties(); 58 | } 59 | 60 | private function set_near(value:Float):Float { 61 | updateProperties(); 62 | return near = value; 63 | } 64 | 65 | private function set_far(value:Float):Float { 66 | updateProperties(); 67 | return far = value; 68 | } 69 | 70 | private function set_fov(value:Float):Float { 71 | updateProperties(); 72 | return fov = value; 73 | } 74 | 75 | private function get_range():Float { 76 | return near - far; 77 | } 78 | 79 | /** 80 | * Updates internal projection properties based on current FOV and depth range. 81 | */ 82 | public function updateProperties():Void { 83 | __tanHalfFov = Math.tan(fov * 0.5); 84 | __depthRange = 1 / range; 85 | __depthScale = (near + far) * __depthRange; 86 | __depthOffset = 2 * near * (far * __depthRange); 87 | } 88 | 89 | /** 90 | * Transforms a 3D vector into 2D screen space using perspective projection. 91 | * 92 | * @param vector The 3D vector to project. 93 | * @param origin Optional origin point for transformation (defaults to screen center). 94 | * @return The projected 2D vector. 95 | */ 96 | public inline function transformVector(vector:Vector3, ?origin:Null):Vector3 { 97 | if (origin == null) { 98 | origin = new Vector3(FlxG.width * 0.5, FlxG.height * 0.5); 99 | } 100 | 101 | var translation = vector - origin; 102 | 103 | final projectedZ = __depthScale * Math.min(translation.z - 1, 0) + __depthOffset; 104 | final projectedFov = (__tanHalfFov / projectedZ); 105 | 106 | translation.setTo(translation.x * projectedFov, translation.y * projectedFov, projectedZ); 107 | return translation += origin; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Reverse.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import flixel.FlxG; 4 | import flixel.math.FlxMath; 5 | import modchart.Manager; 6 | import modchart.backend.core.ArrowData; 7 | import modchart.backend.core.ModifierParameters; 8 | import modchart.backend.util.ModchartUtil; 9 | 10 | // Default modifier 11 | // Handles scroll speed, scroll angle and reverse modifiers 12 | class Reverse extends Modifier { 13 | public function new(pf) { 14 | super(pf); 15 | 16 | setPercent('xmod', 1, -1); 17 | } 18 | 19 | public function getReverseValue(dir:Int, player:Int) { 20 | var kNum = getKeyCount(); 21 | var val:Float = 0; 22 | if (dir >= Math.floor(kNum * 0.5)) 23 | val = val + getPercent("split", player); 24 | 25 | if ((dir % 2) == 1) 26 | val = val + getPercent("alternate", player); 27 | 28 | var first = kNum * 0.25; 29 | var last = kNum - 1 - first; 30 | 31 | if (dir >= first && dir <= last) 32 | val = val + getPercent("cross", player); 33 | 34 | val = val + getPercent('reverse', player) + getPercent("reverse" + Std.string(dir), player); 35 | 36 | if (getPercent("unboundedReverse", player) == 0) { 37 | val %= 2; 38 | if (val > 1) 39 | val = 2 - val; 40 | } 41 | 42 | // downscroll 43 | if (Adapter.instance.getDownscroll()) 44 | val = 1 - val; 45 | return val; 46 | } 47 | 48 | override public function render(curPos:Vector3, params:ModifierParameters) { 49 | var player = params.player; 50 | var initialY = Adapter.instance.getDefaultReceptorY(params.lane, player) + ARROW_SIZEDIV2; 51 | var reversePerc = getReverseValue(params.lane, player); 52 | var shift = FlxMath.lerp(initialY, HEIGHT - initialY, reversePerc); 53 | 54 | var centerPercent = getPercent('centered', params.player); 55 | shift = FlxMath.lerp(shift, (HEIGHT * 0.5) - ARROW_SIZEDIV2, centerPercent); 56 | 57 | var distance = params.distance; 58 | 59 | distance *= Adapter.instance.getCurrentScrollSpeed(); 60 | 61 | var scroll = new Vector3(0, FlxMath.lerp(distance, -distance, reversePerc)); 62 | scroll = applyScrollMods(scroll, params); 63 | 64 | curPos.x = curPos.x + scroll.x; 65 | curPos.y = shift + scroll.y; 66 | curPos.z = curPos.z + scroll.z; 67 | 68 | return curPos; 69 | } 70 | 71 | function applyScrollMods(scroll:Vector3, params:ModifierParameters) { 72 | var player = params.player; 73 | var angleX = 0.; 74 | var angleY = 0.; 75 | var angleZ = 0.; 76 | 77 | // Speed 78 | scroll.y = scroll.y * (getPercent('xmod', player) + getPercent('xmod' + Std.string(params.lane), player)); 79 | 80 | // Main 81 | angleX = angleX + getPercent('scrollAngleX', player); 82 | angleY = angleY + getPercent('scrollAngleY', player); 83 | angleZ = angleZ + getPercent('scrollAngleZ', player); 84 | 85 | // Curved 86 | final shift:Float = params.distance * 0.25 * (1 + getPercent('curvedScrollPeriod', player)); 87 | 88 | angleX = angleX + shift * getPercent('curvedScrollX', player); 89 | angleY = angleY + shift * getPercent('curvedScrollY', player); 90 | angleZ = angleZ + shift * getPercent('curvedScrollZ', player); 91 | 92 | // angleY doesnt do anything if angleX and angleZ are disabled 93 | if (angleX == 0 && angleZ == 0) 94 | return scroll; 95 | 96 | scroll = ModchartUtil.rotate3DVector(scroll, angleX, angleY, angleZ); 97 | 98 | return scroll; 99 | } 100 | 101 | override public function shouldRun(params:ModifierParameters):Bool 102 | return true; 103 | } 104 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/DynamicModifier.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers; 2 | 3 | /** 4 | * `DynamicModifier` is a subclass of `Modifier` that applies dynamic transformations 5 | * to the position and visuals of an arrow during the modchart rendering process. 6 | * 7 | * This type of modifier can be used for scripting, allowing you to define 8 | * custom functions to modify both the position and visuals of arrows 9 | * without the need to modify the source code. 10 | */ 11 | #if !openfl_debug 12 | @:fileXml('tags="haxe,release"') 13 | @:noDebug 14 | #end 15 | class DynamicModifier extends Modifier { 16 | /** 17 | * A function that applies a transformation to the arrow's position. 18 | * 19 | * @param position The current position of the arrow. 20 | * @param params The parameters used for rendering, such as song position, beat, etc. 21 | * @return The transformed position of the arrow. 22 | */ 23 | public var renderFunc:(Vector3, ModifierParameters) -> Vector3; 24 | 25 | /** 26 | * A function that applies transformations to the arrow's visuals. 27 | * 28 | * @param data The current visuals of the arrow. 29 | * @param params The parameters used for rendering, such as song position, beat, etc. 30 | * @return The transformed visuals of the arrow. 31 | */ 32 | public var visualsFunc:(VisualParameters, ModifierParameters) -> VisualParameters; 33 | 34 | /** 35 | * Flag that enables or disables null safety. When enabled, the parameters are copied 36 | * to avoid modifying the original data. This ensures that if the functions return `null`, 37 | * the original values will not be altered, preventing unintended changes. 38 | */ 39 | public var nullSafety:Bool = true; 40 | 41 | private var __skipRender:Bool = false; 42 | private var __skipVisuals:Bool = false; 43 | 44 | /** 45 | * Applies the position transformation defined by `renderFunc`. 46 | * If `renderFunc` is `null`, the function simply returns the current position. 47 | * 48 | * @return The transformed position, or the original if no transformation is applied. 49 | */ 50 | override public function render(position:Vector3, params:ModifierParameters) { 51 | if (__skipRender || renderFunc == null) 52 | return position; 53 | 54 | final safePos = nullSafety ? position.clone() : position; 55 | final safeParams = nullSafety ? Reflect.copy(params) : params; 56 | 57 | final translation:Null = renderFunc(safePos, safeParams); 58 | 59 | if (nullSafety && translation == null) { 60 | trace('[FunkinModchart::DynamicModifier] Failed to run "render" function!'); 61 | __skipRender = true; 62 | } 63 | 64 | return translation != null ? translation : position; 65 | } 66 | 67 | /** 68 | * Applies the visual transformation defined by `visualsFunc`. 69 | * If `visualsFunc` is `null`, the function simply returns the original visuals. 70 | * 71 | * @return The transformed visuals, or the original if no transformation is applied. 72 | */ 73 | override public function visuals(data:VisualParameters, params:ModifierParameters) { 74 | if (__skipVisuals || visualsFunc == null) 75 | return data; 76 | 77 | final safeData = nullSafety ? Reflect.copy(data) : data; 78 | final safeParams = nullSafety ? Reflect.copy(params) : params; 79 | 80 | final modifiedVisuals:Null = visualsFunc(safeData, safeParams); 81 | 82 | if (nullSafety && modifiedVisuals == null) { 83 | trace('[FunkinModchart::DynamicModifier] Failed to run "visuals" function!'); 84 | __skipVisuals = true; 85 | } 86 | 87 | return modifiedVisuals != null ? modifiedVisuals : data; 88 | } 89 | 90 | override public function shouldRun(params:ModifierParameters):Bool { 91 | return true; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /modchart/backend/standalone/adapters/pslice/Pslice.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone.adapters.pslice; 2 | 3 | import flixel.FlxSprite; 4 | import modchart.backend.standalone.adapters.psych.Psych; 5 | import objects.NoteSplash; 6 | import objects.SustainSplash; 7 | import states.PlayState; 8 | 9 | class Pslice extends Psych { 10 | override public function onModchartingInitialization() { 11 | super.onModchartingInitialization(); 12 | 13 | PlayState.instance.grpNoteSplashes.visible = false; 14 | PlayState.instance.grpHoldSplashes.visible = false; 15 | } 16 | 17 | override public function getLaneFromArrow(arrow:FlxSprite) { 18 | if (arrow is NoteSplash) { 19 | var splash:NoteSplash = cast arrow; 20 | @:privateAccess 21 | return splash.babyArrow.noteData; 22 | } else if (arrow is SustainSplash) { 23 | var splash:SustainSplash = cast arrow; 24 | @:privateAccess 25 | return splash.strumNote.noteData; 26 | } 27 | 28 | return super.getLaneFromArrow(arrow); 29 | } 30 | 31 | override public function getPlayerFromArrow(arrow:FlxSprite) { 32 | if (arrow is NoteSplash) { 33 | var splash:NoteSplash = cast arrow; 34 | @:privateAccess 35 | return splash.babyArrow.player; 36 | } else if (arrow is SustainSplash) { 37 | var splash:SustainSplash = cast arrow; 38 | @:privateAccess 39 | return splash.strumNote.player; 40 | } 41 | 42 | return super.getPlayerFromArrow(arrow); 43 | } 44 | 45 | // this code looks so bad 46 | override public function getArrowItems() { 47 | var pspr:Array>> = [[[], [], [], []], [[], [], [], []]]; 48 | 49 | var counts:Array> = [[0, 0, 0, 0], [0, 0, 0, 0]]; 50 | var indices:Array> = [[0, 0, 0, 0], [0, 0, 0, 0]]; 51 | 52 | @:privateAccess 53 | PlayState.instance.grpNoteSplashes.forEachAlive(splash -> { 54 | if (splash.babyArrow != null && splash.active) { 55 | counts[splash.babyArrow.player][3]++; 56 | } 57 | }); 58 | 59 | @:privateAccess 60 | PlayState.instance.grpHoldSplashes.forEachAlive(splash -> { 61 | if (splash.strumNote != null && splash.active) { 62 | counts[splash.strumNote.player][3]++; 63 | } 64 | }); 65 | 66 | @:privateAccess 67 | PlayState.instance.strumLineNotes.forEachAlive(strumNote -> { 68 | counts[strumNote.player][0]++; 69 | }); 70 | 71 | @:privateAccess 72 | PlayState.instance.notes.forEachAlive(strumNote -> { 73 | final player = Adapter.instance.getPlayerFromArrow(strumNote); 74 | counts[player][strumNote.isSustainNote ? 2 : 1]++; 75 | }); 76 | 77 | for (player in 0...2) { 78 | for (i in 0...4) { 79 | pspr[player][i].resize(counts[player][i]); 80 | } 81 | } 82 | 83 | @:privateAccess 84 | PlayState.instance.grpNoteSplashes.forEachAlive(splash -> { 85 | if (splash.babyArrow != null && splash.active) { 86 | var player = splash.babyArrow.player; 87 | pspr[player][3][indices[player][3]++] = splash; 88 | } 89 | }); 90 | 91 | @:privateAccess 92 | PlayState.instance.grpHoldSplashes.forEachAlive(splash -> { 93 | if (splash.strumNote != null && splash.active) { 94 | var player = splash.strumNote.player; 95 | pspr[player][3][indices[player][3]++] = splash; 96 | } 97 | }); 98 | 99 | @:privateAccess 100 | PlayState.instance.strumLineNotes.forEachAlive(strumNote -> { 101 | var player = strumNote.player; 102 | pspr[player][0][indices[player][0]++] = strumNote; 103 | }); 104 | 105 | @:privateAccess 106 | PlayState.instance.notes.forEachAlive(strumNote -> { 107 | final player = Adapter.instance.getPlayerFromArrow(strumNote); 108 | var index = strumNote.isSustainNote ? 2 : 1; 109 | pspr[player][index][indices[player][index]++] = strumNote; 110 | }); 111 | 112 | return pspr; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /modchart/backend/math/Vector3.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.math; 2 | 3 | import openfl.geom.Vector3D; 4 | 5 | /** 6 | * Extended version of `Vector3D` with additional operators for convenience. 7 | * 8 | * This abstract class provides operator overloading for arithmetic operations, 9 | * allowing for more intuitive vector math while maintaining compatibility 10 | * with OpenFL's `Vector3D`. 11 | */ 12 | @:keep 13 | @:forward 14 | abstract Vector3(Vector3D) from Vector3D to Vector3D { 15 | /** 16 | * Creates a new `Vector3` instance. 17 | * @param x The X component of the vector. 18 | * @param y The Y component of the vector. 19 | * @param z The Z component of the vector. 20 | * @param w The W component of the vector (default: 0). 21 | */ 22 | @:keep 23 | public function new(x:Float = 0., y:Float = 0., z:Float = 0., w:Float = 0.) { 24 | this = new Vector3D(x, y, z, w); 25 | } 26 | 27 | /** 28 | * Returns a new vector resulting from the addition of two vectors. 29 | */ 30 | @:op(A + B) 31 | inline public function __plusOp(addition:Vector3):Vector3 { 32 | return new Vector3(this.x + addition.x, this.y + addition.y, this.z + addition.z); 33 | } 34 | 35 | /** 36 | * Returns a new vector resulting from the subtraction of two vectors. 37 | */ 38 | @:op(A - B) 39 | inline public function __minusOp(subtraction:Vector3):Vector3 { 40 | return new Vector3(this.x - subtraction.x, this.y - subtraction.y, this.z - subtraction.z); 41 | } 42 | 43 | /** 44 | * Returns a new vector resulting from the component-wise multiplication of two vectors. 45 | */ 46 | @:op(A * B) 47 | inline public function __multOp(mult:Vector3):Vector3 { 48 | return new Vector3(this.x * mult.x, this.y * mult.y, this.z * mult.z); 49 | } 50 | 51 | /** 52 | * Returns a new vector resulting from the component-wise division of two vectors. 53 | */ 54 | @:op(A / B) 55 | inline public function __divOp(div:Vector3):Vector3 { 56 | return new Vector3(this.x / div.x, this.y / div.y, this.z / div.z); 57 | } 58 | 59 | /** 60 | * Adds another vector to this vector component-wise. 61 | * @return This modified vector. 62 | */ 63 | @:op(A += B) 64 | inline public function __add(addition:Vector3):Vector3 { 65 | this.x += addition.x; 66 | this.y += addition.y; 67 | this.z += addition.z; 68 | return this; 69 | } 70 | 71 | /** 72 | * Subtracts another vector from this vector component-wise. 73 | * @return This modified vector. 74 | */ 75 | @:op(A -= B) 76 | inline public function __subtract(subtraction:Vector3):Vector3 { 77 | this.x -= subtraction.x; 78 | this.y -= subtraction.y; 79 | this.z -= subtraction.z; 80 | return this; 81 | } 82 | 83 | /** 84 | * Multiplies this vector by another vector component-wise. 85 | * @return This modified vector. 86 | */ 87 | @:op(A *= B) 88 | inline public function __multiply(mult:Vector3):Vector3 { 89 | this.x *= mult.x; 90 | this.y *= mult.y; 91 | this.z *= mult.z; 92 | return this; 93 | } 94 | 95 | /** 96 | * Divides this vector by another vector component-wise. 97 | * @return This modified vector. 98 | */ 99 | @:op(A /= B) 100 | inline public function __divide(div:Vector3):Vector3 { 101 | this.x /= div.x; 102 | this.y /= div.y; 103 | this.z /= div.z; 104 | return this; 105 | } 106 | 107 | /** 108 | * Linearly interpolates between this vector and another vector. 109 | * @param target The target vector to interpolate towards. 110 | * @param alpha The interpolation factor (0 = this vector, 1 = target vector). 111 | * @return A new interpolated vector. 112 | */ 113 | public function interpolate(target:Vector3, alpha:Float, ?vector:Vector3):Vector3 { 114 | if (vector == null) 115 | vector = new Vector3(); 116 | 117 | // @formatter:off 118 | vector.setTo( 119 | this.x + (target.x - this.x) * alpha, 120 | this.y + (target.y - this.y) * alpha, 121 | this.z + (target.z - this.z) * alpha 122 | ); 123 | // @formatter:on 124 | return vector; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/list/Bumpy.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers.list; 2 | 3 | import modchart.backend.core.ArrowData; 4 | import modchart.backend.core.ModifierParameters; 5 | 6 | class Bumpy extends Modifier { 7 | public function new(pf) { 8 | super(pf); 9 | 10 | var stuff = ['', 'Angle']; 11 | for (i in 0...stuff.length) { 12 | setPercent('bumpy' + stuff[i] + 'Mult', 1, -1); 13 | setPercent('bumpy' + stuff[i] + 'XMult', 1, -1); 14 | setPercent('bumpy' + stuff[i] + 'YMult', 1, -1); 15 | setPercent('bumpy' + stuff[i] + 'ZMult', 1, -1); 16 | } 17 | } 18 | 19 | static final M_24 = 1 / 24; 20 | 21 | function applyBumpy(curPos:Vector3, params:ModifierParameters, axis:String, realAxis:String) { 22 | final receptorName = Std.string(params.lane); 23 | final player = params.player; 24 | var distance = params.distance; 25 | 26 | var offset = getPercent('bumpy' + axis + 'Offset', player) + getPercent('bumpy' + axis + receptorName + 'Offset', player); 27 | var period = getPercent('bumpy' + axis + 'Period', player) + getPercent('bumpy' + axis + receptorName + 'Period', player); 28 | var mult = getPercent('bumpy' + axis + 'Mult', player) + getPercent('bumpy' + axis + receptorName + 'Mult', player); 29 | 30 | var shift = 0.; 31 | 32 | var angle = 40 * sin(distance + (100 * offset)) / ((period * 24) + 24); 33 | 34 | shift += (getPercent('bumpy' + axis, player) + getPercent('bumpy' + axis + receptorName, player)) * angle; 35 | 36 | switch (realAxis) { 37 | case 'x': 38 | curPos.x += shift; 39 | case 'y': 40 | curPos.y += shift; 41 | case 'z': 42 | curPos.z += shift; 43 | } 44 | } 45 | 46 | public function applyAngle(vis:VisualParameters, params:ModifierParameters, axis:String, realAxis:String) { 47 | final receptorName = Std.string(params.lane); 48 | final player = params.player; 49 | var distance = params.distance; 50 | 51 | var offset = getPercent('bumpyAngle' + axis + 'Offset', player) + getPercent('bumpyAngle' + axis + receptorName + 'Offset', player); 52 | var period = getPercent('bumpyAngle' + axis + 'Period', player) + getPercent('bumpyAngle' + axis + receptorName + 'Period', player); 53 | var mult = getPercent('bumpyAngle' + axis + 'Mult', player) + getPercent('bumpyAngle' + axis + receptorName + 'Mult', player); 54 | 55 | var shift = 0.; 56 | 57 | var scrollSpeed = getScrollSpeed(); 58 | 59 | var bumpyMath = 40 * sin(((distance * 0.01) + (100.0 * offset) / ((period * (mult * 24.0)) + 60 | 24.0)) / ((scrollSpeed * mult) / 2)) * (getKeyCount() / 2.0); 61 | 62 | shift += (getPercent('bumpyAngle' + axis, player) + getPercent('bumpyAngle' + axis + receptorName, player)) * bumpyMath; 63 | 64 | switch (realAxis) { 65 | case 'x': 66 | vis.angleX += shift; 67 | case 'y': 68 | vis.angleY += shift; 69 | case 'z': 70 | vis.angleZ += shift; 71 | } 72 | } 73 | 74 | override public function render(curPos:Vector3, params:ModifierParameters) { 75 | // var player = params.player; 76 | // var distance = params.distance; 77 | // var bumpyX = (40 * sin((distance + (100.0 * getPercent('bumpyXOffset', player))) / ((getPercent('bumpyXPeriod', player) * 24.0) + 24.0))); 78 | // var bumpyY = (40 * sin((distance + (100.0 * getPercent('bumpyYOffset', player))) / ((getPercent('bumpyYPeriod', player) * 24.0) + 24.0))); 79 | // var bumpyZ = (40 * sin((distance + (100.0 * getPercent('bumpyZOffset', player))) / ((getPercent('bumpyZPeriod', player) * 24.0) + 24.0))); 80 | 81 | // curPos.x += bumpyX * getPercent('bumpyX', player); 82 | // curPos.y += bumpyY * getPercent('bumpyY', player); 83 | // curPos.z += bumpyZ * (getPercent('bumpy', player) + getPercent('bumpyZ', player)); 84 | 85 | applyBumpy(curPos, params, '', 'z'); 86 | applyBumpy(curPos, params, 'x', 'x'); 87 | applyBumpy(curPos, params, 'y', 'y'); 88 | applyBumpy(curPos, params, 'z', 'z'); 89 | 90 | return curPos; 91 | } 92 | 93 | override public function visuals(data:VisualParameters, params:ModifierParameters) { 94 | applyAngle(data, params, '', 'z'); 95 | applyAngle(data, params, 'x', 'x'); 96 | applyAngle(data, params, 'y', 'y'); 97 | applyAngle(data, params, 'z', 'z'); 98 | 99 | return data; 100 | } 101 | 102 | override public function shouldRun(params:ModifierParameters):Bool 103 | return true; 104 | } 105 | -------------------------------------------------------------------------------- /modchart/backend/standalone/adapters/fpsplus/Fpsplus.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone.adapters.fpsplus; 2 | 3 | import flixel.FlxCamera; 4 | import flixel.FlxSprite; 5 | import modchart.backend.standalone.IAdapter; 6 | import note.Note; 7 | 8 | class Fpsplus implements IAdapter { 9 | private var __fCrochet:Float = 0; 10 | 11 | public function new() {} 12 | 13 | public function onModchartingInitialization() { 14 | __fCrochet = Conductor.crochet / 4; 15 | } 16 | 17 | public function isTapNote(sprite:FlxSprite) { 18 | return sprite is Note; 19 | } 20 | 21 | // Song related 22 | public function getSongPosition():Float { 23 | return Conductor.songPosition; 24 | } 25 | 26 | @:privateAccess public function getCurrentBeat():Float { 27 | return Conductor.songPosition / Conductor.crochet; 28 | } 29 | 30 | @:privateAccess public function getCurrentCrochet():Float { 31 | return Conductor.crochet; 32 | } 33 | 34 | public function getBeatFromStep(step:Float):Float { 35 | return step * 4; 36 | } 37 | 38 | public function arrowHit(arrow:FlxSprite) { 39 | if (arrow is Note) { 40 | final note:Note = cast arrow; 41 | return note.wasGoodHit; 42 | } 43 | return false; 44 | } 45 | 46 | public function isHoldEnd(arrow:FlxSprite) { 47 | if (arrow is Note) { 48 | final note:Note = cast arrow; 49 | return note.isSustainEnd; 50 | } 51 | return false; 52 | } 53 | 54 | public function getLaneFromArrow(arrow:FlxSprite) { 55 | if (arrow is Note) { 56 | final note:Note = cast arrow; 57 | return note.noteData; 58 | } 59 | 60 | return arrow.ID; 61 | } 62 | 63 | public function getPlayerFromArrow(arrow:FlxSprite) { 64 | if (arrow is Note) { 65 | final castedNote:Note = cast arrow; 66 | return castedNote.mustPress ? 1 : 0; 67 | } 68 | 69 | return PlayState.instance.playerStrums.members.contains(arrow) ? 1 : 0; 70 | } 71 | 72 | public function getHoldLength(item:FlxSprite):Float 73 | return __fCrochet; 74 | 75 | public function getHoldParentTime(arrow:FlxSprite) { 76 | final note:Note = cast arrow; 77 | return note.strumTime; 78 | } 79 | 80 | // im so fucking sorry for those conditionals 81 | public function getKeyCount(?player:Int = 0):Int { 82 | return 4; 83 | } 84 | 85 | public function getPlayerCount():Int { 86 | return 2; 87 | } 88 | 89 | public function getTimeFromArrow(arrow:FlxSprite) { 90 | if (arrow is Note) { 91 | final note:Note = cast arrow; 92 | return note.strumTime; 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | public function getHoldSubdivisions(hold:FlxSprite):Int { 99 | return 3; 100 | } 101 | 102 | public function getDownscroll():Bool { 103 | return config.Config.downscroll; 104 | } 105 | 106 | public function getDefaultReceptorX(lane:Int, player:Int):Float { 107 | return __getStrumGroupFromPlayer(player).members[lane].x; 108 | } 109 | 110 | public function getDefaultReceptorY(lane:Int, player:Int):Float { 111 | return __getStrumGroupFromPlayer(player).members[lane].y; 112 | } 113 | 114 | public function getArrowCamera():Array 115 | return [PlayState.instance.camHUD]; 116 | 117 | public function getCurrentScrollSpeed():Float { 118 | return PlayState.SONG.speed * PlayState.instance.scrollSpeedMultiplier * .45; 119 | } 120 | 121 | // 0 receptors 122 | // 1 tap arrows 123 | // 2 hold arrows 124 | // 3 lane attachments 125 | public function getArrowItems() { 126 | var pspr:Array>> = [[[], [], []], [[], [], []]]; 127 | 128 | @:privateAccess 129 | final strums = [PlayState.instance.enemyStrums, PlayState.instance.playerStrums]; 130 | for (i in 0...strums.length) { 131 | strums[i].forEachAlive(strumNote -> { 132 | if (pspr[i] == null) 133 | pspr[i] = []; 134 | 135 | pspr[i][0].push(strumNote); 136 | }); 137 | } 138 | PlayState.instance.notes.forEachAlive(strumNote -> { 139 | final player = Adapter.instance.getPlayerFromArrow(strumNote); 140 | if (pspr[player] == null) 141 | pspr[player] = []; 142 | 143 | pspr[player][strumNote.isSustainNote ? 2 : 1].push(strumNote); 144 | }); 145 | 146 | return pspr; 147 | } 148 | 149 | private function __getStrumGroupFromPlayer(player:Int):flixel.group.FlxGroup.FlxTypedGroup { 150 | return player == 1 ? PlayState.instance.playerStrums : PlayState.instance.enemyStrums; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /modchart/Config.hx: -------------------------------------------------------------------------------- 1 | package modchart; 2 | 3 | /** 4 | * Configuration settings for modchart behavior. 5 | * 6 | * This class contains various static variables that control rendering, 7 | * performance optimizations, and visual settings for modcharts. 8 | * Adjust these settings to customize how elements behave and render. 9 | */ 10 | class Config { 11 | /** 12 | * Enables or disables 3D cameras. 13 | * 14 | * Setting this to `false` will disable 3D camera functionality, which may improve performance. 15 | * When disabled, all 3D-related transformations and rendering will be skipped. 16 | * 17 | * Default: `true` (3D cameras enabled). 18 | */ 19 | public static var CAMERA3D_ENABLED:Bool = true; 20 | 21 | /** 22 | * Defines the order of rotation axes. 23 | * 24 | * Determines the sequence in which rotations are applied around the X, Y, and Z axes. 25 | * Different orders can produce different final orientations due to rotational dependency. 26 | * 27 | * Default: `Z_Y_X` (Rotates around the X-axis last). 28 | */ 29 | public static var ROTATION_ORDER:RotationOrder = Z_Y_X; 30 | 31 | /** 32 | * Optimizes the rendering of hold arrows. 33 | * 34 | * Theoretically, this makes calculations twice as fast by reducing redundant computations. 35 | * However, it is not recommended for complex modcharts, as it may cause holds to look waggy, 36 | * especially when using modifiers that use rotation or complex path operations> 37 | * 38 | * Default: `false` (Regular hold rendering). 39 | */ 40 | public static var OPTIMIZE_HOLDS:Bool = false; 41 | 42 | /** 43 | * Scales the Z-axis values. 44 | * 45 | * This value is used to multiply the Z coordinate, effectively scaling depth. 46 | * A higher value increases the perceived depth, while a lower value flattens it. 47 | * 48 | * Default: `1` (No scaling applied). 49 | */ 50 | public static var Z_SCALE:Float = 1; 51 | 52 | /** 53 | * Ignores or renders the arrow path lines. 54 | * 55 | * When enabled, performance will be affected 56 | * due to path computation. 57 | * 58 | * Default: `false` (Disabled for performance). 59 | */ 60 | public static var RENDER_ARROW_PATHS:Bool = false; 61 | 62 | /** 63 | * Extra configurations for the Arrow Paths. 64 | */ 65 | public static var ARROW_PATHS_CONFIG:ArrowPathConfig = { 66 | APPLY_COLOR: false, 67 | APPLY_ALPHA: true, 68 | APPLY_DEPTH: true, 69 | APPLY_SCALE: false, 70 | RESOLUTION: 1, 71 | LENGTH: 0 72 | }; 73 | 74 | /** 75 | * Scales the hold end size. 76 | * 77 | * Default: `1` (no scaling applied). 78 | */ 79 | public static var HOLD_END_SCALE:Float = 1; 80 | 81 | /** 82 | * Prevents scaling the hold ends. (Some people doens't like that lol) 83 | * 84 | * **WARNING**: Performance may be affected if there's too much 85 | * hold arrows at screen. (it basicly uses one extra `getPath()` call) 86 | * 87 | * Default: `false` 88 | */ 89 | public static var PREVENT_SCALED_HOLD_END:Bool = false; 90 | 91 | /** 92 | * Enables or disables column-specific modifiers. 93 | * 94 | * Disabling this may improve performance by 95 | * reducing the number of `getPercent()` calls. 96 | * 97 | * **WARNING**: This does **not** directly affect any modifier. 98 | * It only applies to *built-in modifiers*. 99 | * Custom modifiers must manually check 100 | * this config value for compatibility. 101 | * 102 | * Default: `true` 103 | */ 104 | public static var COLUMN_SPECIFIC_MODIFIERS:Bool = true; 105 | 106 | /** 107 | * Shows the sustains behind the strums 108 | * 109 | * Default `false` 110 | */ 111 | public static var HOLDS_BEHIND_STRUM:Bool = false; 112 | } 113 | 114 | typedef ArrowPathConfig = { 115 | /** 116 | * Line alpha gets affected 117 | * by color/glow modifiers. 118 | */ 119 | APPLY_COLOR:Bool, 120 | 121 | /** 122 | * Line alpha gets affected 123 | * by alpha modifiers. 124 | */ 125 | APPLY_ALPHA:Bool, 126 | 127 | /** 128 | * Thickness gets affected by Z. 129 | */ 130 | APPLY_DEPTH:Bool, 131 | 132 | /** 133 | * Thickness gets affected by arrow scale. 134 | */ 135 | APPLY_SCALE:Bool, 136 | 137 | /** 138 | * "Resolution" multiplier of arrow paths. 139 | * Higher value = More divisions = Smoother path. 140 | * **WARNING**: Can't be zero or it will CRASH. 141 | */ 142 | RESOLUTION:Float, 143 | 144 | /** 145 | * Path lines length addition. 146 | */ 147 | LENGTH:Int 148 | } 149 | -------------------------------------------------------------------------------- /assets/modchart/eyeShape.csv: -------------------------------------------------------------------------------- 1 | 0.1473563313484192;-1.173509955406189;-0.025870561599731445;1.0 2 | -0.07278978824615479;-0.8749154210090637;-0.025870442390441895;1.0 3 | -0.1360388994216919;-0.5885784029960632;-0.02587038278579712;1.0 4 | -0.1272134780883789;-0.2826293110847473;-0.025870323181152344;1.0 5 | -0.0346643328666687;-0.014026458375155926;-0.025870265439152718;1.0 6 | 0.07127505540847778;0.19256281852722168;-0.025870218873023987;1.0 7 | 0.20167803764343262;0.3566891551017761;-0.025870174169540405;1.0 8 | 0.3970933258533478;0.5228505730628967;-0.025870174169540405;1.0 9 | 0.6490659117698669;0.6281641721725464;-0.025870144367218018;1.0 10 | 0.9392634630203247;0.6835513710975647;-0.025870144367218018;1.0 11 | 1.1779743432998657;0.6422060132026672;-0.025870144367218018;1.0 12 | 1.3967926502227783;0.5548347234725952;-0.025870144367218018;1.0 13 | 1.5454018115997314;0.4464007318019867;-0.025870174169540405;1.0 14 | 1.6569561958312988;0.35122841596603394;-0.025870203971862793;1.0 15 | 1.7700707912445068;0.2112003117799759;-0.025870218873023987;1.0 16 | 1.8408021926879883;0.026901591569185257;-0.02587025612592697;1.0 17 | 1.77579665184021;-0.17223206162452698;-0.025870293378829956;1.0 18 | 1.5668072700500488;-0.38003724813461304;-0.02587035298347473;1.0 19 | 1.3264399766921997;-0.5410714745521545;-0.02587038278579712;1.0 20 | 1.0712717771530151;-0.6257328391075134;-0.02587038278579712;1.0 21 | 0.7527555227279663;-0.6257328391075134;-0.02587038278579712;1.0 22 | 0.512388288974762;-0.5434396266937256;-0.02587038278579712;1.0 23 | 0.30872732400894165;-0.4131913483142853;-0.02587035298347473;1.0 24 | 0.16129159927368164;-0.2839236855506897;-0.025870323181152344;1.0 25 | 0.0930701494216919;-0.14543351531028748;-0.025870293378829956;1.0 26 | 0.1373387575149536;0.01482496876269579;-0.02587025798857212;1.0 27 | 0.30077648162841797;0.19729122519493103;-0.025870218873023987;1.0 28 | 0.5111165642738342;0.3429112434387207;-0.025870203971862793;1.0 29 | 0.740181028842926;0.4319746792316437;-0.025870174169540405;1.0 30 | 0.9410601854324341;0.46475014090538025;-0.025870174169540405;1.0 31 | 1.257451057434082;0.38441526889801025;-0.025870174169540405;1.0 32 | 1.4755431413650513;0.24583123624324799;-0.025870203971862793;1.0 33 | 1.6275371313095093;0.08942453563213348;-0.025870241224765778;1.0 34 | 1.6554844379425049;-0.007165192160755396;-0.02587026357650757;1.0 35 | 1.5981189012527466;-0.11895430088043213;-0.02587028592824936;1.0 36 | 1.4765236377716064;-0.24839434027671814;-0.02587030827999115;1.0 37 | 1.3108011484146118;-0.3391004204750061;-0.025870323181152344;1.0 38 | 1.1009514331817627;-0.40725255012512207;-0.02587035298347473;1.0 39 | 0.9215005040168762;-0.427845299243927;-0.02587035298347473;1.0 40 | 0.7523459196090698;-0.37195074558258057;-0.02587035298347473;1.0 41 | 0.6116287708282471;-0.2714385986328125;-0.025870323181152344;1.0 42 | 0.5557342171669006;-0.17484888434410095;-0.025870293378829956;1.0 43 | 0.5086650848388672;-0.01255855243653059;-0.025870265439152718;1.0 44 | 0.5185865163803101;0.13845482468605042;-0.02587023377418518;1.0 45 | 0.5732599496841431;0.2533619701862335;-0.025870218873023987;1.0 46 | 0.6765747666358948;0.3585952818393707;-0.02571144700050354;1.0 47 | 0.8463984131813049;0.4268237352371216;-0.024282634258270264;1.0 48 | 1.023078203201294;0.42666760087013245;-0.025711417198181152;1.0 49 | 1.191202998161316;0.3325379490852356;-0.025870203971862793;1.0 50 | 1.2879494428634644;0.1944262534379959;-0.025870218873023987;1.0 51 | 1.294381022453308;0.050065524876117706;-0.025870252400636673;1.0 52 | 1.2249501943588257;-0.0771825760602951;-0.025870278477668762;1.0 53 | 1.0959038734436035;-0.162880077958107;-0.025870293378829956;1.0 54 | 0.9433809518814087;-0.19485558569431305;-0.02587030827999115;1.0 55 | 0.8193532824516296;-0.14596927165985107;-0.025870293378829956;1.0 56 | 0.7654011249542236;-0.04284199699759483;-0.025870271027088165;1.0 57 | 0.7538164854049683;0.03941357508301735;-0.02587025612592697;1.0 58 | 0.783234715461731;0.09530812501907349;-0.025870241224765778;1.0 59 | 0.8508965373039246;0.14973177015781403;-0.02587023377418518;1.0 60 | 0.9268935322761536;0.16247966885566711;-0.02587023377418518;1.0 61 | 1.001909852027893;0.13992571830749512;-0.02587023377418518;1.0 62 | 1.0582947731018066;0.09383721649646759;-0.025870241224765778;1.0 63 | 1.0695717334747314;0.03696205094456673;-0.02587025612592697;1.0 64 | 1.0313280820846558;-0.008636138401925564;-0.02587026357650757;1.0 65 | 0.9739626049995422;-0.025306442752480507;-0.025870267301797867;1.0 66 | 0.9195389151573181;-0.00520401680842042;-0.02587026357650757;1.0 67 | 0.909242570400238;0.018330534920096397;-0.02587025798857212;1.0 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | FunkinModchart 4 |
5 | 6 |

An Modcharting backend library for Friday Night Funkin' made by modders, to modders.

7 |

8 | 9 | **FunkinModchart** is a tool designed to bring [NotITG](https://www.noti.tg/) visuals and capabilities to [Friday Night Funkin](https://ninja-muffin24.itch.io/funkin) ***or VSRG games made in flixel***, adding modifiers to change the **arrow trajectory, colors, transparency and the rotation angle through 3D Axes**, event system to change the modifier's percent easing or setting those to create endless amazing visual effects and even **more**!. 10 | 11 | This framework also provides **extra features** that can help you to make even more crazy visuals, as **arrow paths, 3D view camera, AFTs**, etc. *(If you have already modeled NotITG or StepMania, you know what I am talking about)* 12 | 13 |
14 |

Importing the library

15 | 16 | This library currently has support for multiple Friday Night Funkin' engines, such as [Codename Engine](https://codename-engine.com), [Psych Engine](https://github.com/ShadowMario/FNF-PsychEngine) and [FPS Plus](https://github.com/ThatRozebudDude/FPS-Plus-Public) click [here](https://github.com/TheoDevelops/FunkinModchart/blob/main/SUPPORT.md) for more information, and only takes a couple of lines of code to import it: 17 | 18 | #### Go to your project and open `Project.xml` 19 | At the bottom of where the haxelib section is, paste this code. 20 | ```xml 21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | Fill in the definitions with your engine name and version using the [format](https://github.com/TheoDevelops/FunkinModchart/blob/main/SUPPORT.md) mentioned. 29 | 30 | And if you did everything good, it should compile and work normal ! 31 | 32 |
33 | 34 |
35 |

Using the library

36 | 37 | This is the easiest thing, you only have to do a couple of steps for add the modchart instance to a song. 38 | 39 | #### Import `modchart.Manager` 40 | And then make an instance of it, and add it to the state. 41 | 42 | ```haxe 43 | var funkin_modchart_instance:Manager = new Manager(); 44 | // On your create function. 45 | add(funkin_modchart_instance); 46 | ``` 47 | 48 | This can be done via haxe scripts or source code, and will soon be possible in PsychLua for `PSYCH` as well. 49 | 50 | Make sure that at the time you create the instance, the notes and strums were already generated. 51 | This now all the stuff should be working, do your stuff now. 52 | 53 | #### Making a Modchart 54 | First, you should know all the modcharting functions, check them [here](https://github.com/TheoDevelops/FunkinModchart/blob/main/DOC.md). 55 | To make a modchart you don't necessarily have to follow instructions, it's a matter of experimenting with the modifiers and all the functions that FunkinModchart offers, although previous experience with The Mirin Template and NotITG would help you design a good modchart more easily. 56 | 57 |
58 | 59 |
60 |

Making your own Adapter

61 | 62 | An **Adapter** is a wrapper class which contains all the methods required by the modchart manager to work. 63 | Before you make the Adapter for your Friday Night Funkin' Engine or your VSRG game, there are 2 requirements. 64 | 65 | ### Your game should be made in HaxeFlixel 66 | I think this obvious since this was originally made for only **Friday Night Funkin'** engines, but just in case. 67 | ### Your arrows, receptors and holds needs to be a FlxSprite 68 | FunkinModchart uses a group of custom renderers that takes a **FlxSprite** as input, so you can't use this tool if your arrow system is based on **3D Sprites** or complex graphic rendering. 69 | 70 | To make your own Adapter, read [read the methods of the interface](/modchart/standalone/IAdapter.hx), with a little analysis, you will understand how to make your own adapters. 71 | If you have not understood correctly, [you can rely on existing adapters](/modchart/standalone/adapters/). 72 | 73 | The name of your adapter class will be the name required in the "FM_ENGINE" define. 74 | One more thing you should keep in mind is that the class name must begin with a capital letter, and all other characters must begin with lowercase. 75 | 76 | In case you want to rewrite the adapter when the game is running, you can do so. (can be useful for editors or viewing modcharts outside of the playing field). 77 |
78 | 79 | ## Credits 80 | **Theo**: Owner, Lead coder. 81 | 82 | **Ne_Eo (aka. Neo)**: Coder (bugfixes and Optimisation). 83 | 84 | **EdwhakKB**: Maintainer. 85 | 86 | **OpenITG:** Some math taken for modifiers. 87 | 88 | **4mbr0s3:** Some code taken from [Schmovin'](https://github.com/4mbr0s3-2/Schmovin), his own Modcharting Lib. (really impressive) 89 | 90 | **Soihan**: Logo Artist. 91 | 92 | ## Special Thanks 93 | 94 | **lunarcleint:** Moral support, the goat. 95 | 96 | **Tsaku:** Support, betatester. 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## August 10, 2021 2 | - Hold note quad rework (from Schmovin’) 3 | - Added modifiers (PathModifier, Bumpy, Infinite) 4 | - Fixed note positions 5 | - Improved code structure 6 | - Fixed hold size (`+= 6`) 7 | 8 | ## August 10, 2021 (2) 9 | - Hold scale fix 10 | 11 | ## October 12, 2024 12 | - Hold graphic subdivision system (from Schmovin’) 13 | - A subdivision system was already in, but hold notes were literally split, causing gameplay imbalance and visual issues 14 | - New system reduces lag and avoids health drain/exploit issues 15 | - No need to modify source code when using in CNE 16 | - Fixed hold note position and scale when using zoom 17 | - Code improvements and optimizations 18 | 19 | ## October 12, 2024 (2) 20 | - Schmovin Drunk & Tipsy Math (False Paradise recreation WIP) 21 | 22 | ## October 12, 2024 (3) 23 | - Fixed hold spacing bug when BPM changes 24 | 25 | ## October 15, 2024 26 | - Custom mod examples 27 | - Modchart examples 28 | - False Paradise content (modchart still WIP, not fully functional) 29 | - Other minor additions 30 | 31 | ## October 24, 2024 32 | - Improved Infinite modifier 33 | - General optimization and code improvements 34 | - Added Bounce mod 35 | 36 | ## October 31, 2024 37 | - Switched `List` to `Array` in `ModchartGroup` for performance 38 | - Added arrow paths (toggle via `Manager.renderArrowPaths = true`) 39 | - Submods for arrow paths: 40 | - Alpha 41 | - Thickness 42 | - Scale (length/limit) 43 | 44 | ## November 3, 2024 45 | - Fixed critical memory leak in arrow path renderer (from 70MB to 4GB+) 46 | - Renderer optimization 47 | - Added X mod 48 | 49 | ## November 5, 2024 50 | - Rewrote Path Manager (optimized version) 51 | - Minor code improvements 52 | 53 | ## December 6, 2024 54 | - 3D rotation for regular notes (and hold notes) 55 | - `AngleX`, `AngleY`, `AngleZ` now used as visual components 56 | - Custom 3D camera using `Matrix3D` 57 | 58 | ## December 8, 2024 59 | - Added Skew mods 60 | - Improved Stealth mods (glow/alpha) on hold notes (smoother with subdivision) 61 | 62 | ## December 17, 2024 63 | - Added `Centered2` path (centered path mod) 64 | - General improvements 65 | 66 | ## December 31, 2024 67 | - Added Tornado mod (ported from OpenITG) 68 | - Hold rotation now can be toggled via `rotateHoldX/Y/Z` (0 to 1) 69 | - Improved README 70 | - Further improvements 71 | 72 | ## January 4, 2025 73 | - Renderer classes moved from `Manager.hx` to `ModchartGraphics.hx` 74 | - Major code cleanup and structural improvements 75 | 76 | ## January 6, 2025 77 | - Multiple playfield support (each with own modifiers and percents) 78 | - Plugin-based standalone system (testing phase) 79 | - Removed rewriting of Flixel classes (now uses macros) 80 | 81 | ## January 6, 2025 (2) 82 | - Fixed arrow animation issue 83 | 84 | (*Many changes were not indexed between commits*) 85 | 86 | ## February 11, 2025 87 | - New 3D camera using View Matrix 88 | - Fixed projection issues with arrow rotation (perspective correction) 89 | - Switched from Euler angles to Quaternions 90 | - Fixes and refactors 91 | - Major optimizations 92 | 93 | ## February 12, 2025 94 | - Fixed 3D camera offsets (was breaking visuals) 95 | - Optimized `ModifierGroup` percent handling (`Vector` instead of `IntMap`) 96 | - General prealloc optimizations 97 | - Fixed `Rotate` base modifier (wrong rotation origin) 98 | - Fixed missing X rotation 99 | - Codename adapter now reads real strum position (no modchart offset needed) 100 | 101 | ## February 13, 2025 102 | - Proper support for Sprite Sheet Packed (angle support) 103 | - New Adapter: FPS Plus 104 | 105 | ## February 19, 2025 106 | - Re-added Scripted Modifiers system 107 | 108 | ## March 3, 2025 109 | - Fixed `longHolds` modifier visuals 110 | - Added Hold Rotation mods (rotation based on parent note position) 111 | - Added `DizzyHolds` modifier 112 | - Expanded `Config.hx` customization options (WIP) 113 | 114 | ## March 10, 2025 115 | - New modifier percent system 116 | - General optimizations 117 | - WIP cache system (later discarded) 118 | 119 | ## March 10, 2025 (2) 120 | - Started documentation (WIP) 121 | - Renamed `ScriptedModifier` to `DynamicModifier` 122 | - Compatibility with Flixel 5.1 and below (no color transform) 123 | - Fixes for Psych Engine 0.6 support 124 | 125 | ## March 31, 2025 126 | - Major code cleanup 127 | - Reorganized source files 128 | - Massive optimization pass 129 | 130 | ## August 4, 2025 131 | - Flixel 6 compatibility ([#18](https://github.com/TheoDevelops/FunkinModchart/pull/18)) 132 | - Codename adapter enhancement ([#26](https://github.com/TheoDevelops/FunkinModchart/pull/26)) 133 | - New arrow path renderer (more efficient) 134 | - Rewritten event manager for better performance 135 | - `Add` event works correctly now 136 | - Multiple optimizations and enhancements 137 | 138 | ## August 6, 2025 139 | - Added CSV Paths to Assets for `ArrowShape` and `EyeShape` mods. 140 | - Small improvements. 141 | 142 | ## August, 8, 2025 143 | - Fixed crash caused by Arrow Path when alpha is 0. 144 | - Fixed `ADD` event (again). 145 | 146 | ## Agoust 10, 2025 147 | - PR #39: CNE Invisible Strumline Fix + Draw Sustain Behind Strums 148 | 149 | ## October, 23, 2025 150 | - Fixed camera targets. 151 | - Workaround on Codename adapter to properly use strumlines, strum or notes cameras. 152 | 153 | ## October, 24, 2025 154 | - Fixed holds from player 0 appearing just after a hold from player 1 appears. 155 | - Added the logo. -------------------------------------------------------------------------------- /modchart/backend/standalone/adapters/psych/Psych.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone.adapters.psych; 2 | 3 | #if (FM_ENGINE_VERSION == "1.0" || FM_ENGINE_VERSION == "0.7") 4 | import backend.ClientPrefs; 5 | import backend.Conductor; 6 | import objects.Note; 7 | import objects.NoteSplash; 8 | import objects.StrumNote as Strum; 9 | import states.PlayState; 10 | #else 11 | import ClientPrefs; 12 | import Conductor; 13 | import Note; 14 | import PlayState; 15 | import StrumNote as Strum; 16 | #end 17 | import flixel.FlxCamera; 18 | import flixel.FlxG; 19 | import flixel.FlxSprite; 20 | import modchart.Manager; 21 | import modchart.backend.standalone.IAdapter; 22 | 23 | class Psych implements IAdapter { 24 | private var __fCrochet:Float = 0; 25 | 26 | public function new() { 27 | try { 28 | setupLuaFunctions(); 29 | } catch (e) { 30 | trace('[FunkinModchart Psych Adapter] Failed while adding lua functions: $e'); 31 | } 32 | } 33 | 34 | public function onModchartingInitialization() { 35 | __fCrochet = (Conductor.crochet + 8) / 4; 36 | } 37 | 38 | private function setupLuaFunctions() { 39 | #if LUA_ALLOWED 40 | // todo 41 | #end 42 | } 43 | 44 | public function isTapNote(sprite:FlxSprite) { 45 | return sprite is Note; 46 | } 47 | 48 | // Song related 49 | public function getSongPosition():Float { 50 | return Conductor.songPosition; 51 | } 52 | 53 | public function getCurrentBeat():Float { 54 | @:privateAccess 55 | return PlayState.instance.curDecBeat; 56 | } 57 | 58 | public function getCurrentCrochet():Float { 59 | return Conductor.crochet; 60 | } 61 | 62 | public function getBeatFromStep(step:Float) 63 | return step * .25; 64 | 65 | public function arrowHit(arrow:FlxSprite) { 66 | if (arrow is Note) 67 | return cast(arrow, Note).wasGoodHit; 68 | return false; 69 | } 70 | 71 | public function isHoldEnd(arrow:FlxSprite) { 72 | if (arrow is Note) { 73 | final castedNote = cast(arrow, Note); 74 | 75 | if (castedNote.nextNote != null) 76 | return !castedNote.nextNote.isSustainNote; 77 | } 78 | return false; 79 | } 80 | 81 | public function getLaneFromArrow(arrow:FlxSprite) { 82 | if (arrow is Note) 83 | return cast(arrow, Note).noteData; 84 | else if (arrow is Strum) @:privateAccess 85 | return cast(arrow, Strum).noteData; 86 | #if (FM_ENGINE_VERSION >= "1.0") 87 | if (arrow is NoteSplash) @:privateAccess 88 | return cast(arrow, NoteSplash).babyArrow.noteData; 89 | #end 90 | 91 | return 0; 92 | } 93 | 94 | public function getPlayerFromArrow(arrow:FlxSprite) { 95 | if (arrow is Note) 96 | return cast(arrow, Note).mustPress ? 1 : 0; 97 | if (arrow is Strum) @:privateAccess 98 | return cast(arrow, Strum).player; 99 | #if (FM_ENGINE_VERSION >= "1.0") 100 | if (arrow is NoteSplash) @:privateAccess 101 | return cast(arrow, NoteSplash).babyArrow.player; 102 | #end 103 | return 0; 104 | } 105 | 106 | public function getKeyCount(?player:Int = 0):Int { 107 | return 4; 108 | } 109 | 110 | public function getPlayerCount():Int { 111 | return 2; 112 | } 113 | 114 | public function getTimeFromArrow(arrow:FlxSprite) { 115 | if (arrow is Note) 116 | return cast(arrow, Note).strumTime; 117 | 118 | return 0; 119 | } 120 | 121 | public function getHoldSubdivisions(hold:FlxSprite):Int { 122 | return 4; 123 | } 124 | 125 | public function getHoldLength(item:FlxSprite):Float 126 | return __fCrochet; 127 | 128 | public function getHoldParentTime(arrow:FlxSprite) { 129 | final note:Note = cast arrow; 130 | return note.parent.strumTime; 131 | } 132 | 133 | public function getDownscroll():Bool { 134 | #if (FM_ENGINE_VERSION >= "0.7") 135 | return ClientPrefs.data.downScroll; 136 | #else 137 | return ClientPrefs.downScroll; 138 | #end 139 | } 140 | 141 | inline function getStrumFromInfo(lane:Int, player:Int) { 142 | var group = player == 0 ? PlayState.instance.opponentStrums : PlayState.instance.playerStrums; 143 | var strum = null; 144 | group.forEach(str -> { 145 | @:privateAccess 146 | if (str.noteData == lane) 147 | strum = str; 148 | }); 149 | return strum; 150 | } 151 | 152 | public function getDefaultReceptorX(lane:Int, player:Int):Float { 153 | return getStrumFromInfo(lane, player).x; 154 | } 155 | 156 | public function getDefaultReceptorY(lane:Int, player:Int):Float { 157 | return getDownscroll() ? FlxG.height - getStrumFromInfo(lane, player).y - Note.swagWidth : getStrumFromInfo(lane, player).y; 158 | } 159 | 160 | public function getArrowCamera():Array 161 | return [PlayState.instance.camHUD]; 162 | 163 | public function getCurrentScrollSpeed():Float { 164 | return PlayState.instance.songSpeed * .45; 165 | } 166 | 167 | // 0 receptors 168 | // 1 tap arrows 169 | // 2 hold arrows 170 | public function getArrowItems() { 171 | var pspr:Array>> = [[[], [], [], []], [[], [], [], []]]; 172 | 173 | @:privateAccess 174 | PlayState.instance.strumLineNotes.forEachAlive(strumNote -> { 175 | if (pspr[strumNote.player] == null) 176 | pspr[strumNote.player] = []; 177 | 178 | pspr[strumNote.player][0].push(strumNote); 179 | }); 180 | PlayState.instance.notes.forEachAlive(strumNote -> { 181 | final player = Adapter.instance.getPlayerFromArrow(strumNote); 182 | if (pspr[player] == null) 183 | pspr[player] = []; 184 | 185 | pspr[player][strumNote.isSustainNote ? 2 : 1].push(strumNote); 186 | }); 187 | #if (FM_ENGINE_VERSION >= "1.0") 188 | PlayState.instance.grpNoteSplashes.forEachAlive(splash -> { 189 | @:privateAccess 190 | if (splash.babyArrow != null && splash.active) { 191 | final player = splash.babyArrow.player; 192 | if (pspr[player] == null) 193 | pspr[player] = []; 194 | 195 | pspr[player][3].push(splash); 196 | } 197 | }); 198 | #end 199 | 200 | return pspr; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /modchart/backend/graphics/renderers/ModchartPathRenderer.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.graphics.renderers; 2 | 3 | import flixel.FlxG; 4 | import flixel.graphics.FlxGraphic; 5 | import flixel.util.FlxDestroyUtil; 6 | import openfl.geom.ColorTransform; 7 | 8 | var pathVector = new Vector3(); 9 | 10 | #if !openfl_debug 11 | @:fileXml('tags="haxe,release"') 12 | @:noDebug 13 | #end 14 | final class ModchartPathRenderer extends ModchartRenderer { 15 | var __lineGraphic:FlxGraphic; 16 | var __lastDivisions:Int = -1; 17 | 18 | var uvt:DrawData; 19 | var indices:DrawData; 20 | 21 | public function updateTris(divisions:Int) { 22 | final segs = divisions - 1; 23 | if (divisions != __lastDivisions) { 24 | uvt = new DrawData(segs * 12, true); 25 | indices = new DrawData(segs * 6, true); 26 | var ui = 0, ii = 0, vertCount = 0; 27 | for (div in 0...divisions) { 28 | for (_ in 0...4) { 29 | uvt.set(ui++, 0); 30 | uvt.set(ui++, 0); 31 | uvt.set(ui++, 1); 32 | } 33 | 34 | // indices 35 | indices.set(ii++, vertCount); 36 | indices.set(ii++, vertCount + 1); 37 | indices.set(ii++, vertCount + 2); 38 | indices.set(ii++, vertCount + 1); 39 | indices.set(ii++, vertCount + 3); 40 | indices.set(ii++, vertCount + 2); 41 | 42 | vertCount += 4; 43 | } 44 | } 45 | 46 | __lastDivisions = divisions; 47 | } 48 | 49 | public function new(instance:PlayField) { 50 | super(instance); 51 | 52 | __lineGraphic = FlxG.bitmap.create(10, 10, 0xFFFFFFFF); 53 | } 54 | 55 | var __lastPlayer:Int = -1; 56 | var __lastAlpha:Float = 0; 57 | var __lastThickness:Float = 0; 58 | 59 | // the entry sprite should be A RECEPTOR / STRUM !! 60 | override public function prepare(item:FlxSprite) { 61 | final lane = Adapter.instance.getLaneFromArrow(item); 62 | final fn = Adapter.instance.getPlayerFromArrow(item); 63 | 64 | final canUseLast = fn == __lastPlayer; 65 | 66 | final pathAlpha = canUseLast ? __lastAlpha : instance.getPercent('arrowPathAlpha', fn); 67 | final pathThickness = canUseLast ? __lastThickness : instance.getPercent('arrowPathThickness', fn); 68 | 69 | if (pathAlpha <= 0 || pathThickness <= 0) 70 | return; 71 | 72 | __lastAlpha = pathAlpha; 73 | __lastThickness = pathThickness; 74 | __lastPlayer = fn; 75 | 76 | final divisions = Std.int(20 * Config.ARROW_PATHS_CONFIG.RESOLUTION); 77 | final limit = 1500 + Config.ARROW_PATHS_CONFIG.LENGTH; 78 | final interval = limit / divisions; 79 | final songPos = Adapter.instance.getSongPosition(); 80 | 81 | final segs = divisions - 1; 82 | final vertices = new DrawData(segs * 8, true); 83 | 84 | var vi = 0, vertCount = 0; 85 | 86 | var lastOutput:ModifierOutput = null; 87 | pathVector.setTo(Adapter.instance.getDefaultReceptorX(lane, fn), Adapter.instance.getDefaultReceptorY(lane, fn), 0); 88 | pathVector.incrementBy(ModchartUtil.getHalfPos()); 89 | 90 | final colored = Config.ARROW_PATHS_CONFIG.APPLY_COLOR; 91 | final applyAlpha = Config.ARROW_PATHS_CONFIG.APPLY_ALPHA; 92 | 93 | final transforms:Array = []; 94 | var tID:Int = 0; 95 | transforms.resize(segs); 96 | 97 | for (index in 0...divisions) { 98 | var hitTime = -500 + interval * index; 99 | 100 | var output = instance.modifiers.getPath(pathVector.clone(), { 101 | hitTime: songPos + hitTime, 102 | distance: hitTime, 103 | lane: lane, 104 | player: fn, 105 | isTapArrow: true 106 | }); 107 | 108 | if (lastOutput != null) { 109 | final p0 = lastOutput; 110 | final p1 = output; 111 | 112 | final pos0 = p0.pos; 113 | final pos1 = p1.pos; 114 | 115 | final dx = pos1.x - pos0.x; 116 | final dy = pos1.y - pos0.y; 117 | final len = Math.sqrt(dx * dx + dy * dy); 118 | final nx = -dy / len; 119 | final ny = dx / len; 120 | 121 | final t0 = (pathThickness * (Config.ARROW_PATHS_CONFIG.APPLY_SCALE ? p1.visuals.scaleX : 1) * (Config.ARROW_PATHS_CONFIG.APPLY_DEPTH ? 1 / pos0.z : 1)) * 0.5; 122 | final t1 = (pathThickness * (Config.ARROW_PATHS_CONFIG.APPLY_SCALE ? p1.visuals.scaleX : 1) * (Config.ARROW_PATHS_CONFIG.APPLY_DEPTH ? 1 / pos1.z : 1)) * 0.5; 123 | 124 | final a1x = pos0.x + nx * t0; 125 | final a1y = pos0.y + ny * t0; 126 | final a2x = pos0.x - nx * t0; 127 | final a2y = pos0.y - ny * t0; 128 | 129 | final b1x = pos1.x + nx * t1; 130 | final b1y = pos1.y + ny * t1; 131 | final b2x = pos1.x - nx * t1; 132 | final b2y = pos1.y - ny * t1; 133 | 134 | // vertices 135 | vertices.set(vi++, a1x); 136 | vertices.set(vi++, a1y); 137 | vertices.set(vi++, a2x); 138 | vertices.set(vi++, a2y); 139 | vertices.set(vi++, b1x); 140 | vertices.set(vi++, b1y); 141 | vertices.set(vi++, b2x); 142 | vertices.set(vi++, b2y); 143 | 144 | final glow = (colored ? p0.visuals.glow : 0); 145 | final fAlpha = (applyAlpha ? p0.visuals.alpha : 1); 146 | final negGlow = 1 - glow; 147 | final absGlow = glow * 255; 148 | transforms[tID++] = new ColorTransform(negGlow, negGlow, negGlow, fAlpha * pathAlpha, Math.round(p0.visuals.glowR * absGlow), 149 | Math.round(p0.visuals.glowG * absGlow), Math.round(p0.visuals.glowB * absGlow)); 150 | 151 | vertCount += 4; 152 | } 153 | 154 | lastOutput = output; 155 | } 156 | 157 | updateTris(divisions); 158 | 159 | var newInstruction:FMDrawInstruction = {}; 160 | newInstruction.extra = [vertices, indices, uvt, transforms]; 161 | queue[count++] = newInstruction; 162 | } 163 | 164 | override public function shift() { 165 | if (count == 0 || queue.length <= 0) 166 | return; 167 | 168 | final cameras = Adapter.instance.getArrowCamera(); 169 | for (instruction in queue) { 170 | if (instruction == null) 171 | continue; 172 | final vertices:DrawData = cast instruction.extra[0]; 173 | final indices:DrawData = cast instruction.extra[1]; 174 | final uvt:DrawData = cast instruction.extra[2]; 175 | final transforms:Array = cast instruction.extra[3]; 176 | 177 | for (camera in cameras) { 178 | var item = camera.startTrianglesBatch(__lineGraphic, false, true, NORMAL, true); 179 | @:privateAccess 180 | item.addGradientTriangles(vertices, indices, uvt, null, camera._bounds, transforms); 181 | } 182 | } 183 | } 184 | 185 | override function dispose() { 186 | __lineGraphic = FlxDestroyUtil.destroy(__lineGraphic); 187 | } 188 | 189 | inline static final ARROW_PATH_BOUNDARY_OFFSET:Float = 300; 190 | } 191 | -------------------------------------------------------------------------------- /modchart/backend/standalone/adapters/codename/Codename.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.standalone.adapters.codename; 2 | 3 | import flixel.FlxCamera; 4 | import flixel.FlxG; 5 | import flixel.FlxSprite; 6 | import funkin.backend.system.Conductor; 7 | import funkin.game.Note; 8 | import funkin.game.PlayState; 9 | import funkin.game.Splash; 10 | import funkin.game.Strum; 11 | import funkin.options.Options; 12 | import modchart.backend.standalone.IAdapter; 13 | 14 | class Codename implements IAdapter { 15 | public function new() {} 16 | 17 | public function onModchartingInitialization() { 18 | // do nothing 19 | #if (FM_ENGINE_VERSION == "1.0") 20 | PlayState.instance.splashHandler.visible = false; 21 | #end 22 | 23 | FlxG.signals.postDraw.add(postDraw); 24 | } 25 | 26 | public function isTapNote(sprite:FlxSprite) 27 | return sprite is Note; 28 | 29 | // Song related 30 | public function getSongPosition():Float 31 | return Conductor.songPosition; 32 | 33 | public function getCurrentBeat():Float 34 | return Conductor.curBeatFloat; 35 | 36 | public function getCurrentCrochet():Float 37 | return Conductor.crochet; 38 | 39 | public function getBeatFromStep(step:Float):Float 40 | return Conductor.getTimeInBeats(Conductor.getStepsInTime(step, Conductor.curChangeIndex), Conductor.curChangeIndex); 41 | 42 | public function arrowHit(arrow:FlxSprite) { 43 | if (arrow is Note) { 44 | final note:Note = cast arrow; 45 | return note.wasGoodHit; 46 | } 47 | return false; 48 | } 49 | 50 | public function isHoldEnd(arrow:FlxSprite) { 51 | if (arrow is Note) { 52 | final note:Note = cast arrow; 53 | return note.nextSustain == null; 54 | } 55 | return false; 56 | } 57 | 58 | public function getLaneFromArrow(arrow:FlxSprite) { 59 | if (arrow is Note) { 60 | final note:Note = cast arrow; 61 | return note.strumID; 62 | } else if (arrow is Strum) { 63 | final strum:Strum = cast arrow; 64 | return strum.ID; 65 | } else if (arrow is Splash) { 66 | final splash:Splash = cast arrow; 67 | return splash.strumID; 68 | } 69 | return 0; 70 | } 71 | 72 | public function getPlayerFromArrow(arrow:FlxSprite) { 73 | if (arrow is Note) { 74 | final note:Note = cast arrow; 75 | return note.strumLine.ID; 76 | } else if (arrow is Strum) { 77 | final strum:Strum = cast arrow; 78 | return strum.strumLine.ID; 79 | } else if (arrow is Splash) { 80 | final splash:Splash = cast arrow; 81 | return splash.strum.strumLine.ID; 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | public function getHoldLength(item:FlxSprite):Float { 88 | final note:Note = cast item; 89 | return note.sustainLength; 90 | } 91 | 92 | public function getHoldParentTime(arrow:FlxSprite) { 93 | final note:Note = cast arrow; 94 | return #if (FM_ENGINE_VERSION == "1.0") note.sustainParent.strumTime #else note.strumTime #end; 95 | } 96 | 97 | // im so fucking sorry for those conditionals 98 | public function getKeyCount(?player:Int = 0):Int { 99 | return PlayState.instance != null 100 | && PlayState.instance.strumLines != null 101 | && PlayState.instance.strumLines.members[player] != null ? PlayState.instance.strumLines.members[player].members.length : 4; 102 | } 103 | 104 | public function getPlayerCount():Int { 105 | return PlayState.instance != null && PlayState.instance.strumLines != null ? PlayState.instance.strumLines.length : 2; 106 | } 107 | 108 | public function getTimeFromArrow(arrow:FlxSprite) { 109 | if (arrow is Note) { 110 | final note:Note = cast arrow; 111 | return note.strumTime; 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | public function getHoldSubdivisions(hold:FlxSprite):Int { 118 | #if (FM_ENGINE_VERSION == "1.0") 119 | final val = Options.modchartingHoldSubdivisions; 120 | return val < 1 ? 1 : val; 121 | #else 122 | return 4; 123 | #end 124 | } 125 | 126 | public function getDownscroll():Bool 127 | return PlayState.instance.downscroll; 128 | 129 | public function getDefaultReceptorX(lane:Int, player:Int):Float 130 | return PlayState.instance.strumLines.members[player].members[lane].x; 131 | 132 | public function getDefaultReceptorY(lane:Int, player:Int):Float 133 | return PlayState.instance.strumLines.members[player].members[lane].y; 134 | 135 | public function getArrowCamera():Array 136 | return [PlayState.instance.camHUD]; 137 | 138 | public function getCurrentScrollSpeed():Float 139 | return PlayState.instance.scrollSpeed * .45; 140 | 141 | // 0 receptors 142 | // 1 tap arrows 143 | // 2 hold arrows 144 | // 3 receptor attachments 145 | public function getArrowItems() { 146 | var pspr:Array>> = []; 147 | 148 | var strumLineMembers = PlayState.instance.strumLines.members; 149 | 150 | for (i in 0...strumLineMembers.length) { 151 | final sl = strumLineMembers[i]; 152 | 153 | if (!sl.visible) 154 | continue; 155 | 156 | pspr[i] = []; 157 | pspr[i][0] = cast sl.members.copy(); 158 | pspr[i][1] = []; 159 | pspr[i][2] = []; 160 | pspr[i][3] = []; 161 | 162 | sl.forEach(str -> @:privateAccess { 163 | str._fmExtra.oldCameras = str._cameras; 164 | if (str._cameras == null || str._cameras.length == 0) 165 | str._cameras = str.strumLine.cameras; 166 | }); 167 | var st = 0; 168 | var nt = 0; 169 | sl.notes.forEachAlive((spr) -> @:privateAccess { 170 | spr._fmExtra.oldCameras = spr._cameras; 171 | if ((spr._cameras == null || spr._cameras.length == 0) && spr.__strumCameras != null) 172 | spr.cameras = spr.__strumCameras; 173 | 174 | spr.isSustainNote ? st++ : nt++; 175 | }); 176 | 177 | pspr[i][1].resize(nt); 178 | pspr[i][2].resize(st); 179 | 180 | var si = 0; 181 | var ni = 0; 182 | sl.notes.forEachAlive((spr) -> pspr[i][spr.isSustainNote ? 2 : 1][spr.isSustainNote ? si++ : ni++] = spr); 183 | } 184 | 185 | #if (FM_ENGINE_VERSION == "1.0") 186 | for (grp in PlayState.instance.splashHandler.grpMap) 187 | grp.forEachAlive((spr) -> if (spr.strum != null && spr.active) pspr[spr.strum.strumLine.ID][3].push(spr)); 188 | #end 189 | 190 | return pspr; 191 | } 192 | 193 | function postDraw() { 194 | var strumLineMembers = PlayState.instance.strumLines.members; 195 | for (i in 0...strumLineMembers.length) 196 | @:privateAccess { 197 | final sl = strumLineMembers[i]; 198 | 199 | if (!sl.visible) 200 | continue; 201 | sl.forEach(st -> { 202 | st.cameras = st._fmExtra.oldCameras; 203 | }); 204 | sl.notes.forEachAlive((spr) -> { 205 | spr.cameras = spr._fmExtra.oldCameras; 206 | }); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /modchart/backend/macros/ModifiersMacro.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.macros; 2 | 3 | #if macro 4 | import haxe.macro.Context; 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type; 8 | #end 9 | 10 | class ModifiersMacro { 11 | public static macro function get():ExprOf>> { 12 | if (!onGenerateCallbackRegistered) { 13 | onGenerateCallbackRegistered = true; 14 | Context.onGenerate(onGenerate); 15 | } 16 | 17 | var request:String = 'package~${'modchart.engine.modifiers.list'}~recursive'; 18 | 19 | classListsToGenerate.push(request); 20 | 21 | return macro modchart.backend.macros.CompiledClassList.get($v{request}); 22 | } 23 | 24 | #if macro 25 | /** 26 | * Callback executed after the typing phase but before the generation phase. 27 | * Receives a list of `haxe.macro.Type` for all types in the program. 28 | * 29 | * Only metadata can be modified at this time, which makes it a BITCH to access the data at runtime. 30 | */ 31 | static function onGenerate(allTypes:Array) { 32 | // Reset these, since onGenerate persists across multiple builds. 33 | classListsRaw = []; 34 | 35 | for (request in classListsToGenerate) { 36 | classListsRaw.set(request, []); 37 | } 38 | 39 | for (type in allTypes) { 40 | switch (type) { 41 | // Class instances 42 | case TInst(t, _params): 43 | var classType:ClassType = t.get(); 44 | var className:String = t.toString(); 45 | 46 | if (classType.isInterface) { 47 | // Ignore interfaces. 48 | } else { 49 | for (request in classListsToGenerate) { 50 | if (doesClassMatchRequest(classType, request)) { 51 | classListsRaw.get(request).push(className); 52 | } 53 | } 54 | } 55 | // Other types (things like enums) 56 | default: 57 | continue; 58 | } 59 | } 60 | 61 | compileClassLists(); 62 | } 63 | 64 | /** 65 | * At this stage in the program, `classListsRaw` is generated, but only accessible by macros. 66 | * To make it accessible at runtime, we must: 67 | * - Convert the String names to actual `Class` instances, and store it as `classLists` 68 | * - Insert the `classLists` into the metadata of the `CompiledClassList` class. 69 | * `CompiledClassList` then extracts the metadata and stores it where it can be accessed at runtime. 70 | */ 71 | static function compileClassLists() { 72 | var compiledClassList:ClassType = getClassType("modchart.backend.macros.CompiledClassList"); 73 | 74 | if (compiledClassList == null) 75 | throw "Could not find CompiledClassList class."; 76 | 77 | // Reset outdated metadata. 78 | if (compiledClassList.meta.has('classLists')) 79 | compiledClassList.meta.remove('classLists'); 80 | 81 | var classLists:Array = []; 82 | // Generate classLists. 83 | for (request in classListsToGenerate) { 84 | // Expression contains String, [Class...] 85 | var classListEntries:Array = [macro $v{request}]; 86 | for (i in classListsRaw.get(request)) { 87 | // TODO: Boost performance by making this an Array> instead of an Array 88 | // How to perform perform macro reificiation to types given a name? 89 | classListEntries.push(macro $v{i}); 90 | } 91 | 92 | classLists.push(macro $a{classListEntries}); 93 | } 94 | 95 | // Insert classLists into metadata. 96 | compiledClassList.meta.add('classLists', classLists, Context.currentPos()); 97 | } 98 | 99 | static function doesClassMatchRequest(classType:ClassType, request:String):Bool { 100 | var splitRequest:Array = request.split('~'); 101 | 102 | var requestType:String = splitRequest[0]; 103 | 104 | switch (requestType) { 105 | case 'package': 106 | var targetPackage:String = splitRequest[1]; 107 | var recursive:Bool = splitRequest[2] == 'recursive'; 108 | 109 | var classPackage:String = classType.pack.join('.'); 110 | 111 | if (recursive) { 112 | return StringTools.startsWith(classPackage, targetPackage); 113 | } else { 114 | var regex:EReg = ~/^${targetPackage}(\.|$)/; 115 | return regex.match(classPackage); 116 | } 117 | case 'extend': 118 | var targetClassName:String = splitRequest[1]; 119 | 120 | var targetClassType:ClassType = getClassType(targetClassName); 121 | 122 | if (implementsInterface(classType, targetClassType)) { 123 | return true; 124 | } else if (isSubclassOf(classType, targetClassType)) { 125 | return true; 126 | } 127 | 128 | return false; 129 | 130 | default: 131 | throw 'Unknown request type: ${requestType}'; 132 | } 133 | } 134 | 135 | /** 136 | * Retrieve a ClassType from a string name. 137 | */ 138 | static function getClassType(name:String):ClassType { 139 | switch (Context.getType(name)) { 140 | case TInst(t, _params): 141 | return t.get(); 142 | default: 143 | throw 'Class type could not be parsed: ${name}'; 144 | } 145 | } 146 | 147 | /** 148 | * Determine whether a given ClassType is a subclass of a given superclass. 149 | * @param classType The class to check. 150 | * @param superClass The superclass to check for. 151 | * @return Whether the class is a subclass of the superclass. 152 | */ 153 | public static function isSubclassOf(classType:ClassType, superClass:ClassType):Bool { 154 | if (areClassesEqual(classType, superClass)) 155 | return true; 156 | 157 | if (classType.superClass != null) { 158 | return isSubclassOf(classType.superClass.t.get(), superClass); 159 | } 160 | 161 | return false; 162 | } 163 | 164 | static function areClassesEqual(class1:ClassType, class2:ClassType):Bool { 165 | return class1.pack.join('.') == class2.pack.join('.') && class1.name == class2.name; 166 | } 167 | 168 | /** 169 | * Determine whether a given ClassType implements a given interface. 170 | * @param classType The class to check. 171 | * @param interfaceType The interface to check for. 172 | * @return Whether the class implements the interface. 173 | */ 174 | public static function implementsInterface(classType:ClassType, interfaceType:ClassType):Bool { 175 | for (i in classType.interfaces) { 176 | if (areClassesEqual(i.t.get(), interfaceType)) { 177 | return true; 178 | } 179 | } 180 | 181 | if (classType.superClass != null) { 182 | return implementsInterface(classType.superClass.t.get(), interfaceType); 183 | } 184 | 185 | return false; 186 | } 187 | 188 | static var onGenerateCallbackRegistered:Bool = false; 189 | 190 | static var classListsRaw:Map> = []; 191 | static var classListsToGenerate:Array = []; 192 | #end 193 | } 194 | -------------------------------------------------------------------------------- /modchart/backend/graphics/ModchartCamera3D.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.graphics; 2 | 3 | import flixel.FlxG; 4 | import openfl.Vector; 5 | import openfl.geom.Matrix3D; 6 | 7 | /** 8 | * `FlxCamera3D` extends `FlxCamera` to provide basic 3D camera functionality, 9 | * including transformations for position, rotation, and movement in 3D space. 10 | * 11 | * Features: 12 | * - 3D position (`eyePos`) and target (`lookAt`). 13 | * - View matrix transformation for rendering. 14 | * - Support for pitch, yaw, and roll rotations. 15 | * - Camera movement functions (`moveForward`, `moveRight`, `moveUp`). 16 | * 17 | * `NOTE`: All of those features only work on `FlxSprite3D` instances. 18 | */ 19 | #if !openfl_debug 20 | @:fileXml('tags="haxe,release"') 21 | @:noDebug 22 | #end 23 | final class ModchartCamera3D { 24 | /** 25 | * Represents the depth (Z-axis position) of the camera position. 26 | */ 27 | public var z:Float; 28 | 29 | /** 30 | * The position of the camera (viewpoint) in world coordinates. 31 | * 32 | * This represents the location of the viewer in 3D space. 33 | * The camera will be positioned at this point and will look toward `lookAt`. 34 | * Default: (0, 0, -10), meaning the camera starts 10 units back along the Z-axis. 35 | */ 36 | public var eyePos(default, null):Vector3 = new Vector3(0, 0, -10); 37 | 38 | /** 39 | * The target position that the camera is looking at. 40 | * 41 | * This point determines the direction the camera is facing. 42 | * The view matrix is calculated based on the vector from `eyePos` to `lookAt`. 43 | * Default: (0, 0, 0), meaning the camera looks toward the origin. 44 | */ 45 | public var lookAt(default, null):Vector3 = new Vector3(0, 0, 0); 46 | 47 | /** 48 | * The up direction vector, defining the camera's vertical orientation. 49 | * 50 | * This vector determines which direction is considered "up" for the camera. 51 | * It is typically set to (0, 1, 0) to align with the Y-axis, but can be modified 52 | * for custom orientations (e.g., to simulate a tilted horizon). 53 | */ 54 | public var up(default, null):Vector3 = new Vector3(0, 1, 0); 55 | 56 | /** 57 | * Rotation around the X-axis, controlling the tilt up/down. 58 | * 59 | * - Positive values tilt the camera downward. 60 | * - Negative values tilt the camera upward. 61 | * - Expressed in degrees. 62 | */ 63 | public var pitch:Float = 0; 64 | 65 | /** 66 | * Rotation around the Y-axis, controlling the left/right turn. 67 | * 68 | * - Positive values turn the camera to the right. 69 | * - Negative values turn the camera to the left. 70 | * - Expressed in degrees. 71 | */ 72 | public var yaw:Float = 0; 73 | 74 | /** 75 | * Rotation around the Z-axis, controlling the tilt sideways (roll). 76 | * 77 | * - Positive values tilt the camera clockwise. 78 | * - Negative values tilt the camera counterclockwise. 79 | * - Expressed in degrees. 80 | */ 81 | public var roll:Float = 0; 82 | 83 | @:noCompletion private var __viewMatrix(default, null):Matrix3D = new Matrix3D(); 84 | @:noCompletion private var __rotationMatrix(default, null):Matrix3D = new Matrix3D(); 85 | 86 | public function new() {} 87 | 88 | /** 89 | * Updates the camera's view matrix based on its position and rotation. 90 | * 91 | * This function recalculates the `__viewMatrix`, which is used to transform 92 | * world coordinates into the camera's local space. It applies rotation transformations 93 | * using the pitch, yaw, and roll angles and computes the final view matrix. 94 | * 95 | * Steps: 96 | * 1. Resets the `__viewMatrix` and `__rotationMatrix` to identity. 97 | * 2. Applies rotation transformations to align the camera's orientation. 98 | * 3. Defines the default axis directions (`forward`, `up`, `right`). 99 | * 4. Transforms these axes using the rotation matrix. 100 | * 5. Computes the camera position in view space. 101 | * 6. Constructs the view matrix using the transformed axes and camera position. 102 | * 103 | * This matrix is essential for rendering objects correctly from the camera's perspective. 104 | */ 105 | inline private function updateCameraView() { 106 | __viewMatrix.identity(); 107 | __rotationMatrix.identity(); 108 | 109 | // setup rotations 110 | __rotationMatrix.appendRotation(pitch * 180 / Math.PI, Vector3D.X_AXIS); // x 111 | __rotationMatrix.appendRotation(yaw * 180 / Math.PI, Vector3D.Y_AXIS); // y 112 | __rotationMatrix.appendRotation(roll * 180 / Math.PI, Vector3D.Z_AXIS); // z 113 | 114 | // eye shit 115 | var forward = Vector3D.Z_AXIS; // depth axis 116 | var up = Vector3D.Y_AXIS; // y axis 117 | var right = Vector3D.X_AXIS; // x axis 118 | 119 | // apply rotations 120 | forward = __rotationMatrix.transformVector(forward); 121 | up = __rotationMatrix.transformVector(up); 122 | right = __rotationMatrix.transformVector(right); 123 | 124 | // calc view position 125 | var negEye = new Vector3(-eyePos.x, -eyePos.y, -eyePos.z); 126 | 127 | __viewMatrix.rawData = new Vector(16, true, [ 128 | right.x, up.x, forward.x, 0, 129 | right.y, up.y, forward.y, 0, 130 | right.z, up.z, forward.z, 0, 131 | right.dotProduct(negEye), up.dotProduct(negEye), forward.dotProduct(negEye), 1 132 | ]); 133 | } 134 | 135 | /** 136 | * Transforms a given 3D vector from world space to the camera's view space. 137 | * 138 | * This function applies the current `__viewMatrix` to the input vector, 139 | * converting it from world coordinates to the camera's local coordinate system. 140 | * 141 | * @param vector The `Vector3` representing a point or direction in world space. 142 | * @return A new `Vector3` transformed into the camera's view space. 143 | * 144 | * Example usage: 145 | * ```haxe 146 | * var worldPos = new Vector3(10, 5, -20); 147 | * var viewPos = applyViewTo(worldPos); 148 | * ``` 149 | */ 150 | inline private function applyViewTo(vector:Vector3, ?origin:Vector3 = null) { 151 | var reference = origin != null ? origin : eyePos.add(new Vector3(FlxG.width / 2, FlxG.height / 2)); 152 | return __viewMatrix.transformVector(vector.subtract(reference)).add(reference); 153 | } 154 | 155 | // some helpers lol 156 | public function moveForward(amount:Float):Void { 157 | var forward:Vector3 = lookAt.subtract(eyePos); 158 | forward.normalize(); 159 | forward.scaleBy(amount); 160 | eyePos.incrementBy(forward); 161 | lookAt.incrementBy(forward); 162 | } 163 | 164 | public function moveRight(amount:Float):Void { 165 | var right:Vector3 = up.crossProduct(lookAt.subtract(eyePos)); 166 | right.normalize(); 167 | right.scaleBy(amount); 168 | eyePos.incrementBy(right); 169 | lookAt.incrementBy(right); 170 | } 171 | 172 | public function moveUp(amount:Float):Void { 173 | var moveUp:Vector3 = up.clone(); 174 | moveUp.normalize(); 175 | moveUp.scaleBy(amount); 176 | eyePos.incrementBy(moveUp); 177 | lookAt.incrementBy(moveUp); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /modchart/backend/util/ModchartUtil.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.util; 2 | 3 | import flixel.FlxG; 4 | import flixel.FlxSprite; 5 | import flixel.graphics.tile.FlxDrawTrianglesItem.DrawData; 6 | import flixel.math.FlxAngle; 7 | import flixel.math.FlxMath; 8 | import flixel.math.FlxRect; 9 | import haxe.ds.Vector; 10 | import modchart.engine.events.Event; 11 | import modchart.engine.events.types.AddEvent; 12 | import modchart.engine.events.types.EaseEvent; 13 | import openfl.geom.Matrix3D; 14 | 15 | using StringTools; 16 | 17 | #if !openfl_debug 18 | @:fileXml('tags="haxe,release"') 19 | @:noDebug 20 | #end 21 | @:keep class ModchartUtil { 22 | // pain (we need this if we want support for sprite sheet packer) 23 | @:pure 24 | inline public static function getFrameAngle(spr:FlxSprite):Float { 25 | // return switch (spr.frame.angle) { 26 | // case ANGLE_90: 90; 27 | // case ANGLE_270 | ANGLE_NEG_90: 270; 28 | // default: 0; // ANGLE_0d 29 | // } 30 | return cast spr.frame.angle; // We can just do this, prevents an unused case warning too! 31 | } 32 | 33 | inline public static function findEntryFrom(event:Event) { 34 | final possibleLastEvent = event.parent.getLastEventBefore(event); 35 | 36 | var entryPerc = 0.; 37 | 38 | if (possibleLastEvent != null) { 39 | final evType = possibleLastEvent.getType(); 40 | if (evType == EASE) { 41 | var castedEvent:EaseEvent = cast possibleLastEvent; 42 | entryPerc = (castedEvent.ease(1) * castedEvent.target); 43 | } else if (evType == ADD) { 44 | var castedEvent:AddEvent = cast possibleLastEvent; 45 | @:privateAccess 46 | entryPerc = (castedEvent.entryPerc + (castedEvent.ease(1) * castedEvent.addAmount)); 47 | } else { 48 | entryPerc = possibleLastEvent.target; 49 | } 50 | } else { 51 | entryPerc = event.getModPercent(event.name, event.player); 52 | } 53 | 54 | return entryPerc; 55 | } 56 | 57 | @:pure @:noDebug 58 | inline public static function rotate3DVector(vec:Vector3, angleX:Float, angleY:Float, angleZ:Float):Vector3 { 59 | if (angleX == 0 && angleY == 0 && angleZ == 0) 60 | return vec; 61 | 62 | final RAD = FlxAngle.TO_RAD; 63 | final quatX = Quaternion.fromAxisAngle(Vector3D.X_AXIS, angleX * RAD); 64 | final quatY = Quaternion.fromAxisAngle(Vector3D.Y_AXIS, angleY * RAD); 65 | final quatZ = Quaternion.fromAxisAngle(Vector3D.Z_AXIS, angleZ * RAD); 66 | 67 | // this is confusing, X_Y_Z is done like this: 68 | // OUT = Z; 69 | // OUT *= Y 70 | // OUT *= X 71 | // But it feels wrong, so investigate this 72 | switch (Config.ROTATION_ORDER) { 73 | case Z_X_Y: 74 | quatY.multiplyInPlace(quatX); 75 | quatY.multiplyInPlace(quatZ); 76 | return quatY.rotateVector(vec); 77 | case X_Y_Z: 78 | quatZ.multiplyInPlace(quatY); 79 | quatZ.multiplyInPlace(quatX); 80 | return quatZ.rotateVector(vec); 81 | case X_Z_Y: 82 | quatY.multiplyInPlace(quatZ); 83 | quatY.multiplyInPlace(quatX); 84 | return quatY.rotateVector(vec); 85 | case Y_X_Z: 86 | quatZ.multiplyInPlace(quatX); 87 | quatZ.multiplyInPlace(quatY); 88 | return quatZ.rotateVector(vec); 89 | case Y_Z_X: 90 | quatX.multiplyInPlace(quatZ); 91 | quatX.multiplyInPlace(quatY); 92 | return quatX.rotateVector(vec); 93 | case Z_Y_X: 94 | quatX.multiplyInPlace(quatY); 95 | quatX.multiplyInPlace(quatZ); 96 | return quatX.rotateVector(vec); 97 | case X_Y_X: 98 | quatX.multiplyInPlace(quatY); 99 | quatX.multiplyInPlace(quatX); 100 | return quatX.rotateVector(vec); 101 | case X_Z_X: 102 | quatX.multiplyInPlace(quatZ); 103 | quatX.multiplyInPlace(quatX); 104 | return quatX.rotateVector(vec); 105 | case Y_X_Y: 106 | quatY.multiplyInPlace(quatX); 107 | quatY.multiplyInPlace(quatY); 108 | return quatY.rotateVector(vec); 109 | case Y_Z_Y: 110 | quatY.multiplyInPlace(quatZ); 111 | quatY.multiplyInPlace(quatY); 112 | return quatY.rotateVector(vec); 113 | case Z_X_Z: 114 | quatZ.multiplyInPlace(quatX); 115 | quatZ.multiplyInPlace(quatZ); 116 | return quatZ.rotateVector(vec); 117 | case Z_Y_Z: 118 | quatZ.multiplyInPlace(quatY); 119 | quatZ.multiplyInPlace(quatZ); 120 | return quatZ.rotateVector(vec); 121 | } 122 | } 123 | 124 | inline static public function getHoldUVT(arrow:FlxSprite, subs:Int, ?vector:DrawData) { 125 | var frameAngle = -ModchartUtil.getFrameAngle(arrow); 126 | 127 | var uv:DrawData = null; 128 | 129 | if (vector != null && vector.length >= 8 * subs) 130 | uv = vector; 131 | else 132 | uv = new DrawData(8 * subs, true, []); 133 | 134 | var frameUV = arrow.frame.uv; 135 | 136 | // i do not like this 137 | // but i was suggested to do this instead by theo -swordcube 138 | var left = #if (flixel >= "6.1.0") frameUV.left #else frameUV.x #end; 139 | var right = #if (flixel >= "6.1.0") frameUV.right #else frameUV.y #end; 140 | var top = #if (flixel >= "6.1.0") frameUV.top #else frameUV.width #end; 141 | var bottom = #if (flixel >= "6.1.0") frameUV.bottom #else frameUV.height #end; 142 | 143 | var frameWidth = top - left; 144 | var frameHeight = bottom - right; 145 | 146 | var subDivided = 1.0 / subs; 147 | 148 | // if the frame doesnt have rotation, we skip the rotated uv shit 149 | if ((frameAngle % 360) == 0) { 150 | for (curSub in 0...subs) { 151 | var uvOffset = subDivided * curSub; 152 | var subIndex = curSub * 8; 153 | 154 | uv[subIndex] = uv[subIndex + 4] = left; 155 | uv[subIndex + 2] = uv[subIndex + 6] = top; 156 | uv[subIndex + 1] = uv[subIndex + 3] = right + uvOffset * frameHeight; 157 | uv[subIndex + 5] = uv[subIndex + 7] = right + (uvOffset + subDivided) * frameHeight; 158 | } 159 | return uv; 160 | } 161 | 162 | var angleRad = frameAngle * (Math.PI / 180); 163 | var cosA = ModchartUtil.cos(angleRad); 164 | var sinA = ModchartUtil.sin(angleRad); 165 | 166 | var uCenter = left + frameWidth * .5; 167 | var vCenter = right + frameHeight * .5; 168 | 169 | // my brain is not braining anymore 170 | // i give up 171 | for (curSub in 0...subs) { 172 | var uvOffset = subDivided * curSub; 173 | var subIndex = curSub * 8; 174 | 175 | // uv coords before rotation 176 | var uvCoords = [ 177 | [left, right + uvOffset * frameHeight], // tl 178 | [top, right + uvOffset * frameHeight], // tr 179 | [left, right + (uvOffset + subDivided) * frameHeight], // bl 180 | [top, right + (uvOffset + subDivided) * frameHeight] // br 181 | ]; 182 | 183 | // apply rotation 184 | for (i in 0...4) { 185 | var u = uvCoords[i][0] - uCenter; // x 186 | var v = uvCoords[i][1] - vCenter; // y 187 | 188 | var uRot = u * cosA - v * sinA; 189 | var vRot = u * sinA + v * cosA; 190 | 191 | uv[subIndex + i * 2] = uRot + uCenter; 192 | uv[subIndex + i * 2 + 1] = vRot + vCenter; 193 | } 194 | } 195 | 196 | return uv; 197 | } 198 | 199 | /** 200 | map should be. [ 201 | top left x, top left y, 202 | top right x, top right y, 203 | bot left x, bot left y 204 | bot right x, bot right y 205 | ] 206 | */ 207 | inline static public function appendUVRotation(map:Array, angle:Float) { 208 | return map; 209 | } 210 | 211 | // gonna keep this shits inline cus are basic functions 212 | 213 | public static inline function getHalfPos():Vector3 { 214 | return new Vector3(Manager.ARROW_SIZEDIV2, Manager.ARROW_SIZEDIV2, 0, 0); 215 | } 216 | 217 | @:pure 218 | public static inline function sign(x:Int) 219 | return x == 0 ? 0 : x > 0 ? 1 : -1; 220 | 221 | @:pure 222 | public static inline function clamp(n:Float, l:Float, h:Float) { 223 | if (n < l) 224 | return l; 225 | if (n > h) 226 | return h; 227 | return n; 228 | } 229 | 230 | @:pure 231 | public static inline function sin(num:Float) 232 | return FlxMath.fastSin(num); 233 | 234 | @:pure 235 | public static inline function cos(num:Float) 236 | return FlxMath.fastCos(num); 237 | 238 | @:pure 239 | public static inline function tan(num:Float) 240 | return sin(num) / cos(num); 241 | 242 | @:pure 243 | @:deprecated("Use Vector3.interpolate instead.") 244 | inline public static function lerpVector3D(start:Vector3, end:Vector3, ratio:Float) { 245 | if (ratio == 0) 246 | return start; 247 | if (ratio == 1) 248 | return end; 249 | 250 | final diff = end.subtract(start); 251 | diff.scaleBy(ratio); 252 | 253 | return start.add(diff); 254 | } 255 | 256 | public static function coolTextFile(path:String):Array { 257 | var trim:String; 258 | return [ 259 | for (line in openfl.utils.Assets.getText(path).split("\n")) 260 | if ((trim = line.trim()) != "" && !trim.startsWith("#")) trim 261 | ]; 262 | } 263 | } -------------------------------------------------------------------------------- /modchart/backend/macros/Macro.macro.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.macros; 2 | 3 | import haxe.ds.StringMap; 4 | import haxe.macro.Compiler; 5 | import haxe.macro.Context; 6 | import haxe.macro.Expr; 7 | import haxe.macro.Type.ClassField; 8 | 9 | class Macro { 10 | public static function includeFiles() { 11 | Compiler.include('modchart', true, ['modchart.backend.standalone.adapters']); 12 | Compiler.include("modchart.backend.standalone.adapters." + haxe.macro.Context.definedValue("FM_ENGINE").toLowerCase()); 13 | } 14 | 15 | public static function addModchartStorage():Array { 16 | final fields = Context.getBuildFields(); 17 | final pos = Context.currentPos(); 18 | 19 | for (f in fields) { 20 | if (f.name == 'set_visible') { 21 | switch (f.kind) { 22 | case FFun(fun): 23 | fun.expr = macro { 24 | visible = Value; 25 | _fmVisible = Value; 26 | 27 | return Value; 28 | }; 29 | default: 30 | // do nothing 31 | } 32 | } else if (f.name == 'get_visible') { 33 | switch (f.kind) { 34 | case FFun(fun): 35 | fun.expr = macro { 36 | return _fmVisible; 37 | }; 38 | default: 39 | // do nothing 40 | } 41 | } 42 | } 43 | 44 | // uses _z to prevent collisions with other classes 45 | final zField:Field = { 46 | name: "_z", 47 | access: [APublic], 48 | kind: FieldType.FVar(macro :Float, macro $v{0}), 49 | pos: pos 50 | }; 51 | final visField:Field = { 52 | name: "_fmVisible", 53 | access: [APublic], 54 | kind: FieldType.FVar(macro :Null, macro true), 55 | pos: pos 56 | }; 57 | final extraField:Field = { 58 | name: "_fmExtra", 59 | access: [APublic], 60 | kind: FieldType.FVar(macro :Dynamic, macro {}), 61 | pos: pos 62 | }; 63 | 64 | fields.push(zField); 65 | fields.push(visField); 66 | fields.push(extraField); 67 | 68 | return fields; 69 | } 70 | 71 | public static function buildFlxCamera():Array { 72 | var fields = Context.getBuildFields(); 73 | 74 | // idk why when i dont change the general draw items pooling system, theres so much graphic issues (with colors and uvs) 75 | /* 76 | var newField:Field = { 77 | name: '__fmStartTrianglesBatch', 78 | pos: Context.currentPos(), 79 | access: [APrivate], 80 | kind: FFun({ 81 | args: [ 82 | { 83 | name: "graphic", 84 | type: macro :flixel.graphics.FlxGraphic 85 | }, 86 | { 87 | name: "blend", 88 | type: macro :openfl.display.BlendMode 89 | }, 90 | { 91 | name: "shader", 92 | type: macro :flixel.system.FlxAssets.FlxShader 93 | }, 94 | { 95 | name: "antialiasing", 96 | type: macro :Bool, 97 | value: macro $v{false} 98 | } 99 | ], 100 | expr: macro { 101 | return getNewDrawTrianglesItem(graphic, antialiasing, true, blend, true, shader); 102 | }, 103 | ret: macro :flixel.graphics.tile.FlxDrawTrianglesItem 104 | }) 105 | }; 106 | fields.push(newField); 107 | */ 108 | 109 | for (f in fields) { 110 | if (f.name == 'startTrianglesBatch') { 111 | switch (f.kind) { 112 | case FFun(fun): 113 | // we're just removing a if statement cuz causes some color issues 114 | fun.expr = macro { 115 | return getNewDrawTrianglesItem(graphic, smoothing, isColored, blend #if (flixel >= "5.2.0"), hasColorOffsets, shader #end); 116 | }; 117 | default: 118 | // do nothing 119 | } 120 | } 121 | } 122 | 123 | return fields; 124 | } 125 | 126 | public static function buildFlxDrawTrianglesItem():Array { 127 | var fields = Context.getBuildFields(); 128 | var newField:Field = { 129 | name: 'addGradientTriangles', 130 | pos: Context.currentPos(), 131 | access: [APublic], 132 | kind: FieldType.FFun({ 133 | args: [ 134 | { 135 | name: 'vertices', 136 | type: macro :DrawData 137 | }, 138 | { 139 | name: 'indices', 140 | type: macro :DrawData 141 | }, 142 | { 143 | name: 'uvtData', 144 | type: macro :DrawData 145 | }, 146 | { 147 | name: 'position', 148 | type: macro :FlxPoint, 149 | opt: true 150 | }, 151 | { 152 | name: 'cameraBounds', 153 | type: macro :FlxRect, 154 | opt: true 155 | }, 156 | { 157 | name: 'transforms', 158 | type: macro :Array, 159 | opt: true 160 | } 161 | ], 162 | expr: macro { 163 | if (position == null) 164 | position = point.set(); 165 | 166 | if (cameraBounds == null) 167 | cameraBounds = rect.set(0, 0, FlxG.width, FlxG.height); 168 | 169 | var verticesLength:Int = vertices.length; 170 | var prevVerticesLength:Int = this.vertices.length; 171 | var numberOfVertices:Int = Std.int(verticesLength / 2); 172 | var prevIndicesLength:Int = this.indices.length; 173 | var prevUVTDataLength:Int = this.uvtData.length; 174 | var prevNumberOfVertices:Int = this.numVertices; 175 | 176 | var tempX:Float, tempY:Float; 177 | var i:Int = 0; 178 | var currentVertexPosition:Int = prevVerticesLength; 179 | 180 | while (i < verticesLength) { 181 | tempX = position.x + vertices[i]; 182 | tempY = position.y + vertices[i + 1]; 183 | 184 | this.vertices[currentVertexPosition++] = tempX; 185 | this.vertices[currentVertexPosition++] = tempY; 186 | 187 | if (i == 0) { 188 | bounds.set(tempX, tempY, 0, 0); 189 | } else { 190 | inflateBounds(bounds, tempX, tempY); 191 | } 192 | 193 | i = i + 2; 194 | } 195 | 196 | var indicesLength:Int = indices.length; 197 | if (!cameraBounds.overlaps(bounds)) { 198 | this.vertices.splice(this.vertices.length - verticesLength, verticesLength); 199 | } else { 200 | var uvtDataLength:Int = uvtData.length; 201 | for (i in 0...uvtDataLength) { 202 | this.uvtData[prevUVTDataLength + i] = uvtData[i]; 203 | } 204 | 205 | for (i in 0...indicesLength) { 206 | this.indices[prevIndicesLength + i] = indices[i] + prevNumberOfVertices; 207 | } 208 | 209 | verticesPosition = verticesPosition + verticesLength; 210 | indicesPosition = indicesPosition + indicesLength; 211 | } 212 | 213 | position.putWeak(); 214 | cameraBounds.putWeak(); 215 | 216 | #if (flixel >= "5.2.0") 217 | final indDiv = (1 / indicesLength); 218 | 219 | var curAlphas = []; 220 | curAlphas.resize(indicesLength); 221 | var j = 0; 222 | 223 | for (_ in 0...indicesLength) { 224 | final possibleTransform = transforms[Std.int(_ * indDiv * transforms.length)]; 225 | 226 | var alphaMultiplier = 1.; 227 | 228 | if (possibleTransform != null) 229 | alphaMultiplier = possibleTransform.alphaMultiplier; 230 | 231 | curAlphas[j++] = alphaMultiplier; 232 | } 233 | 234 | alphas = alphas.concat(curAlphas); 235 | 236 | if (colored || hasColorOffsets) { 237 | if (colorMultipliers == null) 238 | colorMultipliers = []; 239 | 240 | if (colorOffsets == null) 241 | colorOffsets = []; 242 | 243 | var curMultipliers = []; 244 | var curOffsets = []; 245 | 246 | var multCount = 0; 247 | var offCount = 0; 248 | 249 | curMultipliers.resize(indicesLength * (3 + 1)); 250 | curOffsets.resize(indicesLength * 4); 251 | 252 | for (_ in 0...indicesLength) { 253 | final transform = transforms[Std.int(_ * indDiv * transforms.length)]; 254 | if (transform != null) { 255 | curMultipliers[multCount + 0] = transform.redMultiplier; 256 | curMultipliers[multCount + 1] = transform.greenMultiplier; 257 | curMultipliers[multCount + 2] = transform.blueMultiplier; 258 | 259 | curOffsets[offCount + 0] = transform.redOffset; 260 | curOffsets[offCount + 1] = transform.greenOffset; 261 | curOffsets[offCount + 2] = transform.blueOffset; 262 | curOffsets[offCount + 3] = transform.alphaOffset; 263 | } else { 264 | curMultipliers[multCount + 0] = 1; 265 | curMultipliers[multCount + 1] = 1; 266 | curMultipliers[multCount + 2] = 1; 267 | 268 | curOffsets[offCount + 0] = 0; 269 | curOffsets[offCount + 1] = 0; 270 | curOffsets[offCount + 2] = 0; 271 | curOffsets[offCount + 3] = 0; 272 | } 273 | 274 | curMultipliers[multCount + 3] = 1; 275 | 276 | multCount = multCount + (3 + 1); 277 | offCount = offCount + 4; 278 | } 279 | 280 | colorMultipliers = colorMultipliers.concat(curMultipliers); 281 | colorOffsets = colorOffsets.concat(curOffsets); 282 | } 283 | #end 284 | } 285 | }), 286 | }; 287 | 288 | fields.push(newField); 289 | 290 | return fields; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /modchart/backend/graphics/renderers/ModchartArrowRenderer.hx: -------------------------------------------------------------------------------- 1 | package modchart.backend.graphics.renderers; 2 | 3 | final matrix:Matrix = new Matrix(); 4 | final fMatrix:FlxMatrix = new FlxMatrix(); 5 | final rotationVector = new Vector3(); 6 | final helperVector = new Vector3(); 7 | 8 | #if !openfl_debug 9 | @:fileXml('tags="haxe,release"') 10 | @:noDebug 11 | #end 12 | final class ModchartArrowRenderer extends ModchartRenderer { 13 | inline private function getGraphicVertices(planeWidth:Float, planeHeight:Float, flipX:Bool, flipY:Bool) { 14 | var x1 = flipX ? planeWidth : -planeWidth; 15 | var x2 = flipX ? -planeWidth : planeWidth; 16 | var y1 = flipY ? planeHeight : -planeHeight; 17 | var y2 = flipY ? -planeHeight : planeHeight; 18 | 19 | return [ 20 | // top left 21 | x1, 22 | y1, 23 | // top right 24 | x2, 25 | y1, 26 | // bottom left 27 | x1, 28 | y2, 29 | // bottom right 30 | x2, 31 | y2 32 | ]; 33 | } 34 | 35 | var __lastOrient:Float = 0; 36 | var __lastC2:Float = 0; 37 | var __lastPlayer:Int = -1; 38 | 39 | override public function prepare(arrow:FlxSprite) { 40 | if (arrow.alpha <= 0) 41 | return; 42 | 43 | final arrowPosition = helperVector; 44 | 45 | final player = Adapter.instance.getPlayerFromArrow(arrow); 46 | 47 | // setup the position 48 | var arrowTime = Adapter.instance.getTimeFromArrow(arrow); 49 | var songPos = Adapter.instance.getSongPosition(); 50 | var arrowDiff = arrowTime - songPos; 51 | 52 | final canUseLast = player == __lastPlayer; 53 | 54 | final centered2 = canUseLast ? __lastC2 : (__lastC2 = instance.getPercent('centered2', player)); 55 | final orient = canUseLast ? __lastOrient : (__lastOrient = instance.getPercent('orient', player)); 56 | 57 | // apply centered 2 (aka centered path) 58 | if (Adapter.instance.isTapNote(arrow)) { 59 | arrowDiff += FlxG.height * 0.25 * centered2; 60 | } else { 61 | arrowTime = songPos + (FlxG.height * 0.25 * centered2); 62 | arrowDiff = arrowTime - songPos; 63 | } 64 | var arrowData:ArrowData = { 65 | hitTime: arrowTime, 66 | distance: arrowDiff, 67 | lane: Adapter.instance.getLaneFromArrow(arrow), 68 | player: player, 69 | isTapArrow: Adapter.instance.isTapNote(arrow) 70 | }; 71 | 72 | arrowPosition.setTo(Adapter.instance.getDefaultReceptorX(arrowData.lane, arrowData.player) + Manager.ARROW_SIZEDIV2, 73 | Adapter.instance.getDefaultReceptorY(arrowData.lane, arrowData.player) + Manager.ARROW_SIZEDIV2, 0); 74 | 75 | final output = instance.modifiers.getPath(arrowPosition, arrowData); 76 | arrowPosition.copyFrom(output.pos.clone()); 77 | 78 | // internal mods 79 | if (orient != 0) { 80 | final nextOutput = instance.modifiers.getPath(new Vector3(Adapter.instance.getDefaultReceptorX(arrowData.lane, arrowData.player) 81 | + Manager.ARROW_SIZEDIV2, 82 | Adapter.instance.getDefaultReceptorY(arrowData.lane, arrowData.player) 83 | + Manager.ARROW_SIZEDIV2), 84 | arrowData, 1, false, true); 85 | final thisPos = output.pos; 86 | final nextPos = nextOutput.pos; 87 | 88 | output.visuals.angleZ += FlxAngle.wrapAngle((-90 + (Math.atan2(nextPos.y - thisPos.y, nextPos.x - thisPos.x) * FlxAngle.TO_DEG)) * orient); 89 | } 90 | 91 | __lastPlayer = player; 92 | 93 | // prepare the instruction for drawing 94 | final projectionDepth = arrowPosition.z; 95 | final depth = projectionDepth; 96 | 97 | var depthScale = 1 / depth; 98 | var planeWidth = arrow.frame.frame.width * arrow.scale.x * .5; 99 | var planeHeight = arrow.frame.frame.height * arrow.scale.y * .5; 100 | 101 | arrow._z = (depth - 1) * 1000; 102 | 103 | var planeVertices = getGraphicVertices(planeWidth, planeHeight, arrow.flipX, arrow.flipY); 104 | var projectionZ:haxe.ds.Vector = new haxe.ds.Vector(Math.ceil(planeVertices.length / 2)); 105 | 106 | var vertPointer = 0; 107 | @:privateAccess do { 108 | rotationVector.setTo(planeVertices[vertPointer], planeVertices[vertPointer + 1], 0); 109 | 110 | // The result of the vert rotation 111 | var rotation = ModchartUtil.rotate3DVector(rotationVector, output.visuals.angleX, output.visuals.angleY, 112 | ModchartUtil.getFrameAngle(arrow) + output.visuals.angleZ + arrow.angle); 113 | 114 | // apply skewness 115 | if (output.visuals.skewX != 0 || output.visuals.skewY != 0) { 116 | matrix.identity(); 117 | 118 | matrix.b = ModchartUtil.tan(output.visuals.skewY * FlxAngle.TO_RAD); 119 | matrix.c = ModchartUtil.tan(output.visuals.skewX * FlxAngle.TO_RAD); 120 | 121 | rotation.x = matrix.__transformX(rotation.x, rotation.y); 122 | rotation.y = matrix.__transformY(rotation.x, rotation.y); 123 | } 124 | rotation.x = rotation.x * depthScale * output.visuals.scaleX; 125 | rotation.y = rotation.y * depthScale * output.visuals.scaleY; 126 | 127 | var view = new Vector3(rotation.x + arrowPosition.x, rotation.y + arrowPosition.y, rotation.z); 128 | if (Config.CAMERA3D_ENABLED) 129 | view = instance.camera3D.applyViewTo(view); 130 | view.z *= 0.001 * Config.Z_SCALE; 131 | 132 | // The result of the perspective projection of rotation 133 | final projection = this.projection.transformVector(view); 134 | 135 | planeVertices[vertPointer] = projection.x; 136 | planeVertices[vertPointer + 1] = projection.y; 137 | 138 | // stores depth from this vert to use it for perspective correction on uv's 139 | projectionZ[Math.floor(vertPointer / 2)] = Math.max(0.0001, projection.z); 140 | 141 | vertPointer = vertPointer + 2; 142 | } while (vertPointer < planeVertices.length); 143 | 144 | // @formatter:off 145 | // this is confusing af 146 | var vertices = new DrawData(12, true, [ 147 | // triangle 1 148 | planeVertices[0], planeVertices[1], // top left 149 | planeVertices[2], planeVertices[3], // top right 150 | planeVertices[6], planeVertices[7], // bottom left 151 | // triangle 2 152 | planeVertices[0], planeVertices[1], // top right 153 | planeVertices[4], planeVertices[5], // top left 154 | planeVertices[6], planeVertices[7] // bottom right 155 | ]); 156 | final uvRectangle = arrow.frame.uv; 157 | var uvData = new DrawData(18, true, [ 158 | #if (flixel >= "6.1.0") 159 | // uv for triangle 1 160 | uvRectangle.left, uvRectangle.right, 1 / projectionZ[0], // top left 161 | uvRectangle.top, uvRectangle.right, 1 / projectionZ[1], // top right 162 | uvRectangle.top, uvRectangle.bottom, 1 / projectionZ[3], // bottom left 163 | // uv for triangle 2 164 | uvRectangle.left, uvRectangle.right, 1 / projectionZ[0], // top right 165 | uvRectangle.left, uvRectangle.bottom, 1 / projectionZ[2], // top left 166 | uvRectangle.top, uvRectangle.bottom, 1 / projectionZ[3] // bottom right 167 | #else 168 | // uv for triangle 1 169 | uvRectangle.x, uvRectangle.y, 1 / projectionZ[0], // top left 170 | uvRectangle.width, uvRectangle.y, 1 / projectionZ[1], // top right 171 | uvRectangle.width, uvRectangle.height, 1 / projectionZ[3], // bottom left 172 | // uv for triangle 2 173 | uvRectangle.x, uvRectangle.y, 1 / projectionZ[0], // top right 174 | uvRectangle.x, uvRectangle.height, 1 / projectionZ[2], // top left 175 | uvRectangle.width, uvRectangle.height, 1 / projectionZ[3] // bottom right 176 | #end 177 | ]); 178 | // @formatter:on 179 | final absGlow = output.visuals.glow * 255; 180 | final negGlow = 1 - output.visuals.glow; 181 | var color = new ColorTransform(negGlow, negGlow, negGlow, arrow.alpha * output.visuals.alpha, Math.round(output.visuals.glowR * absGlow), 182 | Math.round(output.visuals.glowG * absGlow), Math.round(output.visuals.glowB * absGlow)); 183 | 184 | // make the instruction 185 | var newInstruction:FMDrawInstruction = {}; 186 | newInstruction.item = arrow; 187 | newInstruction.vertices = vertices; 188 | newInstruction.uvt = uvData; 189 | newInstruction.indices = new Vector(vertices.length, true, [for (i in 0...vertices.length) i]); 190 | newInstruction.colorData = [color]; 191 | queue[count] = newInstruction; 192 | 193 | count++; 194 | } 195 | 196 | override public function shift() { 197 | __drawInstruction(queue[postCount++]); 198 | } 199 | 200 | private function __drawInstruction(instruction:FMDrawInstruction) { 201 | if (instruction == null) 202 | return; 203 | 204 | final item = instruction.item; 205 | @:privateAccess 206 | final cameras = #if (flixel >= "5.7.0") item.getCamerasLegacy() #else item.get_cameras() #end; 207 | 208 | @:privateAccess 209 | for (camera in cameras) { 210 | final cTransform = instruction.colorData[0]; 211 | cTransform.alphaMultiplier *= camera.alpha; 212 | 213 | camera.drawTriangles(item.graphic, instruction.vertices, instruction.indices, instruction.uvt, new Vector(), null, item.blend, false, 214 | item.antialiasing, cTransform, item.shader); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /modchart/engine/modifiers/ModifierGroup.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine.modifiers; 2 | 3 | import haxe.ds.IntMap; 4 | import haxe.ds.ObjectMap; 5 | import haxe.ds.StringMap; 6 | import haxe.ds.Vector; 7 | import modchart.backend.core.ArrowData; 8 | import modchart.backend.core.ModifierOutput; 9 | import modchart.backend.core.ModifierParameters; 10 | import modchart.backend.core.PercentArray; 11 | import modchart.backend.core.VisualParameters; 12 | import modchart.backend.macros.ModifiersMacro; 13 | import modchart.backend.util.ModchartUtil; 14 | import modchart.engine.modifiers.Modifier; 15 | import modchart.engine.modifiers.list.*; 16 | import modchart.engine.modifiers.list.false_paradise.*; 17 | 18 | #if !openfl_debug 19 | @:fileXml('tags="haxe,release"') 20 | @:noDebug 21 | #end 22 | @:allow(modchart.engine.Modifier) 23 | final class ModifierGroup { 24 | /** 25 | * A `List` containing all compiled `Modifier` classes. 26 | * 27 | * This list is generated at compile time using `ModifiersMacro.get()`. 28 | * It provides a collection of all available modifiers for use in the system. 29 | */ 30 | public static final COMPILED_MODIFIERS = ModifiersMacro.get(); 31 | 32 | /** 33 | * A 2D array storing percentage values, indexed by hashed string keys. 34 | * 35 | * **Usage Notes:** 36 | * - Do not access `percents` directly, as all keys are hashed into 16-bit integers. 37 | * - Use `getPercent(name, player)` to retrieve a value. 38 | * - Use `setPercent(name, value, player)` to modify values safely. 39 | * 40 | * **Hashing Mechanism:** 41 | * - Keys are automatically converted to lowercase and hashed into a 16-bit integer. 42 | * - This ensures efficient storage and retrieval while avoiding direct string key lookups. 43 | */ 44 | public var percents(default, never):PercentArray = new PercentArray(); 45 | 46 | /** 47 | * A `StringMap` that maps modifier names/identifiers to their corresponding `Modifier` class. 48 | * **Note**: This is not actually used internally- 49 | */ 50 | public var modifiers(default, never):StringMap = new StringMap(); 51 | 52 | /** 53 | * The current `PlayField` instance. 54 | * 55 | * When set, all stored modifiers are updated to reference the new `PlayField` instance. 56 | */ 57 | public var playfield(default, set):PlayField; 58 | 59 | public function set_playfield(newPlayfield:PlayField) { 60 | for (i in 0...__modifierCount) { 61 | @:privateAccess __sortedModifiers[i].pf = newPlayfield; 62 | } 63 | return playfield = newPlayfield; 64 | } 65 | 66 | @:noCompletion private var __modifierRegistrery:StringMap> = new StringMap(); 67 | 68 | @:noCompletion private var __sortedModifiers:Vector = new Vector(32); 69 | @:noCompletion private var __modifierCount:Int = 0; 70 | @:noCompletion private var __sortedIDs:Vector = new Vector(32); 71 | @:noCompletion private var __idCount:Int = 0; 72 | 73 | inline private function __loadModifiers() { 74 | for (cls in COMPILED_MODIFIERS) { 75 | var name = Type.getClassName(cls); 76 | name = name.substring(name.lastIndexOf('.') + 1, name.length); 77 | __modifierRegistrery.set(name.toLowerCase(), cast cls); 78 | } 79 | } 80 | 81 | public function new(playfield:PlayField) { 82 | this.playfield = playfield; 83 | 84 | __loadModifiers(); 85 | } 86 | 87 | public inline function postRender() {} 88 | 89 | /** 90 | * Computes the transformed position and visual properties of an arrow based on active modifiers. 91 | * 92 | * @param pos The initial `Vector3` position of the arrow. 93 | * @param data The `ArrowData` containing arrow properties such as lane, player, and timing. 94 | * @param posDiff (Optional) A positional offset applied to the arrow. 95 | * @param allowVis (Optional) If `true`, visual modifications will be applied. 96 | * @param allowPos (Optional) If `true`, positional transformations will be applied. 97 | * @return A `ModifierOutput` structure containing the modified position and visuals. 98 | * 99 | * **Processing Steps:** 100 | * - Retrieves the current song position and beat. 101 | * - Iterates through all active modifiers, applying transformations if conditions are met. 102 | * - Adjusts the `z` position based on `Config.Z_SCALE` and projects the final position. 103 | * - (Caching is currently disabled but could be re-enabled for optimization.) 104 | */ 105 | public inline function getPath(pos:Vector3, data:ArrowData, ?posDiff:Float = 0, ?allowVis:Bool = true, ?allowPos:Bool = true):ModifierOutput { 106 | var visuals:VisualParameters = {}; 107 | 108 | if (!allowVis && !allowPos) 109 | return {pos: pos, visuals: visuals}; 110 | 111 | final songPos = Adapter.instance.getSongPosition(); 112 | final beat = Adapter.instance.getCurrentBeat(); 113 | 114 | final args:ModifierParameters = { 115 | songTime: songPos, 116 | curBeat: beat, 117 | hitTime: data.hitTime + posDiff, 118 | distance: data.distance + posDiff, 119 | lane: data.lane, 120 | player: data.player, 121 | isTapArrow: data.isTapArrow 122 | } 123 | 124 | // sorta optimizations 125 | final mods = __sortedModifiers; 126 | final len = __modifierCount; 127 | 128 | for (i in 0...len) { 129 | final mod = mods[i]; 130 | 131 | if (!mod.shouldRun(args)) 132 | continue; 133 | 134 | if (allowPos) 135 | pos = mod.render(pos, args); 136 | if (allowVis) 137 | visuals = mod.visuals(visuals, args); 138 | } 139 | pos.z *= 0.001 * Config.Z_SCALE; 140 | pos = playfield.projection.transformVector(pos); 141 | final output:ModifierOutput = { 142 | pos: pos, 143 | visuals: visuals 144 | }; 145 | 146 | return output; 147 | } 148 | 149 | public inline function addScriptedModifier(name:String, instance:Modifier) 150 | __addModifier(name, instance); 151 | 152 | public inline function addModifier(name:String) { 153 | var lowerName = name.toLowerCase(); 154 | if (modifiers.exists(lowerName)) 155 | return; 156 | 157 | var modifierClass:Null> = __modifierRegistrery.get(lowerName); 158 | if (modifierClass == null) { 159 | trace('$name modifier was not found !'); 160 | 161 | return; 162 | } 163 | var newModifier = Type.createInstance(modifierClass, [playfield]); 164 | __addModifier(lowerName, newModifier); 165 | } 166 | 167 | public inline function setPercent(name:String, value:Float, player:Int = -1) { 168 | final key = name.toLowerCase(); 169 | 170 | final possiblePercs = percents.get(key); 171 | final generate = possiblePercs == null; 172 | final percs = generate ? __getPercentTemplate() : possiblePercs; 173 | 174 | if (player == -1) 175 | for (_ in 0...percs.length) 176 | percs[_] = value; 177 | else 178 | percs[player] = value; 179 | 180 | // if the percent list already was generated, we dont need to set it again 181 | if (generate) 182 | percents.set(key, percs); 183 | } 184 | 185 | public inline function getPercent(name:String, player:Int):Float { 186 | final percs = percents.get(name.toLowerCase()); 187 | 188 | if (percs != null) 189 | return percs[player]; 190 | return 0; 191 | } 192 | 193 | inline private function __getUnsafe(id:Int, player:Int) { 194 | final percs = percents.getUnsafe(id); 195 | 196 | if (percs != null) 197 | return percs[player]; 198 | return 0; 199 | } 200 | 201 | inline private function __setUnsafe(id:Int, value:Float, player:Int = -1) { 202 | var possiblePercs = percents.getUnsafe(id); 203 | var generate = possiblePercs == null; 204 | var percs = generate ? __getPercentTemplate() : possiblePercs; 205 | 206 | if (player == -1) 207 | for (_ in 0...percs.length) 208 | percs[_] = value; 209 | else 210 | percs[player] = value; 211 | 212 | if (generate) 213 | percents.setUnsafe(id, percs); 214 | } 215 | 216 | @:noCompletion 217 | inline private function __addModifier(name:String, modifier:Modifier) { 218 | modifiers.set(name, modifier); 219 | @:privateAccess modifier.pf = playfield; 220 | 221 | // update modifier identificators 222 | if (__idCount > (__sortedIDs.length - 1)) { 223 | final oldIDs = __sortedIDs.copy(); 224 | __sortedIDs = new Vector(oldIDs.length + 8); 225 | 226 | for (i in 0...oldIDs.length) 227 | __sortedIDs[i] = oldIDs[i]; 228 | } 229 | __sortedIDs[__idCount++] = name; 230 | 231 | // update modifier list 232 | if (__modifierCount > (__sortedModifiers.length - 1)) { 233 | final oldMods = __sortedModifiers.copy(); 234 | __sortedModifiers = new Vector(oldMods.length + 8); 235 | 236 | for (i in 0...oldMods.length) 237 | __sortedModifiers[i] = oldMods[i]; 238 | } 239 | __sortedModifiers[__modifierCount++] = modifier; 240 | } 241 | 242 | @:noCompletion 243 | inline private function __getPercentTemplate():Vector { 244 | final vector = new Vector(Adapter.instance.getPlayerCount()); 245 | for (i in 0...vector.length) 246 | vector[i] = 0; 247 | return vector; 248 | } 249 | 250 | inline private function __findID(str:String) { 251 | @:privateAccess percents.__hashKey(str.toLowerCase()); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /modchart/Manager.hx: -------------------------------------------------------------------------------- 1 | package modchart; 2 | 3 | import flixel.FlxBasic; 4 | import flixel.tweens.FlxEase.EaseFunction; 5 | import flixel.util.FlxSort; 6 | import haxe.ds.Vector; 7 | import modchart.backend.core.ArrowData; 8 | import modchart.backend.core.ModifierParameters; 9 | import modchart.backend.core.Node.NodeFunction; 10 | import modchart.backend.core.VisualParameters; 11 | import modchart.backend.graphics.renderers.*; 12 | import modchart.backend.util.ModchartUtil; 13 | import modchart.engine.modifiers.list.*; 14 | import modchart.events.*; 15 | import modchart.events.types.*; 16 | 17 | @:allow(modchart.backend.ModifierGroup) 18 | @:access(modchart.engine.PlayField) 19 | #if !openfl_debug 20 | @:fileXml('tags="haxe,release"') @:noDebug 21 | #end 22 | final class Manager extends FlxBasic { 23 | /** 24 | * Instance of the Manager. 25 | */ 26 | public static var instance:Manager; 27 | 28 | /** 29 | * Flag to enable or disable rendering of arrow paths. 30 | */ 31 | @:deprecated("Use `Config.RENDER_ARROW_PATHS` instead.") 32 | public var renderArrowPaths:Bool = false; 33 | 34 | /** 35 | * List of playfields managed by the Manager. 36 | */ 37 | public var playfields:Vector = new Vector(16); 38 | 39 | private var playfieldCount:Int = 0; 40 | 41 | public function new() { 42 | super(); 43 | 44 | instance = this; 45 | 46 | Adapter.init(); 47 | Adapter.instance.onModchartingInitialization(); 48 | 49 | addPlayfield(); 50 | } 51 | 52 | /** 53 | * Internal helper function to apply a function to each playfield. 54 | * 55 | * @param func The function to apply to each playfield. 56 | * @param player Optionally, the specific player to target (-1 for all). 57 | */ 58 | @:noCompletion 59 | private inline function __forEachPlayfield(func:PlayField->Void, player:Int = -1) { 60 | // If there's only one playfield or a specific player is provided, apply the function directly 61 | if (playfieldCount <= 1 || player != -1) 62 | return func(playfields[player != -1 ? player : 0]); 63 | 64 | // Otherwise, apply the function to all playfields 65 | for (i in 0...playfields.length) 66 | func(playfields[i]); 67 | } 68 | 69 | /** 70 | * Adds a modifier for all playfields or a specific one. 71 | * 72 | * @param name The name of the modifier. 73 | * @param field Optionally, the specific playfield to target. 74 | */ 75 | public inline function addModifier(name:String, field:Int = -1) 76 | __forEachPlayfield((pf) -> pf.addModifier(name), field); 77 | 78 | /** 79 | * Adds a scripted modifier for all playfields or a specific one. 80 | * 81 | * @param name The name of the modifier. 82 | * @param instance The instance of the modifier. 83 | * @param field Optionally, the specific playfield to target. 84 | */ 85 | public inline function addScriptedModifier(name:String, instance:Modifier, field:Int = -1) 86 | __forEachPlayfield((pf) -> pf.addScriptedModifier(name, instance), field); 87 | 88 | /** 89 | * Sets the percent for a specific modifier for all playfields or a specific one. 90 | * 91 | * @param name The name of the modifier. 92 | * @param value The percent value to set. 93 | * @param player Optionally, the player to target. 94 | * @param field Optionally, the specific playfield to target. 95 | */ 96 | public inline function setPercent(name:String, value:Float, player:Int = -1, field:Int = -1) 97 | __forEachPlayfield((pf) -> pf.setPercent(name, value, player), field); 98 | 99 | /** 100 | * Gets the percent for a specific modifier. 101 | * 102 | * @param name The name of the modifier. 103 | * @param player The player to target. 104 | * @param field Optionally, the specific playfield to target. 105 | * @return The percent value for the modifier. 106 | */ 107 | public inline function getPercent(name:String, player:Int = 0, field:Int = 0):Float { 108 | final possiblePlayfield = playfields[field]; 109 | 110 | if (possiblePlayfield != null) 111 | return possiblePlayfield.getPercent(name, player); 112 | 113 | return 0.; 114 | } 115 | 116 | /** 117 | * Adds an event to all playfields or a specific one. 118 | * 119 | * @param event The event to add. 120 | * @param field Optionally, the specific playfield to target. 121 | */ 122 | public inline function addEvent(event:Event, field:Int = -1) 123 | __forEachPlayfield((pf) -> pf.addEvent(event), field); 124 | 125 | /** 126 | * Sets a specific value at a certain beat for all playfields or a specific one. 127 | * 128 | * @param name The name of the value. 129 | * @param beat The beat at which the value should be set. 130 | * @param value The value to set. 131 | * @param player Optionally, the player to target. 132 | * @param field Optionally, the specific playfield to target. 133 | */ 134 | public inline function set(name:String, beat:Float, value:Float, player:Int = -1, field:Int = -1) 135 | __forEachPlayfield((pf) -> pf.set(name, beat, value, player), field); 136 | 137 | /** 138 | * Applies easing to a modifier. 139 | * 140 | * @param name The name of the modifier. 141 | * @param beat The beat at which to start easing. 142 | * @param length The length of the easing. 143 | * @param value The final value after easing. 144 | * @param easeFunc The easing function to use. 145 | * @param player Optionally, the player to target. 146 | * @param field Optionally, the specific playfield to target. 147 | */ 148 | public inline function ease(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1, field:Int = -1) 149 | __forEachPlayfield((pf) -> pf.ease(name, beat, length, value, easeFunc, player), field); 150 | 151 | /** 152 | * Adds easing to a modifier. 153 | * 154 | * @param name The name of the modifier. 155 | * @param beat The beat at which to start easing. 156 | * @param length The length of the easing. 157 | * @param value The value to apply after easing. 158 | * @param easeFunc The easing function to use. 159 | * @param player Optionally, the player to target. 160 | * @param field Optionally, the specific playfield to target. 161 | */ 162 | public inline function add(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1, field:Int = -1) 163 | __forEachPlayfield((pf) -> pf.add(name, beat, length, value, easeFunc, player), field); 164 | 165 | /** 166 | * Sets and adds a value to a modifier. 167 | * 168 | * @param name The name of the modifier. 169 | * @param beat The beat at which the value should be set. 170 | * @param value The value to set. 171 | * @param player Optionally, the player to target. 172 | * @param field Optionally, the specific playfield to target. 173 | */ 174 | public inline function setAdd(name:String, beat:Float, value:Float, player:Int = -1, field:Int = -1) 175 | __forEachPlayfield((pf) -> pf.setAdd(name, beat, value, player), field); 176 | 177 | /** 178 | * Adds a repeater event for all playfields or a specific one. 179 | * 180 | * @param beat The beat at which the repeater starts. 181 | * @param length The length of the repeat action. 182 | * @param callback The callback function to execute. 183 | * @param field Optionally, the specific playfield to target. 184 | */ 185 | public inline function repeater(beat:Float, length:Float, callback:Event->Void, field:Int = -1) 186 | __forEachPlayfield((pf) -> pf.repeater(beat, length, callback), field); 187 | 188 | /** 189 | * Adds a callback event for all playfields or a specific one. 190 | * 191 | * @param beat The beat at which the callback will be triggered. 192 | * @param callback The callback function to execute. 193 | * @param field Optionally, the specific playfield to target. 194 | */ 195 | public inline function callback(beat:Float, callback:Event->Void, field:Int = -1) 196 | __forEachPlayfield((pf) -> pf.callback(beat, callback), field); 197 | 198 | /** 199 | * Creates a node linking inputs and outputs to a function. 200 | * 201 | * @param input The list of input names. 202 | * @param output The list of output names. 203 | * @param func The function to execute for the node. 204 | * @param field Optionally, the specific playfield to target. 205 | */ 206 | public inline function node(input:Array, output:Array, func:NodeFunction, field:Int = -1) 207 | __forEachPlayfield((pf) -> pf.node(input, output, func), field); 208 | 209 | /** 210 | * Creates an alias for a given modifier. 211 | * 212 | * @param name The original modifier name. 213 | * @param alias The alias name. 214 | * @param field The specific playfield to apply the alias to. 215 | */ 216 | public inline function alias(name:String, alias:String, field:Int) 217 | __forEachPlayfield((pf) -> pf.alias(name, alias), field); 218 | 219 | /** 220 | * Adds a new playfield to the Manager. 221 | */ 222 | public inline function addPlayfield() 223 | playfields[playfieldCount++] = new PlayField(); 224 | 225 | /** 226 | * Updates all playfields in the game loop. 227 | * 228 | * @param elapsed The time elapsed since the last update. 229 | */ 230 | override function update(elapsed:Float):Void { 231 | super.update(elapsed); 232 | 233 | __forEachPlayfield(pf -> pf.update(elapsed)); 234 | } 235 | 236 | /** 237 | * Draws all playfields, sorting them by z-order before drawing. 238 | */ 239 | override function draw():Void { 240 | var total = 0; 241 | __forEachPlayfield(pf -> { 242 | pf.draw(); 243 | total += pf.drawCB.length; 244 | }); 245 | 246 | var drawQueue:Vector = new Vector(total); 247 | 248 | var j = 0; 249 | __forEachPlayfield(pf -> { 250 | for (x in pf.drawCB) 251 | drawQueue[j++] = x; 252 | }); 253 | 254 | drawQueue.sort((a, b) -> { 255 | return FlxSort.byValues(FlxSort.DESCENDING, a.z, b.z); 256 | }); 257 | 258 | for (item in drawQueue) 259 | item.callback(); 260 | } 261 | 262 | /** 263 | * Destroys all playfields and cleans up. 264 | */ 265 | override function destroy():Void { 266 | super.destroy(); 267 | 268 | __forEachPlayfield(pf -> { 269 | pf.destroy(); 270 | }); 271 | } 272 | 273 | // Constants for hold and arrow sizes 274 | public static var HOLD_SIZE:Float = 50 * 0.7; 275 | public static var HOLD_SIZEDIV2:Float = (50 * 0.7) * 0.5; 276 | public static var ARROW_SIZE:Float = 160 * 0.7; 277 | public static var ARROW_SIZEDIV2:Float = (160 * 0.7) * 0.5; 278 | } 279 | 280 | typedef Funny = {callback:Void->Void, z:Float}; 281 | -------------------------------------------------------------------------------- /modchart/engine/PlayField.hx: -------------------------------------------------------------------------------- 1 | package modchart.engine; 2 | 3 | import flixel.FlxBasic; 4 | import flixel.FlxCamera; 5 | import flixel.FlxG; 6 | import flixel.FlxSprite; 7 | import flixel.tweens.FlxEase.EaseFunction; 8 | import modchart.backend.core.Node.NodeFunction; 9 | import modchart.backend.graphics.*; 10 | import modchart.backend.graphics.renderers.*; 11 | import modchart.backend.util.ModchartUtil; 12 | import modchart.engine.events.types.*; 13 | import openfl.display.BitmapData; 14 | import openfl.geom.Rectangle; 15 | 16 | // TODO: make this extend to flxsprite and use parented transformation matrix 17 | #if !openfl_debug 18 | @:fileXml('tags="haxe,release"') 19 | @:noDebug 20 | #end 21 | final class PlayField extends FlxSprite { 22 | public var events:EventManager; 23 | public var modifiers:ModifierGroup; 24 | public var camera3D:ModchartCamera3D; 25 | 26 | private var arrowRenderer:ModchartArrowRenderer; 27 | private var receptorRenderer:ModchartArrowRenderer; 28 | private var attachmentRenderer:ModchartArrowRenderer; 29 | private var holdRenderer:ModchartHoldRenderer; 30 | private var pathRenderer:ModchartPathRenderer; 31 | 32 | public var projection:ModchartPerspective; 33 | 34 | public function new() { 35 | super(); 36 | 37 | moves = false; 38 | 39 | this.events = new EventManager(this); 40 | this.modifiers = new ModifierGroup(this); 41 | 42 | arrowRenderer = new ModchartArrowRenderer(this); 43 | receptorRenderer = new ModchartArrowRenderer(this); 44 | attachmentRenderer = new ModchartArrowRenderer(this); 45 | holdRenderer = new ModchartHoldRenderer(this); 46 | pathRenderer = new ModchartPathRenderer(this); 47 | 48 | camera3D = new ModchartCamera3D(); 49 | projection = new ModchartPerspective(); 50 | 51 | // default mods 52 | addModifier('reverse'); 53 | addModifier('confusion'); 54 | addModifier('stealth'); 55 | addModifier('skew'); 56 | addModifier('zoom'); 57 | 58 | setPercent('arrowPathAlpha', 1, -1); 59 | setPercent('arrowPathThickness', 2, -1); 60 | setPercent('rotateHoldY', 1, -1); 61 | } 62 | 63 | public inline function setPercent(name:String, value:Float, player:Int = -1) 64 | return modifiers.setPercent(name, value, player); 65 | 66 | public inline function getPercent(name:String, player:Int) 67 | return modifiers.getPercent(name, player); 68 | 69 | public inline function addModifier(name:String) 70 | return modifiers.addModifier(name); 71 | 72 | public inline function addScriptedModifier(name:String, instance:Modifier) 73 | return modifiers.addScriptedModifier(name, instance); 74 | 75 | public inline function addEvent(event:Event) { 76 | events.add(event); 77 | } 78 | 79 | public inline function set(name:String, beat:Float, value:Float, player:Int = -1):Void { 80 | if (player == -1) { 81 | for (curField in 0...Adapter.instance.getPlayerCount()) 82 | set(name, beat, value, curField); 83 | return; 84 | } 85 | 86 | addEvent(new SetEvent(name.toLowerCase(), beat, value, player, events)); 87 | } 88 | 89 | public inline function ease(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1):Void { 90 | if (player == -1) { 91 | for (curField in 0...Adapter.instance.getPlayerCount()) 92 | ease(name, beat, length, value, easeFunc, curField); 93 | return; 94 | } 95 | 96 | addEvent(new EaseEvent(name, beat, length, value, easeFunc, player, events)); 97 | } 98 | 99 | public inline function add(name:String, beat:Float, length:Float, addition:Float = 1, easeFunc:EaseFunction, player:Int = -1):Void { 100 | if (player == -1) { 101 | for (curField in 0...Adapter.instance.getPlayerCount()) 102 | add(name, beat, length, addition, easeFunc, curField); 103 | return; 104 | } 105 | 106 | addEvent(new AddEvent(name, beat, length, addition, easeFunc, player, events)); 107 | } 108 | 109 | public inline function setAdd(name:String, beat:Float, valueToAdd:Float, player:Int = -1):Void { 110 | var addition = getPercent(name, player == -1 ? 0 : player); 111 | var value = addition + valueToAdd; 112 | if (player == -1) { 113 | for (curField in 0...Adapter.instance.getPlayerCount()) 114 | set(name, beat, value, curField); 115 | return; 116 | } 117 | 118 | addEvent(new SetEvent(name.toLowerCase(), beat, value, player, events)); 119 | } 120 | 121 | public inline function repeater(beat:Float, length:Float, callback:Event->Void):Void 122 | addEvent(new RepeaterEvent(beat, length, callback, events)); 123 | 124 | public inline function callback(beat:Float, callback:Event->Void):Void 125 | addEvent(new Event(beat, callback, events)); 126 | 127 | public inline function alias(name:String, alias:String) { 128 | aliases.push({ 129 | parent: name, 130 | alias: alias 131 | }); 132 | } 133 | 134 | private var aliases:Array = []; 135 | private var nodes:Array = []; 136 | 137 | /** 138 | * Register a node. 139 | * @param input Input Aux Mods 140 | * @param output Output Mods 141 | * @param func Processor function, Array -> Array 142 | */ 143 | public inline function node(input:Array, output:Array, func:NodeFunction) { 144 | nodes.push({ 145 | input: input, 146 | output: output, 147 | func: func 148 | }); 149 | } 150 | 151 | // EXPERIMENTAL 152 | // FIXME 153 | // Warning: If a node has 'drunk' by example in his output 154 | // and u made a ease on drunk and u made a ease on the node 155 | // input, the eases may overlap, causing visuals issues. 156 | public function updateNodes() { 157 | for (player in 0...Adapter.instance.getPlayerCount()) { 158 | final it = nodes.iterator(); 159 | final n = it.next; 160 | final h = it.hasNext; 161 | do { 162 | final node = n(); 163 | if (node == null) 164 | continue; 165 | 166 | var entryPercs = []; 167 | var outPercs = []; 168 | entryPercs.resize(node.input.length); 169 | 170 | for (i in 0...entryPercs.length) 171 | entryPercs[i] = getPercent(node.input[i], player); 172 | 173 | outPercs = node.func(entryPercs, player); 174 | 175 | final nbl = node.output.length; 176 | if (outPercs == null || outPercs.length < 0) 177 | outPercs = []; 178 | 179 | for (i in 0...nbl) { 180 | final prc = outPercs[i]; 181 | 182 | if (!Math.isNaN(prc) && prc != 0) 183 | setPercent(node.output[i], prc, player); 184 | } 185 | } while (h()); 186 | } 187 | } 188 | 189 | override function update(elapsed:Float):Void { 190 | // Update Event Timeline 191 | events.update(Adapter.instance.getCurrentBeat()); 192 | 193 | updateNodes(); 194 | 195 | super.update(elapsed); 196 | } 197 | 198 | override public function draw() { 199 | __drawPlayField(); 200 | modifiers.postRender(); 201 | } 202 | 203 | override public function destroy() { 204 | arrowRenderer.dispose(); 205 | holdRenderer.dispose(); 206 | receptorRenderer.dispose(); 207 | attachmentRenderer.dispose(); 208 | pathRenderer.dispose(); 209 | super.destroy(); 210 | } 211 | 212 | var drawCB:Array<{callback:Void->Void, z:Float}> = []; 213 | 214 | private function getVisibility(obj:flixel.FlxObject) { 215 | @:bypassAccessor obj.visible = false; 216 | return obj._fmVisible; 217 | } 218 | 219 | private function __drawPlayField() { 220 | drawCB = []; 221 | 222 | // TODO: prepare arrow paths shit 223 | var pathAlphaTotal = .0; 224 | 225 | var playerItems:Array>> = Adapter.instance.getArrowItems(); 226 | 227 | // used for preallocate 228 | var receptorLength = 0; 229 | var arrowLength = 0; 230 | var holdLength = 0; 231 | var attachmentLength = 0; 232 | 233 | for (i in 0...playerItems.length) { 234 | final curItems = playerItems[i]; 235 | 236 | if (curItems[0] != null) 237 | receptorLength = receptorLength + curItems[0].length; 238 | if (curItems[1] != null) 239 | arrowLength = arrowLength + curItems[1].length; 240 | if (curItems[2] != null) 241 | holdLength = holdLength + curItems[2].length; 242 | if (curItems[3] != null) 243 | attachmentLength = attachmentLength + curItems[3].length; 244 | } 245 | 246 | if (receptorLength != 0) 247 | receptorRenderer.preallocate(receptorLength); 248 | if (arrowLength != 0) 249 | arrowRenderer.preallocate(arrowLength); 250 | if (holdLength != 0) 251 | holdRenderer.preallocate(holdLength); 252 | if (attachmentLength != 0) 253 | attachmentRenderer.preallocate(attachmentLength); 254 | 255 | if (Config.RENDER_ARROW_PATHS) 256 | pathRenderer.preallocate(receptorLength); 257 | 258 | drawCB.resize(receptorLength + arrowLength + holdLength + attachmentLength); 259 | 260 | var j = 0; 261 | inline function queue(f:{callback:Void->Void, z:Float}) { 262 | drawCB[j] = f; 263 | j++; 264 | } 265 | 266 | // i is player index 267 | for (i in 0...playerItems.length) { 268 | var curItems:Array> = playerItems[i]; 269 | 270 | if (curItems == null || curItems.length == 0) 271 | continue; 272 | 273 | final drawHolds = () -> { 274 | if (holdLength > 0) { 275 | for (hold in curItems[2]) { 276 | if (!getVisibility(hold)) 277 | continue; 278 | 279 | holdRenderer.prepare(hold); 280 | queue({ 281 | callback: holdRenderer.shift, 282 | z: hold._z 283 | }); 284 | } 285 | } 286 | }; 287 | 288 | // holds (behind strums) 289 | if (Config.HOLDS_BEHIND_STRUM) 290 | drawHolds(); 291 | 292 | // receptors 293 | if (receptorLength > 0) { 294 | for (receptor in curItems[0]) { 295 | if (!getVisibility(receptor)) 296 | continue; 297 | 298 | receptorRenderer.prepare(receptor); 299 | if (Config.RENDER_ARROW_PATHS) 300 | pathRenderer.prepare(receptor); 301 | queue({ 302 | callback: receptorRenderer.shift, 303 | z: receptor._z 304 | }); 305 | } 306 | } 307 | 308 | // holds (infront of strums) 309 | if (!Config.HOLDS_BEHIND_STRUM) 310 | drawHolds(); 311 | 312 | // tap arrow 313 | if (arrowLength > 0) { 314 | for (arrow in curItems[1]) { 315 | if (!getVisibility(arrow)) 316 | continue; 317 | 318 | arrowRenderer.prepare(arrow); 319 | queue({ 320 | callback: arrowRenderer.shift, 321 | z: arrow._z 322 | }); 323 | } 324 | } 325 | 326 | // attachments (splashes) 327 | if (attachmentLength > 0) { 328 | for (attachment in curItems[3]) { 329 | if (!getVisibility(attachment)) 330 | continue; 331 | 332 | attachmentRenderer.prepare(attachment); 333 | queue({ 334 | callback: attachmentRenderer.shift, 335 | z: attachment._z 336 | }); 337 | } 338 | } 339 | } 340 | 341 | for (r in [receptorRenderer, arrowRenderer, holdRenderer, attachmentRenderer]) 342 | r.sort(); 343 | 344 | if (Config.RENDER_ARROW_PATHS) 345 | pathRenderer.shift(); 346 | } 347 | } 348 | --------------------------------------------------------------------------------