├── 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 |

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 |
--------------------------------------------------------------------------------