├── .gitignore
├── .vscode
└── settings.json
├── CHANGELOG.md
├── DOC.md
├── README.md
├── SUPPORT.md
├── TODO.md
├── extraParams.hxml
├── haxelib.json
└── modchart
├── Config.hx
├── Manager.hx
├── Modifier.hx
├── core
├── ModifierGroup.hx
├── PlayField.hx
├── Quanterion.hx
├── environments
│ ├── Andromeda.hx
│ ├── IEnvironment.hx
│ └── ModchartingTools.hx
├── graphics
│ ├── ModchartCamera3D.hx
│ └── ModchartGraphics.hx
├── macros
│ └── Macro.macro.hx
└── util
│ ├── Constants.hx
│ └── ModchartUtil.hx
├── events
├── Event.hx
├── EventManager.hx
└── types
│ ├── AddEvent.hx
│ ├── EaseEvent.hx
│ ├── RepeaterEvent.hx
│ └── SetEvent.hx
├── import.hx
├── modifiers
├── ArrowShape.hx
├── Beat.hx
├── Boost.hx
├── Bounce.hx
├── Bumpy.hx
├── CenterRotate.hx
├── Confusion.hx
├── Drugged.hx
├── Drunk.hx
├── FieldRotate.hx
├── Infinite.hx
├── Invert.hx
├── OpponentSwap.hx
├── PathModifier.hx
├── Radionic.hx
├── ReceptorScroll.hx
├── Reverse.hx
├── Rotate.hx
├── SawTooth.hx
├── Scale.hx
├── SchmovinDrunk.hx
├── SchmovinTipsy.hx
├── SchmovinTornado.hx
├── Skew.hx
├── Square.hx
├── Stealth.hx
├── Tipsy.hx
├── Tornado.hx
├── Transform.hx
├── ZigZag.hx
├── Zoom.hx
└── false_paradise
│ ├── CounterClockWise.hx
│ ├── EyeShape.hx
│ ├── SchmovinArrowShape.hx
│ ├── Spiral.hx
│ ├── Vibrate.hx
│ └── Wiggle.hx
└── standalone
├── Adapter.hx
├── IAdapter.hx
└── adapters
├── codename
└── Codename.hx
├── fpsplus
└── Fpsplus.hx
└── psych
└── Psych.hx
/.gitignore:
--------------------------------------------------------------------------------
1 | .git
2 |
3 | # currently on dev
4 | .modchart/environments/*
5 | .modchart/core/IEnvironment.hx
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[haxe]": {
3 | "editor.formatOnSave": true,
4 | "editor.codeActionsOnSave": {
5 | "source.organizeImports": "always"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 8/10/21
2 | - Hold note quad rework (from Schmovin')
3 | - Added modifiers (PathModifier, Bumpy, Infinite)
4 | - Fixed note positions
5 | - Improved some code
6 | - Fixed hold size (+= 6)
7 |
8 | ## 8/10/21 (2)
9 | - Hold scale fix
10 |
11 | ## 12/10/24
12 | - Hold graphic subdivition system (from Schmovin')
13 | - A subdivision system was already in, but hold notes were literally splited, causing you to gain or lose more heatlh (plus it caused more lag, as well as causing visual issues with texture)
14 | - Also because of the new subdivision system in hold notes, now to implement this library in cne it is no longer necessary to change anything within the source code.
15 | - Fixed hold notes position and scale when scaling by Z or Zoom.
16 | - Code improvements
17 | - Optimization.
18 |
19 | ## 12/10/24 (2)
20 | - Schmovin Drunk & Tipsy Math (false paradise recreation upcoming >:3)
21 |
22 | ## 12/10/24 (3)
23 | - Fix broken hold spacing when bpm changes
24 |
25 | # 15/10/24
26 | - Custom mods examples
27 | - Modchart Examples
28 | - More stuff im forgeting
29 | - False paradise stuff... modchart still wip !! some modifiers are not working, dont play it yet
30 |
31 | # 24/10/24
32 | - Improved Infinite Modifier
33 | - Optimization and code improvement
34 | - Bounce Mod
35 |
36 | # 31/10/24
37 | - Changed List to Array in ModchartGroup (for better performance)
38 | - Added arrow paths (need to enable by Manager.renderArrowPaths = true)
39 | - Arrow path Sub mods
40 | - Alpha
41 | - Thickness
42 | - Scale (Length / Limit)
43 |
44 | # 3/11/24
45 | - Fixed critical memory leak in the arrow path renderer (it went from 70MB to more than 4GB in a very short time).
46 | - Optimized a bit the arrow path renderer.
47 | - Added X mod
48 |
49 | # 5/11/24
50 | - New Optimized Path Manager written by Me
51 | - Small code improvements
52 |
53 | # 06/12/24
54 | - 3D Rotation for regular notes (also holds)
55 | - AngleX, AngleY, AngleZ now are visuals components.
56 | - Custom 3D Camera (Matrix3D)
57 |
58 | # 08/12/24
59 | - Skew Mods
60 | - Stealth mods (alpha, glows) now are smoother on holds (depending of your hold subdivition).
61 |
62 | # 17/12/24
63 | - Centered2 (also known as centered path)
64 | - Improvements
65 |
66 | # 31/12/24
67 | - Tornado Mod (from OpenITG)
68 | - Hold Rotation can be cancelled (via rotateHoldX, rotateHoldY, rotateHoldZ, can be 0-1)
69 | - Better Readme
70 | - Improvements
71 |
72 | # 4/01/25
73 | - Moved renderers from Manager.hx to separate classes (ModchartGraphics.hx)
74 | - Cleaned and improved a lot of code
75 |
76 | # 06/01/25 (penultimate commit)
77 | - Multiple Playfield support (each one can have his own modifiers and percents)
78 | - Plugin-based Standalone System (TESTING PHASE, NOT FINISHED)
79 | - No more rewriting of any flixel class, now all code is added using Macros.
80 |
81 | # 06/01/25 #2
82 | - Fixed arrow animation issue
83 |
84 | -- Many changes were not indexed here --
85 |
86 | # 11/02/2025
87 | - New 3D Camera (using View Matrix)
88 | - Fix Projection on Arrow rotation. (Perspective correction)
89 | - Switching from Euler Angles to Quanterions.
90 | - Fix typos and refactoring.
91 | - Huge optimizations.
92 |
93 | # 12/02/25
94 | - Fixed 3D Camera Offsets (was breaking some stuff)
95 | - Optimized `ModifierGroup` percent management (now uses `haxe.ds.Vector` instead of `IntMap`)
96 | - Optimized a lot of stuff (added preallocation on most stuff possible)
97 | - Fixed `Rotate` (base) modifier (the rotation origin was 'wrong')
98 | - Fixed a oopsie i did (X Rotation was not applied cus a mistake i did lol)
99 | - Addition on `Codename` adapter: Now it read the strum real position (so u can position them without using modchart things)
100 |
101 | # 13/02/25
102 | - Added properly support for Sprite Sheet Packed (i just added frame angles lol)
103 | - New Adapter: FPS Plus
104 |
105 | # 18/08/25
106 | - Tons of optimization and code improvements
107 | - Added longHolds modifier
108 | - Z-Sorting was Fixed
109 |
110 | # 18/08/25 #2
111 | - Fix receptor, arrow, hold draw order.
--------------------------------------------------------------------------------
/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 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
FunkinModchart
3 | An Modcharting backend library for Friday Night Funkin' made by modders, to modders.
4 |
5 |
6 | **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), 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**!.
7 |
8 | 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)*
9 |
10 |
11 | Importing the library
12 |
13 | This library currently has support for multiple engines such as [Codename Engine](https://codename-engine.com), [Psych Engine](https://github.com/ShadowMario/FNF-PsychEngine.com) and [FPS Plus](https://github.com/ThatRozebudDude/FPS-Plus-Public) click [here](SUPPORT.md) for more information, and only takes a couple of lines of code to import it:
14 |
15 | #### Go to your project and open `Project.xml`
16 | At the bottom of where the haxelib section is, paste this code.
17 | ```xml
18 |
19 |
20 |
21 |
22 |
23 | ```
24 |
25 | Fill in the definitions with your engine name and version using the [format](SUPPORT.md) mentioned.
26 |
27 | And if you did everything good, it should compile and work normal !
28 |
29 |
30 |
31 |
32 | Using the library
33 |
34 | This is the easiest thing, you only have to do a couple of steps for add the modchart instance to a song.
35 |
36 | #### Import `modchart.Manager`
37 | And then make an instance of it, and add it to the state.
38 |
39 | ```haxe
40 | var funkin_modchart_instance:Manager = new Manager();
41 | // On your create function.
42 | add(funkin_modchart_instance);
43 | ```
44 |
45 | This can be done via haxe scripts or source code, and will soon be possible in PsychLua for `PSYCH` as well.
46 |
47 | Make sure that at the time you create the instance, the notes and strums were already generated.
48 | This now all the stuff should be working, do your stuff now.
49 |
50 | #### Making a Modchart
51 | First, you should know all the modcharting functions, check they [here](DOC.md).
52 | 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.
53 |
54 |
55 |
56 |
57 | Making your own Adapter
58 |
59 | to do heheheh, if u know coding just check psych and codename adapters and u'll figure out (also check Adapter.hx and AdapterMacro.hx for more information)
60 | also OBVIOUSLY has to be an flixel-based fnf engine
61 |
62 |
63 |
64 | ## Credits
65 | **TheoDev**: Owner, Lead coder.
66 |
67 | **Ne_Eo (aka. Neo)**: Coder, bugfixes & Optimizer.
68 |
69 | **Edwhak**: Maintainer.
70 |
71 | **OpenITG:** Some math taken for modifiers.
72 |
73 | **4mbr0s3:** Some code taken from [Schmovin'](https://github.com/4mbr0s3-2/Schmovin), his own Modcharting Lib. (really impressive)
74 |
75 | ## Special Thanks
76 |
77 | **lunarcleint:** Support, such a nice guy!
78 |
79 | **Tsaku:** Support, bug finder. (thanks !!!)
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | ## Current supported FNF engines/frameworks
2 |
3 | ## Codename Engine
4 | - Beta *last tested commit: 94db415*
5 |
6 | - `FM_ENGINE`: "CODENAME"
7 | - `FM_ENGINE_VERSION`: ""
8 | ## Psych Engine
9 | - 1.0 (also 1.0b)
10 | - 0.7.x
11 | - 0.6.x
12 |
13 | - `FM_ENGINE`: "PSYCH"
14 | - `FM_ENGINE_VERSION`: "1.0" for 1.0 and 1.0b, "0.7" for 0.7.x, "0.6" for 0.6.x
15 | ## FPS Plus
16 | - 6.x
17 |
18 | - `FM_ENGINE`: "FPSPLUS"
19 | - `FM_ENGINE_VERSION`: ""
--------------------------------------------------------------------------------
/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 | ### ModchartGraphics
10 | - [X] Make the `visible` property being used (sprite.exists | alive)
11 | - [ ] Fix proxy renderer
12 |
13 | ### ModchartEnvironment (new class)
14 | - [ ] 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)
--------------------------------------------------------------------------------
/extraParams.hxml:
--------------------------------------------------------------------------------
1 | --macro addMetadata('@:build(modchart.core.macros.Macro.buildFlxCamera())', 'flixel.FlxCamera')
2 | --macro addMetadata('@:build(modchart.core.macros.Macro.buildFlxDrawTrianglesItem())', 'flixel.graphics.tile.FlxDrawTrianglesItem')
3 | --macro addMetadata('@:build(modchart.core.macros.Macro.addModchartStorage())', 'flixel.FlxObject')
--------------------------------------------------------------------------------
/haxelib.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "funkin-modchart",
3 | "license": "Apache",
4 | "description": "An modcharting plugin for Friday Night Funkin'",
5 | "version": "1.2.0",
6 | "releasenote": "FPS Plus Support, Optimizations and Improvements",
7 | "contributors": ["TheoDev"],
8 | "tags": ["fnf", "friday-night-funkin"],
9 | "dependencies": {
10 | "flixel": ""
11 | },
12 | "url": "https://github.com/TheoDevelops/FunkinModchart"
13 | }
--------------------------------------------------------------------------------
/modchart/Config.hx:
--------------------------------------------------------------------------------
1 | package modchart;
2 |
3 | class Config {
4 | /**
5 | * Set to false to disable 3d cameras (it also can improve performance)
6 | */
7 | public static var CAMERA3D_ENABLED:Bool = true;
8 |
9 | /**
10 | * Rotation Axis Order
11 | *
12 | * `Z_Y_X` by default.
13 | */
14 | public static var ROTATION_ORDER:RotationOrder = Z_Y_X;
15 | }
16 |
--------------------------------------------------------------------------------
/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.ArraySort;
7 | import modchart.core.ModifierGroup;
8 | import modchart.core.PlayField;
9 | import modchart.core.graphics.ModchartGraphics.ModchartArrowPath;
10 | import modchart.core.graphics.ModchartGraphics.ModchartArrowRenderer;
11 | import modchart.core.graphics.ModchartGraphics.ModchartHoldRenderer;
12 | import modchart.core.graphics.ModchartGraphics.ModchartRenderer;
13 | import modchart.core.util.Constants.ArrowData;
14 | import modchart.core.util.Constants.RenderParams;
15 | import modchart.core.util.Constants.Visuals;
16 | import modchart.core.util.ModchartUtil;
17 | import modchart.events.*;
18 | import modchart.events.types.*;
19 | import modchart.modifiers.*;
20 |
21 | @:allow(modchart.core.ModifierGroup)
22 | @:allow(modchart.core.graphics.ModchartGraphics)
23 | @:access(modchart.core.PlayField)
24 | class Manager extends FlxBasic {
25 | public static var instance:Manager;
26 |
27 | // turn on if u wanna arrow paths
28 | public var renderArrowPaths:Bool = false;
29 |
30 | public var playfields:Array = [];
31 |
32 | public function new() {
33 | super();
34 |
35 | instance = this;
36 |
37 | Adapter.init();
38 | Adapter.instance.onModchartingInitialization();
39 |
40 | addPlayfield();
41 | }
42 |
43 | @:noCompletion
44 | private inline function __forEachPlayfield(func:PlayField->Void, player:Int = -1) {
45 | if (player != -1)
46 | return func(playfields[player]);
47 | else
48 | for (pf in playfields)
49 | func(pf);
50 | }
51 |
52 | public inline function registerModifier(name:String, mod:Class, player:Int = -1)
53 | __forEachPlayfield((pf) -> pf.registerModifier(name, mod), player);
54 |
55 | public inline function addModifier(name:String, field:Int = -1)
56 | __forEachPlayfield((pf) -> pf.addModifier(name), field);
57 |
58 | public inline function setPercent(name:String, value:Float, player:Int = -1, field:Int = -1)
59 | __forEachPlayfield((pf) -> pf.setPercent(name, value, player), field);
60 |
61 | public inline function getPercent(name:String, player:Int = 0, field:Int = 0):Float {
62 | final possiblePlayfield = playfields[field];
63 |
64 | if (possiblePlayfield != null)
65 | return possiblePlayfield.getPercent(name, player);
66 |
67 | return 0.;
68 | }
69 |
70 | public inline function addEvent(event:Event, field:Int = -1)
71 | __forEachPlayfield((pf) -> pf.addEvent(event), field);
72 |
73 | public inline function set(name:String, beat:Float, value:Float, player:Int = -1, field:Int = -1)
74 | __forEachPlayfield((pf) -> pf.set(name, beat, value, player), field);
75 |
76 | public inline function ease(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1, field:Int = -1)
77 | __forEachPlayfield((pf) -> pf.ease(name, beat, length, value, easeFunc, player), field);
78 |
79 | public inline function add(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1, field:Int = -1)
80 | __forEachPlayfield((pf) -> pf.add(name, beat, length, value, easeFunc, player), field);
81 |
82 | public inline function setAdd(name:String, beat:Float, value:Float, player:Int = -1, field:Int = -1)
83 | __forEachPlayfield((pf) -> pf.setAdd(name, beat, value, player), field);
84 |
85 | public inline function repeater(beat:Float, length:Float, callback:Event->Void, field:Int = -1)
86 | __forEachPlayfield((pf) -> pf.repeater(beat, length, callback), field);
87 |
88 | public inline function callback(beat:Float, callback:Event->Void, field:Int = -1)
89 | __forEachPlayfield((pf) -> pf.callback(beat, callback), field);
90 |
91 | public inline function node(input:Array, output:Array, func:NodeFunction, field:Int = -1)
92 | __forEachPlayfield((pf) -> pf.node(input, output, func), field);
93 |
94 | public inline function alias(name:String, alias:String, field:Int)
95 | __forEachPlayfield((pf) -> pf.alias(name, alias), field);
96 |
97 | public inline function addPlayfield()
98 | playfields.push(new PlayField());
99 |
100 | override function update(elapsed:Float):Void {
101 | super.update(elapsed);
102 |
103 | __forEachPlayfield(pf -> pf.update(elapsed));
104 | }
105 |
106 | override function draw():Void {
107 | var total = 0;
108 | __forEachPlayfield(pf -> {
109 | pf.draw();
110 |
111 | total += pf.drawCB.length;
112 | });
113 |
114 | var drawQueue:haxe.ds.Vector = new haxe.ds.Vector(total);
115 |
116 | var j = 0;
117 | __forEachPlayfield(pf -> {
118 | for (x in pf.drawCB)
119 | drawQueue[j++] = x;
120 | });
121 |
122 | drawQueue.sort((a, b) -> {
123 | return FlxSort.byValues(FlxSort.DESCENDING, a.z, b.z);
124 | });
125 |
126 | for (item in drawQueue) {
127 | item.callback();
128 | }
129 | }
130 |
131 | override function destroy():Void {
132 | super.destroy();
133 |
134 | __forEachPlayfield(pf -> {
135 | pf.destroy();
136 | });
137 | }
138 |
139 | public static var HOLD_SIZE:Float = 50 * 0.7;
140 | public static var HOLD_SIZEDIV2:Float = (50 * 0.7) * 0.5;
141 | public static var ARROW_SIZE:Float = 160 * 0.7;
142 | public static var ARROW_SIZEDIV2:Float = (160 * 0.7) * 0.5;
143 | }
144 |
145 | typedef Funny = {callback:Void->Void, z:Float};
146 |
--------------------------------------------------------------------------------
/modchart/Modifier.hx:
--------------------------------------------------------------------------------
1 | package modchart;
2 |
3 | import flixel.FlxG;
4 | import flixel.math.FlxMath;
5 | import modchart.Manager;
6 | import modchart.core.PlayField;
7 | import modchart.core.util.Constants.ArrowData;
8 | import modchart.core.util.Constants.RenderParams;
9 | import modchart.core.util.Constants.Visuals;
10 | import openfl.geom.Vector3D;
11 |
12 | using StringTools;
13 |
14 | class Modifier {
15 | private var pf:PlayField;
16 |
17 | public function new(pf:PlayField) {
18 | this.pf = pf;
19 | }
20 |
21 | public function render(curPos:Vector3D, params:RenderParams) {
22 | return curPos;
23 | }
24 |
25 | public function visuals(data:Visuals, params:RenderParams):Visuals {
26 | return data;
27 | }
28 |
29 | public function shouldRun(params:RenderParams):Bool
30 | return false;
31 |
32 | public inline function setPercent(name:String, value:Float, player:Int = -1) {
33 | pf.setPercent(name, value, player);
34 | }
35 |
36 | public inline function getPercent(name:String, player:Int):Float {
37 | return pf.getPercent(name, player);
38 | }
39 |
40 | private inline function getKeyCount(player:Int = 0):Int {
41 | return Adapter.instance.getKeyCount();
42 | }
43 |
44 | private inline function getPlayerCount():Int {
45 | return Adapter.instance.getPlayerCount();
46 | }
47 |
48 | // Helpers Functions
49 | private inline function getScrollSpeed():Float
50 | return Adapter.instance.getCurrentScrollSpeed();
51 |
52 | public inline function getReceptorY(lane:Int, player:Int)
53 | return Adapter.instance.getDefaultReceptorY(lane, player);
54 |
55 | public inline function getReceptorX(lane:Int, player:Int)
56 | return Adapter.instance.getDefaultReceptorX(lane, player);
57 |
58 | public function getManager():PlayField
59 | return pf;
60 |
61 | private var WIDTH:Float = FlxG.width;
62 | private var HEIGHT:Float = FlxG.height;
63 | private var ARROW_SIZE(get, never):Float;
64 | private var ARROW_SIZEDIV2(get, never):Float;
65 |
66 | private inline function get_ARROW_SIZE():Float
67 | return Manager.ARROW_SIZE;
68 |
69 | private inline function get_ARROW_SIZEDIV2():Float
70 | return Manager.ARROW_SIZEDIV2;
71 |
72 | private inline function sin(rad:Float):Float
73 | return ModchartUtil.sin(rad);
74 |
75 | private inline function cos(rad:Float):Float
76 | return ModchartUtil.cos(rad);
77 |
78 | private inline function tan(rad:Float):Float
79 | return ModchartUtil.tan(rad);
80 |
81 | public function toString():String {
82 | var classn:String = Type.getClassName(Type.getClass(this));
83 | classn = classn.substring(classn.lastIndexOf('.') + 1);
84 | return 'Modifier[$classn]';
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/modchart/core/ModifierGroup.hx:
--------------------------------------------------------------------------------
1 | package modchart.core;
2 |
3 | import haxe.ds.IntMap;
4 | import haxe.ds.StringMap;
5 | import haxe.ds.Vector;
6 | import modchart.Modifier;
7 | import modchart.core.util.Constants.ArrowData;
8 | import modchart.core.util.Constants.RenderParams;
9 | import modchart.core.util.Constants.Visuals;
10 | import modchart.core.util.ModchartUtil;
11 | import modchart.modifiers.*;
12 | import modchart.modifiers.false_paradise.*;
13 | import openfl.geom.Vector3D;
14 |
15 | @:structInit
16 | @:publicFields
17 | class ModifierOutput {
18 | var pos:Vector3D;
19 | var visuals:Visuals;
20 | }
21 |
22 | @:allow(modchart.Modifier)
23 | class ModifierGroup {
24 | public static var GLOBAL_MODIFIERS:Map> = [
25 | 'reverse' => Reverse,
26 | 'transform' => Transform,
27 | 'opponentswap' => OpponentSwap,
28 | 'drunk' => Drunk,
29 | 'bumpy' => Bumpy,
30 | 'tipsy' => Tipsy,
31 | 'tornado' => Tornado,
32 | 'invert' => Invert,
33 | 'square' => Square,
34 | 'zigzag' => ZigZag,
35 | 'beat' => Beat,
36 | 'boost' => Boost,
37 | 'receptorscroll' => ReceptorScroll,
38 | 'sawtooth' => SawTooth,
39 | 'zoom' => Zoom,
40 | 'rotate' => Rotate,
41 | 'fieldrotate' => FieldRotate,
42 | 'centerrotate' => CenterRotate,
43 | 'confusion' => Confusion,
44 | 'stealth' => Stealth,
45 | 'scale' => Scale,
46 | 'skew' => Skew,
47 | // YOU NEVER STOOD A CHANCE
48 | 'infinite' => Infinite,
49 | 'schmovindrunk' => SchmovinDrunk,
50 | 'schmovintipsy' => SchmovinTipsy,
51 | 'schmovintornado' => SchmovinTornado,
52 | 'wiggle' => Wiggle,
53 | 'arrowshape' => ArrowShape,
54 | 'eyeshape' => EyeShape,
55 | 'spiral' => Spiral,
56 | 'counterclockwise' => CounterClockWise,
57 | 'vibrate' => Vibrate,
58 | 'bounce' => Bounce,
59 | 'radionic' => Radionic,
60 | 'schmovinarrowshape' => SchmovinArrowShape,
61 | 'drugged' => Drugged
62 | ];
63 |
64 | private var MODIFIER_REGISTRY:Map> = GLOBAL_MODIFIERS;
65 |
66 | private var percents:StringMap> = new StringMap();
67 | private var modifiers:StringMap = new StringMap();
68 |
69 | private var sortedMods:Vector;
70 |
71 | private var pf:PlayField;
72 |
73 | public function new(pf:PlayField) {
74 | this.pf = pf;
75 |
76 | __allocModSorting([]);
77 | }
78 |
79 | @:dox(hide)
80 | @:noCompletion private function __allocModSorting(newList:Array) {
81 | return sortedMods = Vector.fromArrayCopy(newList);
82 | }
83 |
84 | // just render mods with the perspective stuff included
85 | public function getPath(pos:Vector3D, data:ArrowData, ?posDiff:Float = 0, ?allowVis:Bool = true, ?allowPos:Bool = true):ModifierOutput {
86 | var visuals:Visuals = {};
87 |
88 | if (!allowVis && !allowPos)
89 | return {pos: pos, visuals: visuals};
90 |
91 | final songPos = Adapter.instance.getSongPosition();
92 | final beat = Adapter.instance.getCurrentBeat();
93 |
94 | final args:RenderParams = {
95 | songTime: songPos,
96 | curBeat: beat,
97 | hitTime: data.hitTime + posDiff,
98 | distance: data.distance + posDiff,
99 | lane: data.lane,
100 | player: data.player,
101 | isTapArrow: data.isTapArrow
102 | }
103 |
104 | for (i in 0...sortedMods.length) {
105 | final mod = modifiers.get(sortedMods[i]);
106 |
107 | if (!mod.shouldRun(args))
108 | continue;
109 |
110 | if (allowPos)
111 | pos = mod.render(pos, args);
112 | if (allowVis)
113 | visuals = mod.visuals(visuals, args);
114 | }
115 | pos.z *= 0.001;
116 | return {
117 | pos: ModchartUtil.project(pos),
118 | visuals: visuals
119 | };
120 | }
121 |
122 | // TODO: add `activeMods` var (for optimization) and percentBackup for editor (can also be helpful for activeMods handling or idk)
123 | var activeMods:Vector;
124 | var percentsBackup:StringMap>;
125 |
126 | public function refreshActiveMods() {}
127 |
128 | public function refreshPercentBackup() {}
129 |
130 | public function registerModifier(name:String, modifier:Class) {
131 | var lowerName = name.toLowerCase();
132 | if (MODIFIER_REGISTRY.get(lowerName) != null) {
133 | trace('[FunkinModchart] There\'s already a modifier named "$name" registered !');
134 | return;
135 | }
136 | MODIFIER_REGISTRY.set(lowerName, modifier);
137 | }
138 |
139 | public function addModifier(name:String) {
140 | var lowerName = name.toLowerCase();
141 | if (modifiers.exists(lowerName))
142 | return;
143 | var modifierClass:Null> = MODIFIER_REGISTRY.get(lowerName);
144 | if (modifierClass == null) {
145 | trace('[FunkinModchart] Modifier named "$name" was not found !');
146 |
147 | return;
148 | }
149 | var newModifier = Type.createInstance(modifierClass, [pf]);
150 | modifiers.set(lowerName, newModifier);
151 |
152 | final newArr = sortedMods.toArray();
153 | newArr.push(lowerName);
154 | __allocModSorting(newArr);
155 | }
156 |
157 | public function setPercent(name:String, value:Float, player:Int = -1) {
158 | var lwr = name.toLowerCase();
159 | var possiblePercs = percents.get(lwr);
160 | var generate = possiblePercs == null;
161 | var percs = generate ? __getAllocatedPercs() : possiblePercs;
162 |
163 | if (player == -1)
164 | for (_ in 0...percs.length)
165 | percs[_] = value;
166 | else
167 | percs[player] = value;
168 |
169 | // if the percent list already was created, we dont need to re-set the list
170 | if (generate)
171 | percents.set(lwr, percs);
172 | }
173 |
174 | public function getPercent(name:String, player:Int):Float {
175 | final percs = percents.get(name.toLowerCase());
176 |
177 | if (percs != null)
178 | return percs[player];
179 | return 0;
180 | }
181 |
182 | private inline function __getAllocatedPercs():Vector {
183 | final vector = new Vector(Adapter.instance.getPlayerCount());
184 | for (i in 0...vector.length)
185 | vector[i] = 0;
186 | return vector;
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/modchart/core/PlayField.hx:
--------------------------------------------------------------------------------
1 | package modchart.core;
2 |
3 | import flixel.FlxBasic;
4 | import flixel.FlxG;
5 | import flixel.FlxSprite;
6 | import flixel.tweens.FlxEase.EaseFunction;
7 | import modchart.core.graphics.ModchartCamera3D;
8 | import modchart.core.graphics.ModchartGraphics.ModchartArrowPath;
9 | import modchart.core.graphics.ModchartGraphics.ModchartArrowRenderer;
10 | import modchart.core.graphics.ModchartGraphics.ModchartHoldRenderer;
11 | import modchart.core.util.ModchartUtil;
12 | import modchart.events.Event;
13 | import modchart.events.EventManager;
14 | import modchart.events.types.AddEvent;
15 | import modchart.events.types.EaseEvent;
16 | import modchart.events.types.RepeaterEvent;
17 | import modchart.events.types.SetEvent;
18 | import openfl.geom.Vector3D;
19 |
20 | // TODO: make this extend to flxsprite and use parented transformation matrix
21 | class PlayField extends FlxBasic {
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:ModchartArrowPath;
31 |
32 | public function new() {
33 | super();
34 |
35 | this.events = new EventManager(this);
36 | this.modifiers = new ModifierGroup(this);
37 |
38 | arrowRenderer = new ModchartArrowRenderer(this);
39 | receptorRenderer = new ModchartArrowRenderer(this);
40 | attachmentRenderer = new ModchartArrowRenderer(this);
41 | holdRenderer = new ModchartHoldRenderer(this);
42 | pathRenderer = new ModchartArrowPath(this);
43 |
44 | camera3D = new ModchartCamera3D();
45 |
46 | // default mods
47 | addModifier('reverse');
48 | addModifier('confusion');
49 | addModifier('stealth');
50 | addModifier('skew');
51 | addModifier('zoom');
52 |
53 | setPercent('arrowPathAlpha', 1, -1);
54 | setPercent('arrowPathThickness', 1, -1);
55 | setPercent('arrowPathDivisions', 1, -1);
56 | setPercent('rotateHoldY', 1, -1);
57 | }
58 |
59 | public function registerModifier(name:String, mod:Class)
60 | return modifiers.registerModifier(name, mod);
61 |
62 | public function setPercent(name:String, value:Float, player:Int = -1)
63 | return modifiers.setPercent(name, value, player);
64 |
65 | public function getPercent(name:String, player:Int)
66 | return modifiers.getPercent(name, player);
67 |
68 | public function addModifier(name:String)
69 | return modifiers.addModifier(name);
70 |
71 | public function addEvent(event:Event) {
72 | events.add(event);
73 | }
74 |
75 | public function set(name:String, beat:Float, value:Float, player:Int = -1):Void {
76 | if (player == -1) {
77 | for (curField in 0...Adapter.instance.getPlayerCount())
78 | set(name, beat, value, curField);
79 | return;
80 | }
81 |
82 | addEvent(new SetEvent(name.toLowerCase(), beat, value, player, events));
83 | }
84 |
85 | public function ease(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1):Void {
86 | if (player == -1) {
87 | for (curField in 0...Adapter.instance.getPlayerCount())
88 | ease(name, beat, length, value, easeFunc, curField);
89 | return;
90 | }
91 |
92 | addEvent(new EaseEvent(name, beat, length, value, easeFunc, player, events));
93 | }
94 |
95 | public function add(name:String, beat:Float, length:Float, value:Float = 1, easeFunc:EaseFunction, player:Int = -1):Void {
96 | if (player == -1) {
97 | for (curField in 0...Adapter.instance.getPlayerCount())
98 | add(name, beat, length, value, easeFunc, curField);
99 | return;
100 | }
101 |
102 | addEvent(new AddEvent(name, beat, length, value, easeFunc, player, events));
103 | }
104 |
105 | public function setAdd(name:String, beat:Float, valueToAdd:Float, player:Int = -1):Void {
106 | var addition = getPercent(name, player == -1 ? 0 : player);
107 | var value = addition + valueToAdd;
108 | if (player == -1) {
109 | for (curField in 0...Adapter.instance.getPlayerCount())
110 | set(name, beat, value, curField);
111 | return;
112 | }
113 |
114 | addEvent(new SetEvent(name.toLowerCase(), beat, value, player, events));
115 | }
116 |
117 | public function repeater(beat:Float, length:Float, callback:Event->Void):Void
118 | addEvent(new RepeaterEvent(beat, length, callback, events));
119 |
120 | public function callback(beat:Float, callback:Event->Void):Void
121 | addEvent(new Event(beat, callback, events));
122 |
123 | public function alias(name:String, alias:String) {
124 | aliases.push({
125 | parent: name,
126 | alias: alias
127 | });
128 | }
129 |
130 | private var aliases:Array = [];
131 | private var nodes:Array = [];
132 |
133 | /**
134 | * Register a node.
135 | * @param input Input Aux Mods
136 | * @param output Output Mods
137 | * @param func Processor function, Array -> Array
138 | */
139 | public function node(input:Array, output:Array, func:NodeFunction) {
140 | nodes.push({
141 | input: input,
142 | output: output,
143 | func: func
144 | });
145 | }
146 |
147 | // EXPERIMENTAL
148 | // FIXME
149 | // Warning: If a node has 'drunk' by example in his output
150 | // and u made a ease on drunk and u made a ease on the node
151 | // input, the eases may overlap, causing visuals issues.
152 | public function updateNodes() {
153 | for (player in 0...Adapter.instance.getPlayerCount()) {
154 | final it = nodes.iterator();
155 | final n = it.next;
156 | final h = it.hasNext;
157 | do {
158 | final node = n();
159 | if (node == null)
160 | continue;
161 |
162 | if (node == null)
163 | continue;
164 |
165 | var entryPercs = [];
166 | var outPercs = [];
167 | entryPercs.resize(node.input.length);
168 |
169 | for (i in 0...entryPercs.length)
170 | entryPercs[i] = getPercent(node.input[i], player);
171 |
172 | outPercs = node.func(entryPercs, player);
173 |
174 | final nbl = node.output.length;
175 | if (outPercs == null || outPercs.length < 0)
176 | outPercs = [];
177 |
178 | for (i in 0...nbl) {
179 | final prc = outPercs[i];
180 |
181 | if (!Math.isNaN(prc) && prc != 0)
182 | setPercent(node.output[i], prc, player);
183 | }
184 | } while (h());
185 | }
186 | }
187 |
188 | override function update(elapsed:Float):Void {
189 | // Update Event Timeline
190 | events.update(Adapter.instance.getCurrentBeat());
191 |
192 | updateNodes();
193 | }
194 |
195 | override public function draw() {
196 | super.draw();
197 | __drawPlayField();
198 | }
199 |
200 | override public function destroy() {
201 | arrowRenderer.dispose();
202 | holdRenderer.dispose();
203 | receptorRenderer.dispose();
204 | attachmentRenderer.dispose();
205 | pathRenderer.dispose();
206 | super.destroy();
207 | }
208 |
209 | var drawCB:Array<{callback:Void->Void, z:Float}> = [];
210 |
211 | private function getVisibility(obj:flixel.FlxObject) {
212 | @:bypassAccessor obj.visible = false;
213 | return obj._fmVisible;
214 | }
215 |
216 | private function __drawPlayField() {
217 | drawCB = [];
218 |
219 | // TODO: prepare arrow paths shit
220 | var pathAlphaTotal = .0;
221 |
222 | var playerItems:Array>> = Adapter.instance.getArrowItems();
223 |
224 | // used for preallocate
225 | var receptorLength = 0;
226 | var arrowLength = 0;
227 | var holdLength = 0;
228 |
229 | for (i in 0...playerItems.length) {
230 | final curItems = playerItems[i];
231 |
232 | receptorLength = receptorLength + curItems[0].length;
233 | arrowLength = arrowLength + curItems[1].length;
234 | holdLength = holdLength + curItems[2].length;
235 | }
236 |
237 | receptorRenderer.preallocate(receptorLength);
238 | arrowRenderer.preallocate(arrowLength);
239 | holdRenderer.preallocate(holdLength);
240 | if (Manager.instance.renderArrowPaths)
241 | pathRenderer.preallocate(receptorLength);
242 |
243 | drawCB.resize(receptorLength + arrowLength + holdLength);
244 |
245 | var j = 0;
246 | inline function queue(f:{callback:Void->Void, z:Float}) {
247 | drawCB[j] = f;
248 | j++;
249 | }
250 |
251 | // i is player index
252 | for (i in 0...playerItems.length) {
253 | var curItems:Array> = playerItems[i];
254 |
255 | // receptors
256 | if (curItems[0] != null && curItems[0].length > 0) {
257 | for (receptor in curItems[0]) {
258 | if (!getVisibility(receptor))
259 | continue;
260 |
261 | receptorRenderer.prepare(receptor);
262 | if (Manager.instance.renderArrowPaths)
263 | pathRenderer.prepare(receptor);
264 | queue({
265 | callback: receptorRenderer.shift,
266 | z: receptor._z
267 | });
268 | }
269 | }
270 |
271 | // holds
272 | if (curItems[2] != null && curItems[2].length > 0) {
273 | for (hold in curItems[2]) {
274 | if (!getVisibility(hold))
275 | continue;
276 |
277 | holdRenderer.prepare(hold);
278 | queue({
279 | callback: holdRenderer.shift,
280 | z: hold._z
281 | });
282 | }
283 | }
284 |
285 | // tap arrow
286 | if (curItems[1] != null && curItems[1].length > 0) {
287 | for (arrow in curItems[1]) {
288 | if (!getVisibility(arrow))
289 | continue;
290 |
291 | arrowRenderer.prepare(arrow);
292 | queue({
293 | callback: arrowRenderer.shift,
294 | z: arrow._z
295 | });
296 | }
297 | }
298 | }
299 |
300 | for (r in [receptorRenderer, arrowRenderer, holdRenderer])
301 | r.sort();
302 |
303 | if (Manager.instance.renderArrowPaths)
304 | pathRenderer.shift();
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/modchart/core/Quanterion.hx:
--------------------------------------------------------------------------------
1 | package modchart.core;
2 |
3 | import modchart.core.util.ModchartUtil;
4 | import openfl.geom.Vector3D;
5 |
6 | @:structInit
7 | @:publicFields
8 | class Quaternion {
9 | var x:Float;
10 | var y:Float;
11 | var z:Float;
12 | var w:Float;
13 |
14 | function new(x:Float, y:Float, z:Float, w:Float) {
15 | this.x = x;
16 | this.y = y;
17 | this.z = z;
18 | this.w = w;
19 | }
20 |
21 | function multiply(q:Quaternion):Quaternion {
22 | return new Quaternion(w * q.x
23 | + x * q.w
24 | + y * q.z
25 | - z * q.y, w * q.y
26 | - x * q.z
27 | + y * q.w
28 | + z * q.x, w * q.z
29 | + x * q.y
30 | - y * q.x
31 | + z * q.w,
32 | w * q.w
33 | - x * q.x
34 | - y * q.y
35 | - z * q.z);
36 | }
37 |
38 | function rotateVector(v:Vector3D):Vector3D {
39 | var qVec = new Quaternion(v.x, v.y, v.z, 0);
40 | var qConj = new Quaternion(-x, -y, -z, w);
41 | var result = this.multiply(qVec).multiply(qConj);
42 | return new Vector3D(result.x, result.y, result.z);
43 | }
44 |
45 | static function fromAxisAngle(axis:Vector3D, angleRad:Float):Quaternion {
46 | var sinHalfAngle = ModchartUtil.sin(angleRad * .5);
47 | var cosHalfAngle = ModchartUtil.cos(angleRad * .5);
48 | return new Quaternion(axis.x * sinHalfAngle, axis.y * sinHalfAngle, axis.z * sinHalfAngle, cosHalfAngle);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/modchart/core/environments/Andromeda.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.environments;
2 |
3 | import haxe.ds.StringMap;
4 | import modchart.events.Event;
5 |
6 | using StringTools;
7 |
8 | // Adapter to use Andromeda's ModMngr functions on FunkinModchart (based on LuaModMngr)
9 | // andromeda engine doesnt support PlayFields so u cant use playfields on this environment :/
10 | class Andromeda implements IEnvironment {
11 | public var parent:Manager;
12 |
13 | private final parseKeys:StringMap = ['localrotate' => 'fieldrotate', 'boost' => 'accelerate'];
14 |
15 | public function setup(parent:Manager) {
16 | this.parent = parent;
17 |
18 | define('oppponentSwap');
19 | define('zigzag');
20 | define('sawtooth');
21 | define('bounce');
22 | define('square');
23 | define('mini');
24 | define('invert');
25 | define('tornado');
26 | define('drunk');
27 | define('beat');
28 | define('rotate');
29 | define('centerrotate');
30 | define('localrotate');
31 | define('accelerate');
32 | define('transform');
33 | define('infinite');
34 | define('receptorscroll');
35 | }
36 |
37 | public function dispose() {}
38 |
39 | public function define(modName:String) {
40 | parent.addModifier(modName);
41 | }
42 |
43 | public function set(modName:String, percent:Float, player:Null) {
44 | parent.setPercent(parseMod(modName), percent * 0.001, parsePlayer(player));
45 | }
46 |
47 | public function get(modName:String, player:Null):Float {
48 | return parent.getPercent(parseMod(modName), parsePlayer(player));
49 | }
50 |
51 | public function queueSet(step:Float, modName:String, percent:Float, player:Null) {
52 | parent.set(parseMod(modName), bfs(step), percent * 0.001, parsePlayer(player));
53 | }
54 |
55 | public function queueEase(step:Float, endStep:Float, modName:String, percent:Float, easingStyle:String, player:Null) {
56 | parent.ease(parseMod(modName), bfs(step), bfs(endStep - step), percent * 0.01, cast Reflect.field(flixel.tweens.FlxEase, easingStyle),
57 | parsePlayer(player));
58 | }
59 |
60 | public function queueEaseL(step:Float, length:Float, modName:String, percent:Float, easingStyle:String, player:Null) {
61 | parent.ease(parseMod(modName), bfs(step), bfs(length), percent * 0.01, cast Reflect.field(flixel.tweens.FlxEase, easingStyle), parsePlayer(player));
62 | }
63 |
64 | private function parsePlayer(player:Null):Int {
65 | if (player == null)
66 | return -1;
67 |
68 | return 1 - player;
69 | }
70 |
71 | private function parseMod(name:String) {
72 | var vname = name.toLowerCase();
73 | for (k => v in parseKeys) {
74 | vname.replace(k, v);
75 | }
76 | return vname;
77 | }
78 |
79 | private final bfs:Float->Float = (s) -> Adapter.instance.getBeatFromStep(s);
80 | }
81 |
--------------------------------------------------------------------------------
/modchart/core/environments/IEnvironment.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.environments;
2 |
3 | interface IEnvironment {
4 | public var parent:Manager;
5 |
6 | public function setup(parent:Manager):Void;
7 | public function dispose():Void;
8 | }
9 |
--------------------------------------------------------------------------------
/modchart/core/environments/ModchartingTools.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.environments;
2 |
3 | import haxe.ds.StringMap;
4 | import modchart.events.Event;
5 |
6 | using StringTools;
7 |
8 | // Adapter to use ModchartingTools's functions/api
9 | class ModchartingTools implements IEnvironment {
10 | public var parent:Manager;
11 |
12 | public function setup(parent:Manager) {
13 | this.parent = parent;
14 | }
15 |
16 | public function dispose() {}
17 |
18 | private final bfs:Float->Float = (s) -> Adapter.instance.getBeatFromStep(s);
19 | }
20 |
--------------------------------------------------------------------------------
/modchart/core/graphics/ModchartCamera3D.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.graphics;
2 |
3 | import flixel.FlxG;
4 | import openfl.Vector;
5 | import openfl.geom.Matrix3D;
6 | import openfl.geom.Vector3D;
7 |
8 | /**
9 | * `FlxCamera3D` extends `FlxCamera` to provide basic 3D camera functionality,
10 | * including transformations for position, rotation, and movement in 3D space.
11 | *
12 | * Features:
13 | * - 3D position (`eyePos`) and target (`lookAt`).
14 | * - View matrix transformation for rendering.
15 | * - Support for pitch, yaw, and roll rotations.
16 | * - Camera movement functions (`moveForward`, `moveRight`, `moveUp`).
17 | *
18 | * `NOTE`: All of those features only work on `FlxSprite3D` instances.
19 | */
20 | class ModchartCamera3D {
21 | /**
22 | * Represents the depth (Z-axis position) of the camera position.
23 | */
24 | public var z:Float;
25 |
26 | /**
27 | * The position of the camera (viewpoint) in world coordinates.
28 | *
29 | * This represents the location of the viewer in 3D space.
30 | * The camera will be positioned at this point and will look toward `lookAt`.
31 | * Default: (0, 0, -10), meaning the camera starts 10 units back along the Z-axis.
32 | */
33 | public var eyePos(default, null):Vector3D = new Vector3D(0, 0, -10);
34 |
35 | /**
36 | * The target position that the camera is looking at.
37 | *
38 | * This point determines the direction the camera is facing.
39 | * The view matrix is calculated based on the vector from `eyePos` to `lookAt`.
40 | * Default: (0, 0, 0), meaning the camera looks toward the origin.
41 | */
42 | public var lookAt(default, null):Vector3D = new Vector3D(0, 0, 0);
43 |
44 | /**
45 | * The up direction vector, defining the camera's vertical orientation.
46 | *
47 | * This vector determines which direction is considered "up" for the camera.
48 | * It is typically set to (0, 1, 0) to align with the Y-axis, but can be modified
49 | * for custom orientations (e.g., to simulate a tilted horizon).
50 | */
51 | public var up(default, null):Vector3D = new Vector3D(0, 1, 0);
52 |
53 | /**
54 | * Rotation around the X-axis, controlling the tilt up/down.
55 | *
56 | * - Positive values tilt the camera downward.
57 | * - Negative values tilt the camera upward.
58 | * - Expressed in degrees.
59 | */
60 | public var pitch:Float = 0;
61 |
62 | /**
63 | * Rotation around the Y-axis, controlling the left/right turn.
64 | *
65 | * - Positive values turn the camera to the right.
66 | * - Negative values turn the camera to the left.
67 | * - Expressed in degrees.
68 | */
69 | public var yaw:Float = 0;
70 |
71 | /**
72 | * Rotation around the Z-axis, controlling the tilt sideways (roll).
73 | *
74 | * - Positive values tilt the camera clockwise.
75 | * - Negative values tilt the camera counterclockwise.
76 | * - Expressed in degrees.
77 | */
78 | public var roll:Float = 0;
79 |
80 | @:noCompletion private var __viewMatrix(default, null):Matrix3D = new Matrix3D();
81 | @:noCompletion private var __rotationMatrix(default, null):Matrix3D = new Matrix3D();
82 |
83 | public function new() {}
84 |
85 | /**
86 | * Updates the camera's view matrix based on its position and rotation.
87 | *
88 | * This function recalculates the `__viewMatrix`, which is used to transform
89 | * world coordinates into the camera's local space. It applies rotation transformations
90 | * using the pitch, yaw, and roll angles and computes the final view matrix.
91 | *
92 | * Steps:
93 | * 1. Resets the `__viewMatrix` and `__rotationMatrix` to identity.
94 | * 2. Applies rotation transformations to align the camera's orientation.
95 | * 3. Defines the default axis directions (`forward`, `up`, `right`).
96 | * 4. Transforms these axes using the rotation matrix.
97 | * 5. Computes the camera position in view space.
98 | * 6. Constructs the view matrix using the transformed axes and camera position.
99 | *
100 | * This matrix is essential for rendering objects correctly from the camera's perspective.
101 | */
102 | inline private function updateCameraView() {
103 | __viewMatrix.identity();
104 | __rotationMatrix.identity();
105 |
106 | // setup rotations
107 | __rotationMatrix.appendRotation(pitch * 180 / Math.PI, Vector3D.X_AXIS); // x
108 | __rotationMatrix.appendRotation(yaw * 180 / Math.PI, Vector3D.Y_AXIS); // y
109 | __rotationMatrix.appendRotation(roll * 180 / Math.PI, Vector3D.Z_AXIS); // z
110 |
111 | // eye shit
112 | var forward = Vector3D.Z_AXIS; // depth axis
113 | var up = Vector3D.Y_AXIS; // y axis
114 | var right = Vector3D.X_AXIS; // x axis
115 |
116 | // apply rotations
117 | forward = __rotationMatrix.transformVector(forward);
118 | up = __rotationMatrix.transformVector(up);
119 | right = __rotationMatrix.transformVector(right);
120 |
121 | // calc view position
122 | var negEye = new Vector3D(-eyePos.x, -eyePos.y, -eyePos.z);
123 |
124 | __viewMatrix.rawData = new Vector(16, true, [
125 | right.x, up.x, forward.x, 0,
126 | right.y, up.y, forward.y, 0,
127 | right.z, up.z, forward.z, 0,
128 | right.dotProduct(negEye), up.dotProduct(negEye), forward.dotProduct(negEye), 1
129 | ]);
130 | }
131 |
132 | /**
133 | * Transforms a given 3D vector from world space to the camera's view space.
134 | *
135 | * This function applies the current `__viewMatrix` to the input vector,
136 | * converting it from world coordinates to the camera's local coordinate system.
137 | *
138 | * @param vector The `Vector3D` representing a point or direction in world space.
139 | * @return A new `Vector3D` transformed into the camera's view space.
140 | *
141 | * Example usage:
142 | * ```haxe
143 | * var worldPos = new Vector3D(10, 5, -20);
144 | * var viewPos = applyViewTo(worldPos);
145 | * ```
146 | */
147 | inline private function applyViewTo(vector:Vector3D, ?origin:Vector3D = null) {
148 | var reference = origin != null ? origin : eyePos.add(new Vector3D(FlxG.width / 2, FlxG.height / 2));
149 | return __viewMatrix.transformVector(vector.subtract(reference)).add(reference);
150 | }
151 |
152 | // some helpers lol
153 | public function moveForward(amount:Float):Void {
154 | var forward:Vector3D = lookAt.subtract(eyePos);
155 | forward.normalize();
156 | forward.scaleBy(amount);
157 | eyePos.incrementBy(forward);
158 | lookAt.incrementBy(forward);
159 | }
160 |
161 | public function moveRight(amount:Float):Void {
162 | var right:Vector3D = up.crossProduct(lookAt.subtract(eyePos));
163 | right.normalize();
164 | right.scaleBy(amount);
165 | eyePos.incrementBy(right);
166 | lookAt.incrementBy(right);
167 | }
168 |
169 | public function moveUp(amount:Float):Void {
170 | var moveUp:Vector3D = up.clone();
171 | moveUp.normalize();
172 | moveUp.scaleBy(amount);
173 | eyePos.incrementBy(moveUp);
174 | lookAt.incrementBy(moveUp);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/modchart/core/graphics/ModchartGraphics.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.graphics;
2 |
3 | import flixel.FlxBasic;
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.FlxPoint;
10 | import flixel.util.FlxSort;
11 | import haxe.ds.ObjectMap;
12 | import haxe.ds.Vector as NativeVector;
13 | import modchart.core.util.Constants.ArrowData;
14 | import modchart.core.util.Constants.Visuals;
15 | import modchart.core.util.ModchartUtil;
16 | import openfl.Vector;
17 | import openfl.display.GraphicsPathCommand;
18 | import openfl.display.Shape;
19 | import openfl.geom.ColorTransform;
20 | import openfl.geom.Matrix;
21 | import openfl.geom.Vector3D;
22 |
23 | var rotationVector = new Vector3D();
24 | var helperVector = new Vector3D();
25 | var __matrix:Matrix = new Matrix();
26 | var pathVector = new Vector3D();
27 |
28 | typedef HoldSegmentOutput = {
29 | depth:Float,
30 | left:Vector3D,
31 | right:Vector3D,
32 | visuals:Visuals
33 | }
34 |
35 | class ModchartRenderer extends FlxBasic {
36 | private var instance:Null;
37 | private var queue:NativeVector;
38 | private var count:Int = 0;
39 | private var postCount:Int = 0;
40 |
41 | public function new(instance:PlayField) {
42 | super();
43 |
44 | this.instance = instance;
45 | }
46 |
47 | // Renderer-side
48 | public function prepare(item:T) {}
49 |
50 | public function shift():Void {}
51 |
52 | public function dispose() {}
53 |
54 | // Built-in functions
55 | public function preallocate(length:Int) {
56 | queue = new NativeVector(length);
57 | count = postCount = 0;
58 | }
59 |
60 | public function sort() {
61 | queue.sort((a, b) -> {
62 | return FlxSort.byValues(FlxSort.DESCENDING, a.item._z, b.item._z);
63 | });
64 | }
65 |
66 | // public function render(times:Null):Void {}
67 | }
68 |
69 | class ModchartHoldRenderer extends ModchartRenderer {
70 | private var __lastHoldSubs:Int = -1;
71 |
72 | var _indices:Null> = new Vector();
73 |
74 | /**
75 | * Returns the normal points along the hold path at specific hitTime using.
76 | *
77 | * Based on schmovin' hold system
78 | * @param basePos The hold position per default
79 | * @see https://en.wikipedia.org/wiki/Unit_circle
80 | */
81 | @:noCompletion
82 | inline private function getHoldSegment(hold:FlxSprite, basePos:Vector3D, params:ArrowData):HoldSegmentOutput {
83 | @:privateAccess
84 | var holdOffset = params.__holdSubdivisionOffset;
85 | final oldHitTime = params.hitTime;
86 |
87 | var hitTime = Adapter.instance.getHoldParentTime(hold);
88 | hitTime = hitTime + (oldHitTime - hitTime) * __long;
89 |
90 | params.hitTime = hitTime;
91 | params.distance = hitTime - Adapter.instance.getSongPosition();
92 | if (params.hitten && params.distance < 0)
93 | params.distance = 0;
94 |
95 | final origin = instance.modifiers.getPath(basePos.clone(), params);
96 | final next = instance.modifiers.getPath(basePos.clone(), params, 1, false, true);
97 |
98 | var depth = (origin.pos.z - 1) * 1000;
99 |
100 | var curPoint = origin.pos;
101 | var nextPoint = next.pos;
102 |
103 | var zScale:Float = curPoint.z != 0 ? (1 / curPoint.z) : 1;
104 | curPoint.z = nextPoint.z = 0;
105 |
106 | // normalized points difference (from 0-1)
107 | var unit = nextPoint.subtract(curPoint);
108 | unit.normalize();
109 |
110 | var size = hold.frame.frame.width * hold.scale.x * .5;
111 |
112 | var quad0 = new Vector3D(-unit.y * size, unit.x * size);
113 | var quad1 = new Vector3D(unit.y * size, -unit.x * size);
114 |
115 | @:privateAccess
116 | for (i in 0...2) {
117 | var visuals = switch (i) {
118 | case 0: origin.visuals;
119 | case 1: next.visuals;
120 | default: null;
121 | }
122 | var quad = switch (i) {
123 | case 0: quad0;
124 | case 1: quad1;
125 | default: null;
126 | }
127 | var rotation = quad;
128 | var angX = visuals.angleX * __rotateX;
129 | var angY = visuals.angleY * __rotateY;
130 | var angZ = visuals.angleZ * __rotateZ;
131 | var rotated = angX != 0 || angY != 0 || angZ != 0;
132 |
133 | if (rotated)
134 | rotation = ModchartUtil.rotate3DVector(quad, angX, angY, angZ);
135 |
136 | if (visuals.skewX != 0 || visuals.skewY != 0) {
137 | __matrix.identity();
138 |
139 | __matrix.b = ModchartUtil.tan(visuals.skewY * FlxAngle.TO_RAD);
140 | __matrix.c = ModchartUtil.tan(visuals.skewX * FlxAngle.TO_RAD);
141 |
142 | rotation.x = __matrix.__transformX(rotation.x, rotation.y);
143 | rotation.y = __matrix.__transformY(rotation.x, rotation.y);
144 | }
145 | rotation.x = rotation.x * zScale * visuals.scaleX;
146 | rotation.y = rotation.y * zScale * visuals.scaleY;
147 |
148 | var view = new Vector3D(rotation.x + curPoint.x, rotation.y + curPoint.y, rotation.z);
149 | if (Config.CAMERA3D_ENABLED)
150 | view = instance.camera3D.applyViewTo(view);
151 | view.z *= 0.001;
152 |
153 | // The result of the perspective projection of rotation
154 | var projection = view;
155 | if (rotated)
156 | projection = ModchartUtil.project(view);
157 |
158 | quad.x = projection.x;
159 | quad.y = projection.y;
160 | quad.z = projection.z;
161 | }
162 | return {
163 | left: quad0,
164 | right: quad1,
165 | visuals: origin.visuals,
166 | depth: depth
167 | };
168 | }
169 |
170 | private var __long:Float = 0.0;
171 | private var __straightAmount:Float = 0.0;
172 | private var __calculatingSegments:Bool = false;
173 | private var __rotateX:Float = 0;
174 | private var __rotateY:Float = 0;
175 | private var __rotateZ:Float = 0;
176 |
177 | /*
178 | private function getStraightHoldSegment(hold:FlxSprite, basePos:Vector3D, params:ArrowData):Array {
179 | var perc = __straightAmount;
180 |
181 | var oldHitTime = params.hitTime;
182 | params.hitTime = Adapter.instance.getHoldParentTime(hold);
183 |
184 | var distance = params.hitTime - Adapter.instance.getSongPosition();
185 | params.distance = oldHitTime == 0 ? 0 : distance;
186 |
187 | var holdProgress = oldHitTime - params.hitTime;
188 | final origin = instance.modifiers.getPath(basePos.clone(), params);
189 | final next = instance.modifiers.getPath(basePos.clone(), params, 1);
190 | final originPos = origin.pos;
191 | final nextPos = next.pos;
192 |
193 | originPos.z = nextPos.z = 0;
194 |
195 | // actual unit
196 | var unit = nextPos.subtract(originPos);
197 | unit.normalize();
198 |
199 | // the center position of the hold
200 | var holdOrigin = unit.clone();
201 | holdOrigin.scaleBy(holdProgress * __long);
202 | holdOrigin.incrementBy(originPos);
203 |
204 | var zScale:Float = holdOrigin.z != 0 ? (1 / holdOrigin.z) : 1;
205 | holdOrigin.z = 0;
206 |
207 | var size = hold.frame.frame.width * hold.scale.x * .5;
208 |
209 | var quad0 = new Vector3D(-unit.y * size, unit.x * size);
210 | var quad1 = new Vector3D(unit.y * size, -unit.x * size);
211 |
212 | return [
213 | [
214 | holdOrigin.add(quad0),
215 | holdOrigin.add(quad1),
216 | holdOrigin.add(new Vector3D(0, 0, 1 + (1 - zScale) * 0.001))
217 | ],
218 | origin.visuals
219 | ];
220 | }*/
221 | @:noCompletion
222 | inline private function updateIndices() {
223 | final HOLD_SUBDIVISIONS = Adapter.instance.getHoldSubdivisions();
224 |
225 | _indices.length = (HOLD_SUBDIVISIONS * 6);
226 | for (sub in 0...HOLD_SUBDIVISIONS) {
227 | var vert = sub * 4;
228 | var count = sub * 6;
229 |
230 | _indices[count] = _indices[count + 3] = vert;
231 | _indices[count + 2] = _indices[count + 5] = vert + 3;
232 | _indices[count + 1] = vert + 1;
233 | _indices[count + 4] = vert + 2;
234 | }
235 | }
236 |
237 | override public function prepare(item:FlxSprite):Void {
238 | final arrow:FlxSprite = item;
239 | final newInstruction:FMDrawInstruction = {};
240 | final HOLD_SUBDIVISIONS = Adapter.instance.getHoldSubdivisions();
241 |
242 | if (__lastHoldSubs != HOLD_SUBDIVISIONS)
243 | updateIndices();
244 |
245 | if (__lastHoldSubs == -1)
246 | __lastHoldSubs = Adapter.instance.getHoldSubdivisions();
247 |
248 | Manager.HOLD_SIZE = arrow.width;
249 | Manager.HOLD_SIZEDIV2 = arrow.width * .5;
250 |
251 | final player = Adapter.instance.getPlayerFromArrow(item);
252 | final lane = Adapter.instance.getLaneFromArrow(item);
253 |
254 | final basePos = new Vector3D(Adapter.instance.getDefaultReceptorX(lane, player),
255 | Adapter.instance.getDefaultReceptorY(lane, player)).add(ModchartUtil.getHalfPos());
256 |
257 | var vertices:openfl.Vector = new openfl.Vector(8 * HOLD_SUBDIVISIONS, true);
258 | var transfTotal:Array = [];
259 | transfTotal.resize(HOLD_SUBDIVISIONS);
260 | var tID = 0;
261 |
262 | var lastData:ArrowData = null;
263 | var lastSegment:Null = null;
264 |
265 | var depthApplied = false;
266 | var alphaTotal:Float = 0.;
267 |
268 | // refresh global mods percents
269 | __long = instance.getPercent('longHolds', player) - instance.getPercent('shortHolds', player) + 1;
270 |
271 | __rotateX = instance.getPercent('rotateHoldX', player);
272 | __rotateY = instance.getPercent('rotateHoldY', player);
273 | __rotateZ = instance.getPercent('rotateHoldZ', player);
274 |
275 | var getSegmentFunc = getHoldSegment;
276 |
277 | var vertPointer = 0;
278 |
279 | var subCr = ((Adapter.instance.getStaticCrochet() * .25) * ((Adapter.instance.isHoldEnd(item)) ? 0.6 : 1)) / HOLD_SUBDIVISIONS;
280 | for (sub in 0...HOLD_SUBDIVISIONS) {
281 | var subOff = subCr * sub;
282 |
283 | var out1 = lastSegment;
284 |
285 | if (out1 == null)
286 | out1 = getSegmentFunc(item, basePos, lastData != null ? lastData : getArrowParams(arrow, subOff));
287 | var out2 = getSegmentFunc(item, basePos, (lastData = getArrowParams(arrow, subOff + subCr)));
288 |
289 | lastSegment = out2;
290 |
291 | if (!depthApplied) {
292 | arrow._z = out1.depth;
293 | depthApplied = true;
294 | }
295 |
296 | alphaTotal = alphaTotal + out1.visuals.alpha;
297 |
298 | var vertPos = (vertPointer++) * 8;
299 | vertices[vertPos] = out1.left.x;
300 | vertices[vertPos + 1] = out1.left.y;
301 | vertices[vertPos + 2] = out1.right.x;
302 | vertices[vertPos + 3] = out1.right.y;
303 | vertices[vertPos + 4] = out2.left.x;
304 | vertices[vertPos + 5] = out2.left.y;
305 | vertices[vertPos + 6] = out2.right.x;
306 | vertices[vertPos + 7] = out2.right.y;
307 |
308 | final negGlow = 1 - out1.visuals.glow;
309 | final absGlow = out1.visuals.glow * 255;
310 | transfTotal[tID++] = new ColorTransform(negGlow, negGlow, negGlow, out1.visuals.alpha * arrow.alpha, Math.round(out1.visuals.glowR * absGlow),
311 | Math.round(out1.visuals.glowG * absGlow), Math.round(out1.visuals.glowB * absGlow));
312 | }
313 |
314 | newInstruction.item = item;
315 | newInstruction.vertices = vertices;
316 | newInstruction.indices = _indices.copy();
317 | newInstruction.uvt = ModchartUtil.getHoldUVT(arrow, HOLD_SUBDIVISIONS);
318 | newInstruction.colorData = transfTotal;
319 | newInstruction.extra = [alphaTotal];
320 |
321 | queue[count] = newInstruction;
322 | count++;
323 |
324 | __lastHoldSubs = Adapter.instance.getHoldSubdivisions();
325 | }
326 |
327 | override public function shift() {
328 | __drawInstruction(queue[postCount++]);
329 | }
330 |
331 | private function __drawInstruction(instruction:FMDrawInstruction) {
332 | if (cast(instruction.extra[0], Float) <= 0)
333 | return;
334 |
335 | final item:FlxSprite = instruction.item;
336 |
337 | var cameras = item._cameras != null ? item._cameras : Adapter.instance.getArrowCamera();
338 |
339 | @:privateAccess for (camera in cameras) {
340 | var cTransforms = instruction.colorData.copy();
341 |
342 | for (t in cTransforms)
343 | t.alphaMultiplier *= camera.alpha;
344 |
345 | var item = camera.startTrianglesBatch(item.graphic, item.antialiasing, true, item.blend, true, item.shader);
346 | item.addGradientTriangles(instruction.vertices, instruction.indices, instruction.uvt, new openfl.Vector(), null, camera._bounds, cTransforms);
347 | }
348 | }
349 |
350 | inline private function getArrowParams(arrow:FlxSprite, posOff:Float = 0):ArrowData {
351 | final player = Adapter.instance.getPlayerFromArrow(arrow);
352 | final lane = Adapter.instance.getLaneFromArrow(arrow);
353 |
354 | final centered2 = instance.getPercent('centered2', player);
355 | final timeC2 = flixel.FlxG.height * 0.25 * centered2;
356 | final hitTime = Adapter.instance.getTimeFromArrow(arrow);
357 |
358 | var pos = (hitTime - Adapter.instance.getSongPosition()) + posOff;
359 |
360 | pos += timeC2;
361 |
362 | return {
363 | __holdSubdivisionOffset: posOff,
364 | hitTime: hitTime + posOff + timeC2,
365 | distance: pos,
366 | lane: lane,
367 | player: player,
368 | hitten: Adapter.instance.arrowHit(arrow),
369 | isTapArrow: true
370 | };
371 | }
372 | }
373 |
374 | class ModchartArrowRenderer extends ModchartRenderer {
375 | inline private function getGraphicVertices(planeWidth:Float, planeHeight:Float, flipX:Bool, flipY:Bool) {
376 | var x1 = flipX ? planeWidth : -planeWidth;
377 | var x2 = flipX ? -planeWidth : planeWidth;
378 | var y1 = flipY ? planeHeight : -planeHeight;
379 | var y2 = flipY ? -planeHeight : planeHeight;
380 |
381 | return [
382 | // top left
383 | x1,
384 | y1,
385 | // top right
386 | x2,
387 | y1,
388 | // bottom left
389 | x1,
390 | y2,
391 | // bottom right
392 | x2,
393 | y2
394 | ];
395 | }
396 |
397 | override public function prepare(arrow:FlxSprite) {
398 | final arrowPosition = helperVector;
399 |
400 | final player = Adapter.instance.getPlayerFromArrow(arrow);
401 |
402 | // setup the position
403 | var arrowTime = Adapter.instance.getTimeFromArrow(arrow);
404 | var songPos = Adapter.instance.getSongPosition();
405 | var arrowDiff = arrowTime - songPos;
406 |
407 | // apply centered 2 (aka centered path)
408 | if (Adapter.instance.isTapNote(arrow)) {
409 | arrowDiff += FlxG.height * 0.25 * instance.getPercent('centered2', player);
410 | } else {
411 | arrowTime = songPos + (FlxG.height * 0.25 * instance.getPercent('centered2', player));
412 | arrowDiff = arrowTime - songPos;
413 | }
414 | var arrowData:ArrowData = {
415 | hitTime: arrowTime,
416 | distance: arrowDiff,
417 | lane: Adapter.instance.getLaneFromArrow(arrow),
418 | player: player,
419 | isTapArrow: Adapter.instance.isTapNote(arrow)
420 | };
421 |
422 | arrowPosition.setTo(Adapter.instance.getDefaultReceptorX(arrowData.lane, arrowData.player) + Manager.ARROW_SIZEDIV2,
423 | Adapter.instance.getDefaultReceptorY(arrowData.lane, arrowData.player) + Manager.ARROW_SIZEDIV2, 0);
424 |
425 | final output = instance.modifiers.getPath(arrowPosition, arrowData);
426 | arrowPosition.copyFrom(output.pos.clone());
427 |
428 | // internal mods
429 | final orient = instance.getPercent('orient', arrowData.player);
430 | if (orient != 0) {
431 | final nextOutput = instance.modifiers.getPath(new Vector3D(Adapter.instance.getDefaultReceptorX(arrowData.lane, arrowData.player)
432 | + Manager.ARROW_SIZEDIV2,
433 | Adapter.instance.getDefaultReceptorY(arrowData.lane, arrowData.player)
434 | + Manager.ARROW_SIZEDIV2),
435 | arrowData, 1, false, true);
436 | final thisPos = output.pos;
437 | final nextPos = nextOutput.pos;
438 |
439 | output.visuals.angleZ += FlxAngle.wrapAngle((-90 + (Math.atan2(nextPos.y - thisPos.y, nextPos.x - thisPos.x) * FlxAngle.TO_DEG)) * orient);
440 | }
441 |
442 | // prepare the instruction for drawing
443 | final projectionDepth = arrowPosition.z;
444 | final depth = projectionDepth;
445 |
446 | var depthScale = 1 / depth;
447 | var planeWidth = arrow.frame.frame.width * arrow.scale.x * .5;
448 | var planeHeight = arrow.frame.frame.height * arrow.scale.y * .5;
449 |
450 | arrow._z = (depth - 1) * 1000;
451 |
452 | var planeVertices = getGraphicVertices(planeWidth, planeHeight, arrow.flipX, arrow.flipY);
453 | var projectionZ:haxe.ds.Vector = new haxe.ds.Vector(Math.ceil(planeVertices.length / 2));
454 |
455 | var vertPointer = 0;
456 | @:privateAccess do {
457 | rotationVector.setTo(planeVertices[vertPointer], planeVertices[vertPointer + 1], 0);
458 |
459 | // The result of the vert rotation
460 | var rotation = ModchartUtil.rotate3DVector(rotationVector, output.visuals.angleX, output.visuals.angleY,
461 | ModchartUtil.getFrameAngle(arrow) + output.visuals.angleZ);
462 |
463 | // apply skewness
464 | if (output.visuals.skewX != 0 || output.visuals.skewY != 0) {
465 | __matrix.identity();
466 |
467 | __matrix.b = ModchartUtil.tan(output.visuals.skewY * FlxAngle.TO_RAD);
468 | __matrix.c = ModchartUtil.tan(output.visuals.skewX * FlxAngle.TO_RAD);
469 |
470 | rotation.x = __matrix.__transformX(rotation.x, rotation.y);
471 | rotation.y = __matrix.__transformY(rotation.x, rotation.y);
472 | }
473 | rotation.x = rotation.x * depthScale * output.visuals.scaleX;
474 | rotation.y = rotation.y * depthScale * output.visuals.scaleY;
475 |
476 | var view = new Vector3D(rotation.x + arrowPosition.x, rotation.y + arrowPosition.y, rotation.z);
477 | if (Config.CAMERA3D_ENABLED)
478 | view = instance.camera3D.applyViewTo(view);
479 | view.z *= 0.001;
480 |
481 | // The result of the perspective projection of rotation
482 | final projection = ModchartUtil.project(view);
483 |
484 | planeVertices[vertPointer] = projection.x;
485 | planeVertices[vertPointer + 1] = projection.y;
486 |
487 | // stores depth from this vert to use it for perspective correction on uv's
488 | projectionZ[Math.floor(vertPointer / 2)] = Math.max(0.0001, projection.z);
489 |
490 | vertPointer = vertPointer + 2;
491 | } while (vertPointer < planeVertices.length);
492 |
493 | // @formatter:off
494 | // this is confusing af
495 | var vertices = new DrawData(12, true, [
496 | // triangle 1
497 | planeVertices[0], planeVertices[1], // top left
498 | planeVertices[2], planeVertices[3], // top right
499 | planeVertices[6], planeVertices[7], // bottom left
500 | // triangle 2
501 | planeVertices[0], planeVertices[1], // top right
502 | planeVertices[4], planeVertices[5], // top left
503 | planeVertices[6], planeVertices[7] // bottom right
504 | ]);
505 | final uvRectangle = arrow.frame.uv;
506 | var uvData = new DrawData(18, true, [
507 | // uv for triangle 1
508 | uvRectangle.x, uvRectangle.y, 1 / projectionZ[0], // top left
509 | uvRectangle.width, uvRectangle.y, 1 / projectionZ[1], // top right
510 | uvRectangle.width, uvRectangle.height, 1 / projectionZ[3], // bottom left
511 | // uv for triangle 2
512 | uvRectangle.x, uvRectangle.y, 1 / projectionZ[0], // top right
513 | uvRectangle.x, uvRectangle.height, 1 / projectionZ[2], // top left
514 | uvRectangle.width, uvRectangle.height, 1 / projectionZ[3] // bottom right
515 | ]);
516 | // @formatter:on
517 | final absGlow = output.visuals.glow * 255;
518 | final negGlow = 1 - output.visuals.glow;
519 | var color = new ColorTransform(negGlow, negGlow, negGlow, arrow.alpha * output.visuals.alpha, Math.round(output.visuals.glowR * absGlow),
520 | Math.round(output.visuals.glowG * absGlow), Math.round(output.visuals.glowB * absGlow));
521 |
522 | // make the instruction
523 | var newInstruction:FMDrawInstruction = {};
524 | newInstruction.item = arrow;
525 | newInstruction.vertices = vertices;
526 | newInstruction.uvt = uvData;
527 | newInstruction.indices = new Vector(vertices.length, true, [for (i in 0...vertices.length) i]);
528 | newInstruction.colorData = [color];
529 | queue[count] = newInstruction;
530 |
531 | count++;
532 | }
533 |
534 | override public function shift() {
535 | __drawInstruction(queue[postCount++]);
536 | }
537 |
538 | /*
539 | override public function render(times:Null):Void
540 | {
541 | if (times == null)
542 | times = queue.length;
543 |
544 | var iterator = queue.iterator();
545 | var count = 0;
546 |
547 | @:privateAccess do {
548 | __drawInstruction(iterator.next());
549 |
550 | count++;
551 | } while (count < times && iterator.hasNext());
552 |
553 | queue = queue.splice(count, queue.length);
554 | }*/
555 | private function __drawInstruction(instruction:FMDrawInstruction) {
556 | if (instruction.colorData[0].alphaMultiplier <= 0)
557 | return;
558 |
559 | final item = instruction.item;
560 | final cameras = item._cameras != null ? item._cameras : Adapter.instance.getArrowCamera();
561 |
562 | @:privateAccess
563 | for (camera in cameras) {
564 | final cTransform = instruction.colorData[0];
565 | cTransform.alphaMultiplier *= camera.alpha;
566 |
567 | camera.drawTriangles(item.graphic, instruction.vertices, instruction.indices, instruction.uvt, new Vector(), null, item.blend, false,
568 | item.antialiasing, cTransform, item.shader);
569 | }
570 | }
571 | }
572 |
573 | class ModchartArrowPath extends ModchartRenderer {
574 | var __shape:Shape = new Shape();
575 | var __display:FlxSprite = new FlxSprite();
576 | var __queuedPoints:Array> = [];
577 | var __pathPoints:Vector = new Vector();
578 | var __pathCommands:Vector = new Vector();
579 |
580 | public function new(instance:PlayField) {
581 | super(instance);
582 |
583 | __display.makeGraphic(FlxG.width, FlxG.height, 0x00FFFFFF);
584 | }
585 |
586 | // the entry sprite should be A RECEPTOR / STRUM !!
587 | override public function prepare(item:FlxSprite) {
588 | final lane = Adapter.instance.getLaneFromArrow(item);
589 | final fn = Adapter.instance.getPlayerFromArrow(item);
590 |
591 | final alpha = instance.getPercent('arrowPathAlpha', fn);
592 | final thickness = 1 + Math.round(instance.getPercent('arrowPathThickness', fn));
593 |
594 | if (alpha <= 0 || thickness <= 0)
595 | return;
596 |
597 | final divisions = Math.round(60 / Math.max(1, instance.getPercent('arrowPathDivisions', fn)));
598 | final limit = 1500 * (1 + instance.getPercent('arrowPathLength', fn));
599 | final interval = limit / divisions;
600 |
601 | var moved = false;
602 |
603 | pathVector.setTo(Adapter.instance.getDefaultReceptorX(lane, fn), Adapter.instance.getDefaultReceptorY(lane, fn), 0);
604 | pathVector.incrementBy(ModchartUtil.getHalfPos());
605 |
606 | var pointData:Array> = [];
607 | pointData.resize(divisions);
608 |
609 | var pID = 0;
610 |
611 | var songPos = Adapter.instance.getSongPosition();
612 |
613 | for (sub in 0...divisions) {
614 | var hitTime = -500 + interval * sub;
615 |
616 | var output = instance.modifiers.getPath(pathVector.clone(), {
617 | hitTime: songPos + hitTime,
618 | distance: hitTime,
619 | lane: lane,
620 | player: fn,
621 | isTapArrow: true
622 | }, 0, false);
623 | final position = output.pos;
624 |
625 | /**
626 | * So it seems that if the lines are too far from the screen
627 | causes HORRIBLE memory leaks (from 60mb to 3gb-5gb in 2 seconds WHAT THE FUCK)
628 | */
629 | if ((position.x <= 0 - thickness - ARROW_PATH_BOUNDARY_OFFSET)
630 | || (position.x >= __display.pixels.rect.width + ARROW_PATH_BOUNDARY_OFFSET)
631 | || (position.y <= 0 - thickness - ARROW_PATH_BOUNDARY_OFFSET)
632 | || (position.y >= __display.pixels.rect.height + ARROW_PATH_BOUNDARY_OFFSET))
633 | continue;
634 |
635 | pointData[pID++] = [!moved, position.x, position.y];
636 |
637 | moved = true;
638 | }
639 |
640 | var newInstruction:FMDrawInstruction = {};
641 | newInstruction.mappedExtra = [
642 | 'style' => [thickness, 0xFFFFFFFF, alpha],
643 | 'position' => pointData,
644 | 'lane' => lane
645 | ];
646 |
647 | queue[count] = newInstruction;
648 | count++;
649 | }
650 |
651 | override public function shift() {
652 | if (queue.length <= 0)
653 | return;
654 |
655 | __pathPoints.splice(0, __pathPoints.length);
656 | __pathCommands.splice(0, __pathCommands.length);
657 | __shape.graphics.clear();
658 | __display.cameras = Adapter.instance.getArrowCamera();
659 |
660 | var lastLane = -1;
661 |
662 | for (i in 0...queue.length) {
663 | final instruction = queue[i];
664 | final thisLane = instruction.mappedExtra.get('lane');
665 |
666 | if (lastLane != thisLane) {
667 | final style = instruction.mappedExtra.get('style');
668 | __shape.graphics.lineStyle(style[0], style[1], style[2], false, NORMAL, ROUND, ROUND);
669 | }
670 |
671 | final steps = instruction.mappedExtra.get('position').iterator();
672 |
673 | var stepsHasNext = steps.hasNext;
674 | var stepsNext = steps.next;
675 |
676 | while (stepsHasNext()) {
677 | final thisStep = stepsNext();
678 |
679 | // in case the instruction is null (if the point is not visible in screen, we skip it)
680 | if (thisStep == null)
681 | continue;
682 |
683 | final needsToMove:Bool = cast thisStep[0];
684 | final posX:Float = cast thisStep[1];
685 | final posY:Float = cast thisStep[2];
686 |
687 | __pathCommands.push(needsToMove ? GraphicsPathCommand.MOVE_TO : GraphicsPathCommand.LINE_TO);
688 | __pathPoints.push(posX);
689 | __pathPoints.push(posY);
690 | }
691 |
692 | lastLane = thisLane;
693 | }
694 |
695 | __shape.graphics.drawPath(__pathCommands, __pathPoints);
696 |
697 | // then drawing the path pixels into the sprite pixels
698 | __display.pixels.fillRect(__display.pixels.rect, 0x00FFFFFF);
699 | __display.pixels.draw(__shape);
700 | // draw the sprite to the cam
701 | __display.draw();
702 | }
703 |
704 | override function dispose() {
705 | __display.destroy();
706 | __shape.graphics.clear();
707 | __pathPoints.splice(0, __pathPoints.length);
708 | __pathCommands.splice(0, __pathCommands.length);
709 | }
710 |
711 | inline static final ARROW_PATH_BOUNDARY_OFFSET:Float = 50;
712 | }
713 |
714 | // TODO: fix this, i have this class in private cuz it sucks and doesnt even draw anything
715 | // also should i call this actor frame ?
716 | class ModchartProxyRenderer extends ModchartRenderer {
717 | override public function prepare(cam:FlxCamera):Void {}
718 | }
719 |
720 | /*
721 | private class ModchartRenderCache {
722 | static var positionCache:ObjectMap;
723 |
724 | static function get(time:Float, lane:Int, player:Int):MCachedInfo
725 | return positionCache.get(buildInfo(time, lane, player));
726 |
727 | static function set(output:modchart.core.ModifierGroup.ModifierOutput, time:Float, lane:Int, player:Int):MCachedInfo
728 | positionCache.set(buildInfo(time, lane, player));
729 |
730 | static function buildInfo(time:Float, lane:Int, player:Int):MCachedInfo {
731 | return {
732 | time: time,
733 | lane: lane,
734 | player: player
735 | };
736 | }
737 |
738 | static function dispose() {
739 | for (out in positionCache) {
740 | out.visuals = null;
741 | out.pos = null;
742 | }
743 | positionCache.clear();
744 | positionCache = null;
745 | }
746 | }
747 |
748 | typedef MCachedInfo = {
749 | time:Float,
750 | lane:Int,
751 | player:Int
752 | };*/
753 | @:publicFields
754 | @:structInit
755 | class FMDrawInstruction {
756 | var item:FlxSprite;
757 | var vertices:openfl.Vector;
758 | var uvt:openfl.Vector;
759 | var indices:openfl.Vector;
760 | var colorData:Array;
761 |
762 | var extra:Array;
763 | var mappedExtra:Map;
764 |
765 | public function new() {}
766 | }
767 |
--------------------------------------------------------------------------------
/modchart/core/macros/Macro.macro.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.macros;
2 |
3 | import haxe.macro.Compiler;
4 | import haxe.macro.Context;
5 | import haxe.macro.Expr.FieldType;
6 | import haxe.macro.Expr;
7 |
8 | class Macro {
9 | public static function includeFiles() {
10 | Compiler.include('modchart', true, ['modchart.standalone.adapters']);
11 | Compiler.include("modchart.standalone.adapters." + haxe.macro.Context.definedValue("FM_ENGINE").toLowerCase());
12 | }
13 |
14 | public static function addModchartStorage():Array {
15 | final fields = Context.getBuildFields();
16 | final pos = Context.currentPos();
17 |
18 | for (f in fields) {
19 | if (f.name == 'set_visible') {
20 | switch (f.kind) {
21 | case FFun(fun):
22 | fun.expr = macro {
23 | visible = value;
24 | _fmVisible = value;
25 |
26 | return value;
27 | };
28 | default:
29 | // do nothing
30 | }
31 | }
32 | }
33 |
34 | // uses _z to prevent collisions with other classes
35 | final zField:Field = {
36 | name: "_z",
37 | access: [APublic],
38 | kind: FieldType.FVar(macro :Float, macro $v{0}),
39 | pos: pos
40 | };
41 | final visField:Field = {
42 | name: "_fmVisible",
43 | access: [APublic],
44 | kind: FieldType.FVar(macro :Null, macro true),
45 | pos: pos
46 | };
47 |
48 | fields.push(zField);
49 | fields.push(visField);
50 |
51 | return fields;
52 | }
53 |
54 | public static function buildFlxCamera():Array {
55 | var fields = Context.getBuildFields();
56 |
57 | // idk why when i dont change the general draw items pooling system, theres so much graphic issues (with colors and uvs)
58 | /*
59 | var newField:Field = {
60 | name: '__fmStartTrianglesBatch',
61 | pos: Context.currentPos(),
62 | access: [APrivate],
63 | kind: FFun({
64 | args: [
65 | {
66 | name: "graphic",
67 | type: macro :flixel.graphics.FlxGraphic
68 | },
69 | {
70 | name: "blend",
71 | type: macro :openfl.display.BlendMode
72 | },
73 | {
74 | name: "shader",
75 | type: macro :flixel.system.FlxAssets.FlxShader
76 | },
77 | {
78 | name: "antialiasing",
79 | type: macro :Bool,
80 | value: macro $v{false}
81 | }
82 | ],
83 | expr: macro {
84 | return getNewDrawTrianglesItem(graphic, antialiasing, true, blend, true, shader);
85 | },
86 | ret: macro :flixel.graphics.tile.FlxDrawTrianglesItem
87 | })
88 | };
89 | fields.push(newField);
90 | */
91 |
92 | for (f in fields) {
93 | if (f.name == 'startTrianglesBatch') {
94 | switch (f.kind) {
95 | case FFun(fun):
96 | // we're just removing a if statement cuz causes some color issues
97 | fun.expr = macro {
98 | return getNewDrawTrianglesItem(graphic, smoothing, isColored, blend, hasColorOffsets, shader);
99 | };
100 | default:
101 | // do nothing
102 | }
103 | }
104 | }
105 |
106 | return fields;
107 | }
108 |
109 | public static function buildFlxDrawTrianglesItem():Array {
110 | var fields = Context.getBuildFields();
111 | var newField:Field = {
112 | name: 'addGradientTriangles',
113 | pos: Context.currentPos(),
114 | access: [APublic],
115 | kind: FieldType.FFun({
116 | args: [
117 | {
118 | name: 'vertices',
119 | type: macro :DrawData
120 | },
121 | {
122 | name: 'indices',
123 | type: macro :DrawData
124 | },
125 | {
126 | name: 'uvtData',
127 | type: macro :DrawData
128 | },
129 | {
130 | name: 'colors',
131 | type: macro :DrawData,
132 | opt: true
133 | },
134 | {
135 | name: 'position',
136 | type: macro :FlxPoint,
137 | opt: true
138 | },
139 | {
140 | name: 'cameraBounds',
141 | type: macro :FlxRect,
142 | opt: true
143 | },
144 | {
145 | name: 'transforms',
146 | type: macro :Array,
147 | opt: true
148 | }
149 | ],
150 | expr: macro {
151 | if (position == null)
152 | position = point.set();
153 |
154 | if (cameraBounds == null)
155 | cameraBounds = rect.set(0, 0, FlxG.width, FlxG.height);
156 |
157 | var verticesLength:Int = vertices.length;
158 | var prevVerticesLength:Int = this.vertices.length;
159 | var numberOfVertices:Int = Std.int(verticesLength / 2);
160 | var prevIndicesLength:Int = this.indices.length;
161 | var prevUVTDataLength:Int = this.uvtData.length;
162 | var prevColorsLength:Int = this.colors.length;
163 | var prevNumberOfVertices:Int = this.numVertices;
164 |
165 | var tempX:Float, tempY:Float;
166 | var i:Int = 0;
167 | var currentVertexPosition:Int = prevVerticesLength;
168 |
169 | while (i < verticesLength) {
170 | tempX = position.x + vertices[i];
171 | tempY = position.y + vertices[i + 1];
172 |
173 | this.vertices[currentVertexPosition++] = tempX;
174 | this.vertices[currentVertexPosition++] = tempY;
175 |
176 | if (i == 0) {
177 | bounds.set(tempX, tempY, 0, 0);
178 | } else {
179 | inflateBounds(bounds, tempX, tempY);
180 | }
181 |
182 | i = i + 2;
183 | }
184 |
185 | var indicesLength:Int = indices.length;
186 | if (!cameraBounds.overlaps(bounds)) {
187 | this.vertices.splice(this.vertices.length - verticesLength, verticesLength);
188 | } else {
189 | var uvtDataLength:Int = uvtData.length;
190 | for (i in 0...uvtDataLength) {
191 | this.uvtData[prevUVTDataLength + i] = uvtData[i];
192 | }
193 |
194 | for (i in 0...indicesLength) {
195 | this.indices[prevIndicesLength + i] = indices[i] + prevNumberOfVertices;
196 | }
197 |
198 | if (colored) {
199 | for (i in 0...numberOfVertices) {
200 | this.colors[prevColorsLength + i] = colors[i];
201 | }
202 |
203 | colorsPosition = colorsPosition + numberOfVertices;
204 | }
205 |
206 | verticesPosition = verticesPosition + verticesLength;
207 | indicesPosition = indicesPosition + indicesLength;
208 | }
209 |
210 | position.putWeak();
211 | cameraBounds.putWeak();
212 |
213 | final indDiv = (1 / indicesLength);
214 |
215 | var curAlphas = [];
216 | curAlphas.resize(indicesLength);
217 | var j = 0;
218 |
219 | for (_ in 0...indicesLength) {
220 | final possibleTransform = transforms[Std.int(_ * indDiv * transforms.length)];
221 |
222 | var alphaMultiplier = 1.;
223 |
224 | if (possibleTransform != null)
225 | alphaMultiplier = possibleTransform.alphaMultiplier;
226 |
227 | curAlphas[j++] = alphaMultiplier;
228 | }
229 |
230 | alphas = alphas.concat(curAlphas);
231 |
232 | if (colored || hasColorOffsets) {
233 | if (colorMultipliers == null)
234 | colorMultipliers = [];
235 |
236 | if (colorOffsets == null)
237 | colorOffsets = [];
238 |
239 | var curMultipliers = [];
240 | var curOffsets = [];
241 |
242 | var multCount = 0;
243 | var offCount = 0;
244 |
245 | curMultipliers.resize(indicesLength * (3 + 1));
246 | curOffsets.resize(indicesLength * 4);
247 |
248 | for (_ in 0...indicesLength) {
249 | final transform = transforms[Std.int(_ * indDiv * transforms.length)];
250 | if (transform != null) {
251 | curMultipliers[multCount + 0] = transform.redMultiplier;
252 | curMultipliers[multCount + 1] = transform.greenMultiplier;
253 | curMultipliers[multCount + 2] = transform.blueMultiplier;
254 |
255 | curOffsets[offCount + 0] = transform.redOffset;
256 | curOffsets[offCount + 1] = transform.greenOffset;
257 | curOffsets[offCount + 2] = transform.blueOffset;
258 | curOffsets[offCount + 3] = transform.alphaOffset;
259 | } else {
260 | curMultipliers[multCount + 0] = 1;
261 | curMultipliers[multCount + 1] = 1;
262 | curMultipliers[multCount + 2] = 1;
263 |
264 | curOffsets[offCount + 0] = 0;
265 | curOffsets[offCount + 1] = 0;
266 | curOffsets[offCount + 2] = 0;
267 | curOffsets[offCount + 3] = 0;
268 | }
269 |
270 | curMultipliers[multCount + 3] = 1;
271 |
272 | multCount = multCount + (3 + 1);
273 | offCount = offCount + 4;
274 | }
275 |
276 | colorMultipliers = colorMultipliers.concat(curMultipliers);
277 | colorOffsets = colorOffsets.concat(curOffsets);
278 | }
279 | }
280 | }),
281 | };
282 |
283 | fields.push(newField);
284 |
285 | return fields;
286 | }
287 | /*
288 | public static function generateModList()
289 | {
290 | Context.onAfterInitMacros(() -> {
291 | final modifierList:Array> = Type.resolveClass('CompileTime').getClassList('modchart.modifiers', true, modchart.Modifier);
292 | final mappedModifiers:Map> = [];
293 |
294 | for (i in 0...modifierList.length)
295 | {
296 | final modifierClass = modifierList[i];
297 |
298 | final meta = Meta.getType(modifierClass);
299 | if (meta != null)
300 | {
301 | Context.info('$meta', Context.currentPos());
302 | }
303 |
304 | var modifierName = Type.getClassName(modifierClass);
305 | modifierName = modifierName.substring(modifierName.lastIndexOf('.') + 1);
306 | mappedModifiers[modifierName.toLowerCase()] = modifierClass;
307 | }
308 |
309 | Constants.MODIFIER_LIST = mappedModifiers;
310 |
311 | Context.info('---- Modifiers Founded ----\n$mappedModifiers');
312 | });
313 | }*/
314 | }
315 |
--------------------------------------------------------------------------------
/modchart/core/util/Constants.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.util;
2 |
3 | class Constants {}
4 |
5 | enum abstract RotationOrder(String) from String to String {
6 | final X_Y_Z = "x_y_z";
7 | final X_Z_Y = "x_z_y";
8 | final Y_X_Z = "y_x_z";
9 | final Y_Z_X = "y_z_x";
10 | final Z_X_Y = "z_x_y";
11 | final Z_Y_X = "z_y_x";
12 |
13 | final X_Y_X = "x_y_x";
14 | final X_Z_X = "x_z_x";
15 | final Y_X_Y = "y_x_y";
16 | final Y_Z_Y = "y_z_y";
17 | final Z_X_Z = "z_x_z";
18 | final Z_Y_Z = "z_y_z";
19 | }
20 |
21 | @:structInit
22 | class RenderParams {
23 | public var songTime:Float;
24 | public var hitTime:Float;
25 | public var distance:Float;
26 | public var curBeat:Float;
27 |
28 | public var lane:Int = 0;
29 | public var player:Int = 0;
30 | public var isTapArrow:Bool = false;
31 | }
32 |
33 | @:structInit
34 | class ArrowData {
35 | public var hitTime:Float = 0;
36 | public var distance:Float = 0;
37 |
38 | public var lane:Int = 0;
39 | public var player:Int = 0;
40 |
41 | public var hitten:Bool = false;
42 | public var isTapArrow:Bool = false;
43 |
44 | private var __holdSubdivisionOffset:Float = .0;
45 | }
46 |
47 | @:structInit
48 | class Visuals {
49 | public var scaleX:Float = 1;
50 | public var scaleY:Float = 1;
51 | public var alpha:Float = 1;
52 | public var glow:Float = 0;
53 | public var glowR:Float = 1;
54 | public var glowG:Float = 1;
55 | public var glowB:Float = 1;
56 | public var angleX:Float = 0;
57 | public var angleY:Float = 0;
58 | public var angleZ:Float = 0;
59 | public var skewX:Float = 0;
60 | public var skewY:Float = 0;
61 | }
62 |
63 | @:publicFields
64 | @:structInit
65 | class HoldSegment {
66 | var origin:Vector3D;
67 | var left:Vector3D;
68 | var right:Vector3D;
69 | }
70 |
71 | @:publicFields
72 | @:structInit
73 | class Node {
74 | public var input:Array = [];
75 | public var output:Array = [];
76 | public var func:NodeFunction = (_, o) -> _;
77 | }
78 |
79 | @:publicFields
80 | @:structInit
81 | class ModAlias {
82 | public var parent:String;
83 | public var alias:String;
84 | }
85 |
86 | /*
87 | abstract ModScheme(Dynamic) from Dynamic from String from Array to Dynamic {
88 | public inline function get():ModScheme
89 | {
90 | return this is String ? [this] : this;
91 | }
92 | }
93 | */
94 | // (InputModPercents, PlayerNumber) -> OutputModPercents
95 | typedef NodeFunction = (Array, Int) -> Array;
96 |
--------------------------------------------------------------------------------
/modchart/core/util/ModchartUtil.hx:
--------------------------------------------------------------------------------
1 | package modchart.core.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 haxe.ds.Vector;
9 | import modchart.core.Quanterion;
10 | import openfl.geom.Matrix3D;
11 | import openfl.geom.Vector3D;
12 |
13 | using StringTools;
14 |
15 | @:keep class ModchartUtil {
16 | // pain (we need this if we want support for sprite sheet packer)
17 | inline public static function getFrameAngle(spr:FlxSprite):Float {
18 | return switch (spr.frame.angle) {
19 | case ANGLE_90: 90;
20 | case ANGLE_NEG_90: -90;
21 | case ANGLE_270: 270;
22 | default: 0; // ANGLE_0
23 | }
24 | }
25 |
26 | // NO MORE GIMBAL LOCK
27 | inline public static function rotate3DVector(vec:Vector3D, angleX:Float, angleY:Float, angleZ:Float):Vector3D {
28 | if (angleX == 0 && angleY == 0 && angleZ == 0)
29 | return vec;
30 |
31 | final RAD = FlxAngle.TO_RAD;
32 | final quatX = Quaternion.fromAxisAngle(Vector3D.X_AXIS, angleX * RAD);
33 | final quatY = Quaternion.fromAxisAngle(Vector3D.Y_AXIS, angleY * RAD);
34 | final quatZ = Quaternion.fromAxisAngle(Vector3D.Z_AXIS, angleZ * RAD);
35 |
36 | var finalQuat:Quaternion;
37 | // pain
38 | switch (Config.ROTATION_ORDER) {
39 | case X_Y_Z:
40 | finalQuat = quatX.multiply(quatY.multiply(quatZ));
41 | case X_Z_Y:
42 | finalQuat = quatX.multiply(quatZ.multiply(quatY));
43 | case Y_X_Z:
44 | finalQuat = quatY.multiply(quatX.multiply(quatZ));
45 | case Y_Z_X:
46 | finalQuat = quatY.multiply(quatZ.multiply(quatX));
47 | case Z_X_Y:
48 | finalQuat = quatZ.multiply(quatX.multiply(quatY));
49 | case Z_Y_X:
50 | finalQuat = quatZ.multiply(quatY.multiply(quatX));
51 | case X_Y_X:
52 | finalQuat = quatX.multiply(quatY.multiply(quatX));
53 | case X_Z_X:
54 | finalQuat = quatX.multiply(quatZ.multiply(quatX));
55 | case Y_X_Y:
56 | finalQuat = quatY.multiply(quatX.multiply(quatY));
57 | case Y_Z_Y:
58 | finalQuat = quatY.multiply(quatZ.multiply(quatY));
59 | case Z_X_Z:
60 | finalQuat = quatZ.multiply(quatX.multiply(quatZ));
61 | case Z_Y_Z:
62 | finalQuat = quatZ.multiply(quatY.multiply(quatZ));
63 | }
64 | return finalQuat.rotateVector(vec);
65 | }
66 |
67 | // 90 degrees as default
68 | static var fov:Float = Math.PI / 2;
69 | static var near:Int = 0;
70 | static var far:Int = 1;
71 | static var range:Int = -1;
72 |
73 | // Based on schmovin's perspective projection math
74 | inline static public function project(pos:Vector3D, ?origin:Vector3D) {
75 | final fov = Math.PI / 2;
76 |
77 | var originalZ = pos.z;
78 |
79 | if (origin == null)
80 | origin = new Vector3D(FlxG.width / 2, FlxG.height / 2);
81 | pos.decrementBy(origin);
82 |
83 | final worldZ = Math.min(pos.z - 1, 0); // bound to 1000 z
84 |
85 | final halfFovTan = 1 / ModchartUtil.tan(fov * .5);
86 | final rangeDivision = 1 / range;
87 |
88 | final projectionScale = (near + far) * rangeDivision;
89 | final projectionOffset = 2 * near * (far * rangeDivision);
90 | final projectionZ = projectionScale * worldZ + projectionOffset;
91 |
92 | final projectedPos = new Vector3D(pos.x * halfFovTan, pos.y * halfFovTan, projectionZ * projectionZ, projectionZ);
93 | projectedPos.project();
94 | projectedPos.incrementBy(origin);
95 |
96 | projectedPos.w = (projectedPos.z - 1) * 1000;
97 | return projectedPos;
98 | }
99 |
100 | inline static public function buildHoldVertices(upper:Array, lower:Array) {
101 | return [
102 | upper[0].x, upper[0].y,
103 | upper[1].x, upper[1].y,
104 | lower[0].x, lower[0].y,
105 | lower[1].x, lower[1].y
106 | ];
107 | }
108 |
109 | inline static public function getHoldUVT(arrow:FlxSprite, subs:Int) {
110 | var frameAngle = -ModchartUtil.getFrameAngle(arrow);
111 |
112 | var uv = new DrawData(8 * subs, true, []);
113 |
114 | var frameUV = arrow.frame.uv;
115 | var frameWidth = frameUV.width - frameUV.x;
116 | var frameHeight = frameUV.height - frameUV.y;
117 |
118 | var subDivided = 1.0 / subs;
119 |
120 | // if the frame doesnt have rotation, we skip the rotated uv shit
121 | if ((frameAngle % 360) == 0) {
122 | for (curSub in 0...subs) {
123 | var uvOffset = subDivided * curSub;
124 | var subIndex = curSub * 8;
125 |
126 | uv[subIndex] = uv[subIndex + 4] = frameUV.x;
127 | uv[subIndex + 2] = uv[subIndex + 6] = frameUV.width;
128 | uv[subIndex + 1] = uv[subIndex + 3] = frameUV.y + uvOffset * frameHeight;
129 | uv[subIndex + 5] = uv[subIndex + 7] = frameUV.y + (uvOffset + subDivided) * frameHeight;
130 | }
131 | return uv;
132 | }
133 |
134 | var angleRad = frameAngle * (Math.PI / 180);
135 | var cosA = ModchartUtil.cos(angleRad);
136 | var sinA = ModchartUtil.sin(angleRad);
137 |
138 | var uCenter = frameUV.x + frameWidth * .5;
139 | var vCenter = frameUV.y + frameHeight * .5;
140 |
141 | // my brain is not braining anymore
142 | // i give up
143 | for (curSub in 0...subs) {
144 | var uvOffset = subDivided * curSub;
145 | var subIndex = curSub * 8;
146 |
147 | // uv coords before rotation
148 | var uvCoords = [
149 | [frameUV.x, frameUV.y + uvOffset * frameHeight], // tl
150 | [frameUV.width, frameUV.y + uvOffset * frameHeight], // tr
151 | [frameUV.x, frameUV.y + (uvOffset + subDivided) * frameHeight], // bl
152 | [frameUV.width, frameUV.y + (uvOffset + subDivided) * frameHeight] // br
153 | ];
154 |
155 | // apply rotation
156 | for (i in 0...4) {
157 | var u = uvCoords[i][0] - uCenter; // x
158 | var v = uvCoords[i][1] - vCenter; // y
159 |
160 | var uRot = u * cosA - v * sinA;
161 | var vRot = u * sinA + v * cosA;
162 |
163 | uv[subIndex + i * 2] = uRot + uCenter;
164 | uv[subIndex + i * 2 + 1] = vRot + vCenter;
165 | }
166 | }
167 |
168 | return uv;
169 | }
170 |
171 | // gonna keep this shits inline cus are basic functions
172 | public static inline function getHalfPos():Vector3D {
173 | return new Vector3D(Manager.ARROW_SIZEDIV2, Manager.ARROW_SIZEDIV2, 0, 0);
174 | }
175 |
176 | public inline static function sign(x:Int)
177 | return x == 0 ? 0 : x > 0 ? 1 : -1;
178 |
179 | public inline static function clamp(n:Float, l:Float, h:Float) {
180 | if (n < l)
181 | return l;
182 | if (n > h)
183 | return h;
184 | return n;
185 | }
186 |
187 | public static inline function sin(num:Float)
188 | return FlxMath.fastSin(num);
189 |
190 | public static inline function cos(num:Float)
191 | return FlxMath.fastCos(num);
192 |
193 | public static inline function tan(num:Float)
194 | return sin(num) / cos(num);
195 |
196 | inline public static function lerpVector3D(start:Vector3D, end:Vector3D, ratio:Float) {
197 | final diff = end.subtract(start);
198 | diff.scaleBy(ratio);
199 |
200 | return start.add(diff);
201 | }
202 |
203 | public static function coolTextFile(path:String):Array {
204 | var trim:String;
205 | return [
206 | for (line in openfl.utils.Assets.getText(path).split("\n"))
207 | if ((trim = line.trim()) != "" && !trim.startsWith("#")) trim
208 | ];
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/modchart/events/Event.hx:
--------------------------------------------------------------------------------
1 | package modchart.events;
2 |
3 | import modchart.Manager;
4 |
5 | class Event {
6 | public var name:String;
7 | public var target:Float;
8 |
9 | public var beat:Float;
10 | public var player:Int;
11 |
12 | public var callback:Event->Void;
13 | public var parent:EventManager;
14 |
15 | private var mercy:Bool = false;
16 |
17 | public var fired:Bool = false;
18 |
19 | public var active:Bool = false;
20 |
21 | public function new(beat:Float, callback:Event->Void, parent:EventManager, ?mercy:Bool = false) {
22 | this.beat = beat;
23 | this.callback = callback;
24 | this.mercy = mercy;
25 |
26 | this.parent = parent;
27 | }
28 |
29 | public function update(curBeat:Float) {
30 | if (curBeat >= beat && callback != null) {
31 | callback(this);
32 |
33 | fired = !mercy;
34 |
35 | if (fired)
36 | callback = null;
37 | }
38 | }
39 |
40 | public function create() {}
41 |
42 | public inline function setModPercent(name, value, player) {
43 | parent.pf.setPercent(name, value, player);
44 | }
45 |
46 | public inline function getModPercent(name, player):Float {
47 | return parent.pf.getPercent(name, player);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/modchart/events/EventManager.hx:
--------------------------------------------------------------------------------
1 | package modchart.events;
2 |
3 | import haxe.ds.StringMap;
4 | import modchart.core.PlayField;
5 | import modchart.core.util.ModchartUtil;
6 | import modchart.events.types.*;
7 |
8 | @:allow(modchart.events.Event)
9 | class EventManager {
10 | private var table:StringMap>> = new StringMap();
11 | private var eventList:Array = [];
12 |
13 | private var pf:PlayField;
14 |
15 | public function new(pf:PlayField) {
16 | this.pf = pf;
17 | }
18 |
19 | public function add(event:Event) {
20 | if (event.name != null) {
21 | final lwr = event.name.toLowerCase();
22 | var player = event.player;
23 |
24 | var entry = table.get(lwr);
25 | if (entry == null)
26 | table.set(lwr, entry = []);
27 | if (entry[player] == null)
28 | entry[player] = [];
29 |
30 | entry[player].push(event);
31 | }
32 |
33 | eventList.push(event);
34 |
35 | sortEvents();
36 | }
37 |
38 | public function update(curBeat:Float) {
39 | for (ev in eventList) {
40 | ev.active = false;
41 |
42 | if (ev.beat >= curBeat)
43 | continue;
44 |
45 | ev.active = true;
46 | ev.update(curBeat);
47 | }
48 | }
49 |
50 | public function getLastEventBefore(event:Event):Event {
51 | final playerEvents = table.get(event.name.toLowerCase());
52 | if (playerEvents == null) {
53 | return null;
54 | }
55 |
56 | final eventList = playerEvents[event.player];
57 | if (eventList == null) {
58 | return null;
59 | }
60 |
61 | final lastIndex = eventList.indexOf(event);
62 | if (lastIndex > 0) {
63 | final possibleEvent = eventList[lastIndex - 1];
64 | return possibleEvent != null ? possibleEvent : null;
65 | }
66 |
67 | return null;
68 | }
69 |
70 | private function sortEvents() {
71 | for (modTab in table.iterator()) {
72 | if (modTab == null)
73 | continue;
74 | for (events in modTab)
75 | if (events != null && events.length > 0)
76 | events.sort(__sortFunction);
77 | }
78 | eventList.sort(__sortFunction);
79 | }
80 |
81 | @:noCompletion
82 | private final __sortFunction:(Event, Event) -> Int = (a, b) -> {
83 | return Math.floor(a.beat - b.beat);
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/modchart/events/types/AddEvent.hx:
--------------------------------------------------------------------------------
1 | package modchart.events.types;
2 |
3 | import flixel.math.FlxMath;
4 | import flixel.tweens.FlxEase.EaseFunction;
5 | import flixel.tweens.FlxEase;
6 | import modchart.events.Event;
7 |
8 | typedef AddData = {
9 | var startBeat:Float;
10 | var endBeat:Float;
11 |
12 | var beatLength:Float;
13 |
14 | var ease:EaseFunction;
15 | }
16 |
17 | class AddEvent extends Event {
18 | public var data:AddData;
19 |
20 | public function new(mod:String, beat:Float, len:Float, target:Float, ease:EaseFunction, player:Int, parent:EventManager) {
21 | this.name = mod;
22 | this.player = player;
23 |
24 | this.data = {
25 | startBeat: beat,
26 | endBeat: beat + len,
27 | beatLength: len,
28 | ease: ease != null ? ease : FlxEase.linear
29 | };
30 |
31 | this.target = target;
32 |
33 | super(beat, (_) -> {}, parent, true);
34 | }
35 |
36 | var additionPerc:Null = null;
37 |
38 | override function update(curBeat:Float) {
39 | if (fired)
40 | return;
41 |
42 | if (curBeat < data.endBeat) {
43 | if (additionPerc == null)
44 | additionPerc = getModPercent(name, player);
45 |
46 | var progress = (curBeat - data.startBeat) / (data.endBeat - data.startBeat);
47 | // maybe we should make it use bound?
48 | var out = FlxMath.lerp(additionPerc, additionPerc + target, data.ease(progress)); // it "adds" value to the perc instead of rewrite it
49 | setModPercent(name, out, player);
50 | fired = false;
51 | } else if (curBeat >= data.endBeat) {
52 | fired = true;
53 |
54 | // we're using the ease function bc it may dont return 1
55 | setModPercent(name, data.ease(1) * (additionPerc + target), player);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/modchart/events/types/EaseEvent.hx:
--------------------------------------------------------------------------------
1 | package modchart.events.types;
2 |
3 | import flixel.math.FlxMath;
4 | import flixel.tweens.FlxEase.EaseFunction;
5 | import flixel.tweens.FlxEase;
6 | import modchart.events.Event;
7 |
8 | typedef EaseData = {
9 | var startBeat:Float;
10 | var endBeat:Float;
11 |
12 | var beatLength:Float;
13 |
14 | var ease:EaseFunction;
15 | }
16 |
17 | class EaseEvent extends Event {
18 | public var data:EaseData;
19 |
20 | public function new(mod:String, beat:Float, len:Float, target:Float, ease:EaseFunction, player:Int, parent:EventManager) {
21 | this.name = mod;
22 | this.player = player;
23 |
24 | this.data = {
25 | startBeat: beat,
26 | endBeat: beat + len,
27 | beatLength: len,
28 | ease: ease != null ? ease : FlxEase.linear
29 | };
30 |
31 | this.target = target;
32 |
33 | super(beat, (_) -> {}, parent, true);
34 | }
35 |
36 | var entryPerc:Null = null;
37 |
38 | override function update(curBeat:Float) {
39 | if (fired)
40 | return;
41 |
42 | if (curBeat < data.endBeat) {
43 | if (entryPerc == null) {
44 | // this fixed A LOT of visual issues when convining eases and sets
45 | // based on schmovin timeline
46 | final possibleLastEvent = parent.getLastEventBefore(this);
47 |
48 | if (possibleLastEvent != null)
49 | entryPerc = possibleLastEvent.target;
50 | else
51 | entryPerc = getModPercent(name, player);
52 | }
53 |
54 | var progress = (curBeat - data.startBeat) / (data.endBeat - data.startBeat);
55 | // maybe we should make it use bound?
56 | var out = FlxMath.lerp(entryPerc, target, data.ease(progress));
57 | setModPercent(name, out, player);
58 | fired = false;
59 | } else if (curBeat >= data.endBeat) {
60 | fired = true;
61 |
62 | // we're using the ease function bc it may dont return 1
63 | setModPercent(name, data.ease(1) * target, player);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/modchart/events/types/RepeaterEvent.hx:
--------------------------------------------------------------------------------
1 | package modchart.events.types;
2 |
3 | import modchart.events.Event;
4 |
5 | class RepeaterEvent extends Event {
6 | var end:Float;
7 |
8 | public function new(beat:Float, length:Float, callback:Event->Void, parent:EventManager) {
9 | super(beat, callback, parent);
10 |
11 | end = beat + length;
12 | }
13 |
14 | override function update(curBeat:Float):Void {
15 | if (fired)
16 | return;
17 |
18 | if (curBeat < end) {
19 | callback(this);
20 | fired = false;
21 | } else if (curBeat >= end) {
22 | fired = true;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/modchart/events/types/SetEvent.hx:
--------------------------------------------------------------------------------
1 | package modchart.events.types;
2 |
3 | import modchart.events.Event;
4 |
5 | class SetEvent extends Event {
6 | public function new(mod:String, beat:Float, target:Float, player:Int, parent:EventManager) {
7 | this.name = mod;
8 | this.target = target;
9 | this.player = player;
10 |
11 | super(beat, (_) -> {
12 | setModPercent(mod, target, player);
13 | }, parent);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/modchart/import.hx:
--------------------------------------------------------------------------------
1 | #if !macro
2 | import modchart.Config;
3 | import modchart.Manager;
4 | import modchart.Modifier;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import modchart.core.util.Constants.Visuals;
8 | import modchart.core.util.Constants;
9 | import modchart.core.util.ModchartUtil;
10 | import modchart.events.Event;
11 | import modchart.standalone.Adapter;
12 | import openfl.geom.Vector3D;
13 | #end
14 |
--------------------------------------------------------------------------------
/modchart/modifiers/ArrowShape.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.PlayField;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.modifiers.PathModifier.PathNode;
7 | import openfl.geom.Vector3D;
8 |
9 | class ArrowShape extends PathModifier {
10 | public function new(pf:PlayField) {
11 | var path:Array = [];
12 |
13 | for (line in ModchartUtil.coolTextFile('assets/modchart/arrowShape.csv')) {
14 | var coords = line.split(';');
15 | path.push({
16 | x: Std.parseFloat(coords[0]) * 200,
17 | y: Std.parseFloat(coords[1]) * 200,
18 | z: Std.parseFloat(coords[2]) * 200
19 | });
20 | }
21 |
22 | super(pf, path);
23 |
24 | pathOffset.setTo(WIDTH * 0.5, HEIGHT * 0.5 + 280, 0);
25 | }
26 |
27 | override function render(pos:Vector3D, params:RenderParams) {
28 | var perc = getPercent('arrowshape', params.player);
29 |
30 | if (perc == 0)
31 | return pos;
32 |
33 | pathOffset.z = pos.z;
34 | return computePath(pos, params, perc);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modchart/modifiers/Beat.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import flixel.tweens.FlxEase;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import openfl.geom.Vector3D;
8 |
9 | class Beat extends Modifier {
10 | public function new(pf) {
11 | super(pf);
12 |
13 | var stuff = ['', 'x', 'y', 'z'];
14 | for (i in 0...stuff.length) {
15 | setPercent('beat' + stuff[i] + 'speed', 1, -1);
16 | setPercent('beat' + stuff[i] + 'mult', 1, -1);
17 | setPercent('beat' + stuff[i] + 'offset', 0, -1);
18 | setPercent('beat' + stuff[i] + 'alternate', 1, -1);
19 | }
20 | }
21 |
22 | function beatMath(params:RenderParams, offset:Float, speed:Float, mult:Float, alternate:Float):Float {
23 | var fAccelTime = 0.2;
24 | var fTotalTime = 0.5;
25 |
26 | var timmy:Float = (params.curBeat + offset) * speed;
27 |
28 | var posMult:Float = mult * 2; // Multiplied by 2 to make the effect more pronounced instead of being like drunk-lite lmao
29 |
30 | var curBeat = timmy + fAccelTime;
31 | var bEvenBeat = (Math.floor(curBeat) % 2) != 0;
32 |
33 | if (curBeat < 0)
34 | return 0;
35 |
36 | curBeat -= Math.floor(curBeat);
37 | curBeat += 1;
38 | curBeat -= Math.floor(curBeat);
39 |
40 | if (curBeat >= fTotalTime)
41 | return 0;
42 |
43 | var fAmount:Float;
44 |
45 | if (curBeat < fAccelTime) {
46 | fAmount = FlxMath.remapToRange(curBeat, 0.0, fAccelTime, 0.0, 1.0);
47 | fAmount *= fAmount;
48 | } else
49 | /* curBeat < fTotalTime */ {
50 | fAmount = FlxMath.remapToRange(curBeat, fAccelTime, fTotalTime, 1.0, 0.0);
51 | fAmount = 1 - (1 - fAmount) * (1 - fAmount);
52 | }
53 |
54 | if (bEvenBeat && alternate >= 0.5)
55 | fAmount *= -1;
56 |
57 | var fShift = 20.0 * fAmount * sin((params.distance * 0.01 * posMult) + (Math.PI / 2.0));
58 | return fShift;
59 | }
60 |
61 | function doBeat(curPos:Vector3D, params:RenderParams, axis:String, realAxis:String) {
62 | final receptorName = Std.string(params.lane);
63 | final player = params.player;
64 | var distance = params.distance;
65 |
66 | var offset = getPercent('beat' + axis + 'Offset', player) + getPercent('beat' + axis + receptorName + 'Offset', player);
67 | var speed = getPercent('beat' + axis + 'Speed', player) + getPercent('beat' + axis + receptorName + 'Speed', player);
68 | var mult = getPercent('beat' + axis + 'Mult', player) + getPercent('beat' + axis + receptorName + 'Mult', player);
69 | var alternate = getPercent('beat' + axis + 'Alternate', player) + getPercent('beat' + axis + receptorName + 'Alternate', player);
70 |
71 | var shift = 0.;
72 |
73 | shift += beatMath(params, offset, speed, mult, alternate) * (getPercent('beat' + axis, player) + getPercent('beat' + axis + receptorName, player));
74 |
75 | switch (realAxis) {
76 | case 'x':
77 | curPos.x += shift;
78 | case 'y':
79 | curPos.y += shift;
80 | case 'z':
81 | curPos.z += shift;
82 | }
83 | }
84 |
85 | override public function render(curPos:Vector3D, params:RenderParams) {
86 | doBeat(curPos, params, '', 'x');
87 | doBeat(curPos, params, 'x', 'x');
88 | doBeat(curPos, params, 'y', 'y');
89 | doBeat(curPos, params, 'z', 'z');
90 |
91 | return curPos;
92 | }
93 |
94 | override public function shouldRun(params:RenderParams):Bool
95 | return true;
96 | }
97 |
--------------------------------------------------------------------------------
/modchart/modifiers/Boost.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import flixel.tweens.FlxEase;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import modchart.core.util.ModchartUtil;
8 | import openfl.geom.Vector3D;
9 |
10 | class Boost extends Modifier {
11 | public function new(pf) {
12 | super(pf);
13 |
14 | setPercent('waveMult', 1, -1);
15 | }
16 |
17 | static final DIV38 = 1 / 38;
18 |
19 | override public function render(curPos:Vector3D, params:RenderParams) {
20 | var player = params.player;
21 | var lane = Std.string(params.lane);
22 |
23 | final boost = (getPercent('boost', params.player) + getPercent('boost' + lane, params.player));
24 | final brake = (getPercent('brake', params.player) + getPercent('brake' + lane, params.player));
25 | final wave = (getPercent('wave', params.player) + getPercent('wave' + lane, params.player));
26 |
27 | if (boost != 0) {
28 | // Accelerate / Boost
29 | final scale = HEIGHT * (1 + (getPercent('boostScale', player)));
30 | final off = params.distance * 1.5 / ((params.distance + (scale) / 1.2) / scale);
31 | curPos.y += ModchartUtil.clamp(boost * (off - params.distance), -600, 600);
32 | }
33 | if (brake != 0) {
34 | // Decelerate / Brake
35 | final scale2 = HEIGHT * (1 + getPercent('brakeScale', player));
36 |
37 | var off2 = params.distance * 1.5 / ((params.distance + (scale2) / 1.2) / scale2);
38 | curPos.y += ModchartUtil.clamp(-brake * (off2 - params.distance), -600, 600);
39 | }
40 | if (wave != 0) {
41 | curPos.y += (-wave * 100) * sin(params.distance * DIV38 * getPercent('waveMult', player) * 0.2);
42 | }
43 |
44 | return curPos;
45 | }
46 |
47 | override public function shouldRun(params:RenderParams):Bool
48 | return true;
49 | }
50 |
--------------------------------------------------------------------------------
/modchart/modifiers/Bounce.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Bounce extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var player = params.player;
10 | var speed = getPercent('bounceSpeed', player);
11 | var offset = getPercent('bounceOffset', player);
12 |
13 | var bounce = Math.abs(sin((params.curBeat + offset) * (1 + speed) * Math.PI)) * ARROW_SIZE;
14 |
15 | curPos.x += bounce * getPercent('bounceX', player);
16 | curPos.y += bounce * (getPercent('bounce', player) + getPercent('bounceY', player));
17 | curPos.z += bounce * getPercent('bounceZ', player);
18 |
19 | return curPos;
20 | }
21 |
22 | override public function shouldRun(params:RenderParams):Bool
23 | return true;
24 | }
25 |
--------------------------------------------------------------------------------
/modchart/modifiers/Bumpy.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Bumpy extends Modifier {
8 | public function new(pf) {
9 | super(pf);
10 |
11 | var stuff = ['', 'Angle'];
12 | for (i in 0...stuff.length) {
13 | setPercent('bumpy' + stuff[i] + 'Mult', 1, -1);
14 | setPercent('bumpy' + stuff[i] + 'XMult', 1, -1);
15 | setPercent('bumpy' + stuff[i] + 'YMult', 1, -1);
16 | setPercent('bumpy' + stuff[i] + 'ZMult', 1, -1);
17 | }
18 | }
19 |
20 | function applyBumpy(curPos:Vector3D, params:RenderParams, axis:String, realAxis:String) {
21 | final receptorName = Std.string(params.lane);
22 | final player = params.player;
23 | var distance = params.distance;
24 |
25 | var offset = getPercent('bumpy' + axis + 'Offset', player) + getPercent('bumpy' + axis + receptorName + 'Offset', player);
26 | var period = getPercent('bumpy' + axis + 'Period', player) + getPercent('bumpy' + axis + receptorName + 'Period', player);
27 | var mult = getPercent('bumpy' + axis + 'Mult', player) + getPercent('bumpy' + axis + receptorName + 'Mult', player);
28 |
29 | var shift = 0.;
30 |
31 | var scrollSpeed = getScrollSpeed();
32 |
33 | var bumpyMath = 40 * sin(((distance * 0.01) + (100.0 * offset) / ((period * (mult * 24.0)) +
34 | 24.0)) / ((scrollSpeed * mult) / 2)) * (getKeyCount() / 2.0);
35 |
36 | // var bumpyMath = (40 * sin((distance + (100.0 * offset)) / ((period * (mult*24.0)) + 24.0)));
37 |
38 | shift += (getPercent('bumpy' + axis, player) + getPercent('bumpy' + axis + receptorName, player)) * bumpyMath;
39 |
40 | switch (realAxis) {
41 | case 'x':
42 | curPos.x += shift;
43 | case 'y':
44 | curPos.y += shift;
45 | case 'z':
46 | curPos.z += shift;
47 | }
48 | }
49 |
50 | public function applyAngle(vis:Visuals, params:RenderParams, axis:String, realAxis:String) {
51 | final receptorName = Std.string(params.lane);
52 | final player = params.player;
53 | var distance = params.distance;
54 |
55 | var offset = getPercent('bumpyAngle' + axis + 'Offset', player) + getPercent('bumpyAngle' + axis + receptorName + 'Offset', player);
56 | var period = getPercent('bumpyAngle' + axis + 'Period', player) + getPercent('bumpyAngle' + axis + receptorName + 'Period', player);
57 | var mult = getPercent('bumpyAngle' + axis + 'Mult', player) + getPercent('bumpyAngle' + axis + receptorName + 'Mult', player);
58 |
59 | var shift = 0.;
60 |
61 | var scrollSpeed = getScrollSpeed();
62 |
63 | var bumpyMath = 40 * sin(((distance * 0.01) + (100.0 * offset) / ((period * (mult * 24.0)) +
64 | 24.0)) / ((scrollSpeed * mult) / 2)) * (getKeyCount() / 2.0);
65 |
66 | shift += (getPercent('bumpyAngle' + axis, player) + getPercent('bumpyAngle' + axis + receptorName, player)) * bumpyMath;
67 |
68 | switch (realAxis) {
69 | case 'x':
70 | vis.angleX += shift;
71 | case 'y':
72 | vis.angleY += shift;
73 | case 'z':
74 | vis.angleZ += shift;
75 | }
76 | }
77 |
78 | override public function render(curPos:Vector3D, params:RenderParams) {
79 | // var player = params.player;
80 | // var distance = params.distance;
81 | // var bumpyX = (40 * sin((distance + (100.0 * getPercent('bumpyXOffset', player))) / ((getPercent('bumpyXPeriod', player) * 24.0) + 24.0)));
82 | // var bumpyY = (40 * sin((distance + (100.0 * getPercent('bumpyYOffset', player))) / ((getPercent('bumpyYPeriod', player) * 24.0) + 24.0)));
83 | // var bumpyZ = (40 * sin((distance + (100.0 * getPercent('bumpyZOffset', player))) / ((getPercent('bumpyZPeriod', player) * 24.0) + 24.0)));
84 |
85 | // curPos.x += bumpyX * getPercent('bumpyX', player);
86 | // curPos.y += bumpyY * getPercent('bumpyY', player);
87 | // curPos.z += bumpyZ * (getPercent('bumpy', player) + getPercent('bumpyZ', player));
88 |
89 | applyBumpy(curPos, params, '', 'z');
90 | applyBumpy(curPos, params, 'x', 'x');
91 | applyBumpy(curPos, params, 'y', 'y');
92 | applyBumpy(curPos, params, 'z', 'z');
93 |
94 | return curPos;
95 | }
96 |
97 | override public function visuals(data:Visuals, params:RenderParams) {
98 | applyAngle(data, params, '', 'z');
99 | applyAngle(data, params, 'x', 'x');
100 | applyAngle(data, params, 'y', 'y');
101 | applyAngle(data, params, 'z', 'z');
102 |
103 | return data;
104 | }
105 |
106 | override public function shouldRun(params:RenderParams):Bool
107 | return true;
108 | }
109 |
--------------------------------------------------------------------------------
/modchart/modifiers/CenterRotate.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class CenterRotate extends Rotate {
10 | override public function getOrigin(curPos:Vector3D, params:RenderParams):Vector3D {
11 | return new Vector3D(FlxG.width * 0.5, HEIGHT * 0.5);
12 | }
13 |
14 | override public function getRotateName():String
15 | return 'centerRotate';
16 |
17 | override public function shouldRun(params:RenderParams):Bool
18 | return true;
19 | }
20 |
--------------------------------------------------------------------------------
/modchart/modifiers/Confusion.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.Constants.Visuals;
6 | import openfl.geom.Vector3D;
7 |
8 | class Confusion extends Modifier {
9 | static final dNames = ['x' => 'roll', 'y' => 'twirl', 'z' => 'dizzy'];
10 |
11 | public function applyConfusion(vis:Visuals, params:RenderParams, axis:String, realAxis:String) {
12 | final receptorName = Std.string(params.lane);
13 | final player = params.player;
14 |
15 | var angle = 0.;
16 | // real confusion
17 | angle -= (params.curBeat * (getPercent('confusion' + axis, player) + getPercent('confusion' + axis + receptorName, player))) % 360;
18 | // offset
19 | angle += getPercent('confusionOffset' + axis, player) + getPercent('confusionOffset' + axis + receptorName, player);
20 | // dizzy mods
21 | final cName = dNames.get(realAxis);
22 | angle += getPercent(cName, player) * (params.distance * 0.1 * (1 + getPercent('${cName}Speed', player)));
23 |
24 | switch (realAxis) {
25 | case 'x':
26 | vis.angleX += angle;
27 | case 'y':
28 | vis.angleY += angle;
29 | case 'z':
30 | vis.angleZ += angle;
31 | }
32 | }
33 |
34 | override public function visuals(data:Visuals, params:RenderParams) {
35 | applyConfusion(data, params, '', 'z');
36 | applyConfusion(data, params, 'x', 'x');
37 | applyConfusion(data, params, 'y', 'y');
38 | applyConfusion(data, params, 'z', 'z');
39 |
40 | return data;
41 | }
42 |
43 | override public function shouldRun(params:RenderParams):Bool
44 | return true;
45 | }
46 |
--------------------------------------------------------------------------------
/modchart/modifiers/Drugged.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.Constants.Visuals;
7 | import modchart.core.util.ModchartUtil;
8 | import openfl.geom.Vector3D;
9 |
10 | class Drugged extends Modifier {
11 | override public function render(curPos:Vector3D, params:RenderParams) {
12 | var amplitude = 1.;
13 | var frequency = 1.;
14 |
15 | var x = (params.distance * 0.009) + (params.lane * 0.125);
16 | var y = 0.;
17 | y = sin(x * frequency);
18 | var t = 0.01 * (-Adapter.instance.getSongPosition() * 0.0025 * 130.0);
19 | y += sin(x * frequency * 2.1 + t) * 4.5;
20 | y += sin(x * frequency * 1.72 + t * 1.121) * 4.0;
21 | y += sin(x * frequency * 2.221 + t * 0.437) * 5.0;
22 | y += sin(x * frequency * 3.1122 + t * 4.269) * 2.5;
23 | y *= amplitude * 0.06;
24 |
25 | curPos.x += y * getPercent('drugged', params.player) * ARROW_SIZE * 0.8;
26 |
27 | return curPos;
28 | }
29 |
30 | override public function visuals(visuals:Visuals, params:RenderParams) {
31 | var drug = getPercent('drugged', params.player);
32 |
33 | var amplitude = 1.;
34 | var frequency = 1.;
35 |
36 | var x = (params.distance * 0.025) + (params.lane * 0.3);
37 | var y = 0.;
38 | y = sin(x * frequency);
39 | var t = 0.01 * (-Adapter.instance.getSongPosition() * 0.005 * 130.0);
40 | y += sin(x * frequency * 2.1 + t) * 4.5;
41 | y += sin(x * frequency * 1.72 + t * 1.121) * 4.0;
42 | y += sin(x * frequency * 2.221 + t * 0.437) * 5.0;
43 | y += sin(x * frequency * 3.1122 + t * 4.269) * 2.5;
44 | y *= amplitude * 0.06;
45 |
46 | y = -FlxMath.bound(y, -1, 1);
47 |
48 | var squishX = 1 + FlxMath.bound(y, -1, 0) * -1 * 0.6;
49 | var squishY = 1 + FlxMath.bound(y, 0, 1) * 0.6;
50 |
51 | visuals.scaleX *= squishX * drug;
52 | visuals.scaleY *= squishY * drug;
53 |
54 | var preproduct = Math.asin(y);
55 | // var cosdY = cos(preproduct);
56 |
57 | visuals.glow = y * -.7;
58 | visuals.glowR -= 0.5 + sin(preproduct * 1.4) * .5;
59 | visuals.glowG += 0.4 + cos(preproduct * 0.5) * .6;
60 | visuals.glowB -= 0.2 + tan(preproduct) * .8;
61 |
62 | return visuals;
63 |
64 | // curPos.x += y * getPercent('drugged', params.player);
65 | }
66 |
67 | override public function shouldRun(params:RenderParams):Bool
68 | return true;
69 | }
70 |
--------------------------------------------------------------------------------
/modchart/modifiers/Drunk.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Drunk extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var player = params.player;
10 | var speed = getPercent('drunkSpeed', player);
11 | var period = getPercent('drunkPeriod', player);
12 | var offset = getPercent('drunkOffset', player);
13 |
14 | var shift = params.songTime * 0.001 * (1 + speed) + params.lane * ((offset * 0.2) + 0.2) + params.distance * ((period * 10) + 10) / HEIGHT;
15 | var drunk = (cos(shift) * ARROW_SIZE * 0.5);
16 |
17 | curPos.x += drunk * (getPercent('drunk', player) + getPercent('drunkX', player));
18 | curPos.y += drunk * getPercent('drunkY', player);
19 | curPos.z += drunk * getPercent('drunkZ', player);
20 |
21 | return curPos;
22 | }
23 |
24 | override public function shouldRun(params:RenderParams):Bool
25 | return true;
26 | }
27 |
--------------------------------------------------------------------------------
/modchart/modifiers/FieldRotate.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class FieldRotate extends Rotate {
10 | override public function getOrigin(curPos:Vector3D, params:RenderParams):Vector3D {
11 | var x:Float = (WIDTH * 0.5) - ARROW_SIZE - 54 + ARROW_SIZE * 1.5;
12 | switch (params.player) {
13 | case 0:
14 | x -= WIDTH * 0.5 - ARROW_SIZE * 2 - 100;
15 | case 1:
16 | x += WIDTH * 0.5 - ARROW_SIZE * 2 - 100;
17 | }
18 | x -= 56;
19 |
20 | return new Vector3D(x, HEIGHT * 0.5);
21 | }
22 |
23 | override public function getRotateName():String
24 | return 'fieldRotate';
25 |
26 | override public function shouldRun(params:RenderParams):Bool
27 | return true;
28 | }
29 |
--------------------------------------------------------------------------------
/modchart/modifiers/Infinite.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class Infinite extends Modifier {
10 | override function render(pos:Vector3D, params:RenderParams) {
11 | var perc = getPercent('infinite', params.player);
12 |
13 | if (perc == 0)
14 | return pos;
15 |
16 | var infinite = new Vector3D();
17 |
18 | // alternate the angles
19 | var rat = params.lane % 2 == 0 ? 1 : -1;
20 | // adding 45° so the arrows ends at the end
21 | var fTime = (-params.distance * Math.PI * 0.001) + rat * Math.PI / 2;
22 | // used for make the curve
23 | final invTransf = (2 / (3 - cos(fTime * 2)));
24 |
25 | // apply the scroll
26 | infinite.setTo(WIDTH * .5 + invTransf * cos(fTime) * 580, HEIGHT * .5 + invTransf * (sin(fTime * 2) * .5) * 750, 0);
27 |
28 | return ModchartUtil.lerpVector3D(pos, infinite, perc);
29 | }
30 |
31 | override public function shouldRun(params:RenderParams):Bool
32 | return true;
33 | }
34 |
--------------------------------------------------------------------------------
/modchart/modifiers/Invert.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Invert extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | final player = params.player;
10 | final invert = -(params.lane % 2 - 0.5) / 0.5;
11 | final flip = (params.lane - 1.5) * -2;
12 | final sine = sin(params.distance * Math.PI * (1 / 222));
13 |
14 | curPos.x += ARROW_SIZE * (invert * getPercent('invert', player) + invert * (getPercent('invertSine', player) * sine)
15 | + flip * getPercent('flip', player) + flip * (getPercent('flipSine', player) * sine));
16 |
17 | return curPos;
18 | }
19 |
20 | override public function shouldRun(params:RenderParams):Bool
21 | return true;
22 | }
23 |
--------------------------------------------------------------------------------
/modchart/modifiers/OpponentSwap.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.ModchartUtil;
6 | import openfl.geom.Vector3D;
7 |
8 | class OpponentSwap extends Modifier {
9 | override public function render(curPos:Vector3D, params:RenderParams) {
10 | final player = params.player;
11 | final perc = getPercent('opponentSwap', player);
12 |
13 | if (perc == 0)
14 | return curPos;
15 |
16 | var distX = WIDTH * .5;
17 | curPos.x -= distX * ModchartUtil.sign((player + 1) * 2 - 3) * perc;
18 | return curPos;
19 | }
20 |
21 | override public function shouldRun(params:RenderParams):Bool
22 | return true;
23 | }
24 |
--------------------------------------------------------------------------------
/modchart/modifiers/PathModifier.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import haxe.ds.Vector;
5 | import modchart.core.PlayField;
6 | import modchart.core.util.Constants.ArrowData;
7 | import modchart.core.util.Constants.RenderParams;
8 | import modchart.core.util.ModchartUtil;
9 | import openfl.geom.Vector3D;
10 |
11 | /**
12 | * An Path Manager for FunkinModchart
13 | *
14 | * @author TheoDev
15 | */
16 | @:skipModifier
17 | class PathModifier extends Modifier {
18 | private var __path:Vector;
19 | private var __pathBound:Float = 1500;
20 |
21 | public var pathOffset:Vector3D = new Vector3D();
22 |
23 | public function new(pf:PlayField, path:Array) {
24 | super(pf);
25 |
26 | loadPath(path);
27 | }
28 |
29 | public function loadPath(newPath:Array) {
30 | __path = Vector.fromArrayCopy(newPath);
31 | }
32 |
33 | public function computePath(pos:Vector3D, params:RenderParams, percent:Float) {
34 | var __path_length = __path.length;
35 | if (__path_length <= 0)
36 | return pos;
37 | if (__path_length == 1) {
38 | var pathNode = __path[0];
39 | return new Vector3D(pathNode.x, pathNode.y, pathNode.z);
40 | }
41 |
42 | var nodeProgress = (__path_length - 1) * (Math.abs(Math.min(__pathBound, params.distance)) * (1 / __pathBound));
43 | var thisNodeIndex = Math.floor(nodeProgress);
44 | var nextNodeIndex = Math.floor(Math.min(thisNodeIndex + 1, __path_length - 1));
45 | var nextNodeRatio = nodeProgress - thisNodeIndex;
46 |
47 | var thisNode = __path[thisNodeIndex];
48 | var nextNode = __path[nextNodeIndex];
49 |
50 | return ModchartUtil.lerpVector3D(pos,
51 | new Vector3D(FlxMath.lerp(thisNode.x, nextNode.x, nextNodeRatio), FlxMath.lerp(thisNode.y, nextNode.y, nextNodeRatio),
52 | FlxMath.lerp(thisNode.z, nextNode.z, nextNodeRatio)).add(pathOffset),
53 | percent);
54 | }
55 |
56 | override public function shouldRun(params:RenderParams):Bool
57 | return true;
58 | }
59 |
60 | @:structInit
61 | class PathNode {
62 | public var x:Float;
63 | public var y:Float;
64 | public var z:Float;
65 | }
66 |
--------------------------------------------------------------------------------
/modchart/modifiers/Radionic.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.Constants.Visuals;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | // Circular motion based on the lane.
10 | // Naming this `Radionic` since it seems like a Radionic Graphic.
11 | // Inspired by `The Poenix NotITG Modchart` at 0:35
12 | // Warning!: This should be AFTER regular modifiers (drunk, beat, transform, etc) and BEFORE rotation modifiers.
13 | class Radionic extends Modifier {
14 | override public function render(pos:Vector3D, params:RenderParams) {
15 | final perc = getPercent('radionic', params.player);
16 |
17 | if (perc == 0)
18 | return pos;
19 |
20 | final reverse = getManager().modifiers.modifiers.get('reverse');
21 |
22 | final angle = ((1 / Adapter.instance.getStaticCrochet()) * ((params.songTime + params.distance) * Math.PI * .25) + (Math.PI * params.player));
23 | final offsetX = pos.x - getReceptorX(params.lane, params.player);
24 | final offsetY = reverse != null ? (pos.y - reverse.render(pos, params).y) : 0;
25 |
26 | final circf = ARROW_SIZE + params.lane * ARROW_SIZE;
27 |
28 | final sinAng = sin(angle);
29 | final cosAng = cos(angle);
30 |
31 | final radionicVec = new Vector3D();
32 |
33 | radionicVec.x = WIDTH * 0.5 + ((sinAng * offsetY + cosAng * (circf + offsetX)) * 0.7) * 1.125;
34 | radionicVec.y = HEIGHT * 0.5 + ((cosAng * offsetY + sinAng * (circf + offsetX)) * 0.7) * 0.875;
35 | radionicVec.z = pos.z;
36 |
37 | return ModchartUtil.lerpVector3D(pos, radionicVec, perc);
38 | }
39 |
40 | // should i include this?
41 | // nah i will do this manually
42 |
43 | /*
44 | override public function visuals(data:Visuals, params:RenderParams):Visuals
45 | {
46 | final perc = getPercent('radionic', params.player);
47 | final amount = 0.6;
48 |
49 | vis.scaleX = perc * (vis.scaleY = 1 + amount - FlxEase.cubeOut((params.curBeat - Math.floor(params.curBeat))) * amount);
50 | vis.glow = perc * (-(amount - FlxEase.cubeOut((params.curBeat - Math.floor(params.curBeat))) * amount) * 2);
51 |
52 | return vis;
53 | }*/
54 | override public function shouldRun(params:RenderParams):Bool
55 | return true;
56 | }
57 |
--------------------------------------------------------------------------------
/modchart/modifiers/ReceptorScroll.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import flixel.math.FlxMath;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import modchart.core.util.Constants.Visuals;
8 | import modchart.core.util.ModchartUtil;
9 | import openfl.geom.Vector3D;
10 |
11 | class ReceptorScroll extends Modifier {
12 | override public function render(curPos:Vector3D, params:RenderParams) {
13 | final perc = getPercent('receptorScroll', params.player);
14 |
15 | if (perc == 0)
16 | return curPos;
17 |
18 | final moveSpeed = Adapter.instance.getStaticCrochet() * 4;
19 |
20 | var diff = -params.distance;
21 | var songTime = Adapter.instance.getSongPosition();
22 | var vDiff = -(diff - songTime) / moveSpeed;
23 | var reversed = Math.floor(vDiff) % 2 == 0;
24 |
25 | var startY = curPos.y;
26 | var revPerc = reversed ? 1 - vDiff % 1 : vDiff % 1;
27 | // haha perc 30
28 | var upscrollOffset = 50;
29 | var downscrollOffset = HEIGHT - 150;
30 |
31 | var endY = upscrollOffset + ((downscrollOffset - ARROW_SIZEDIV2) * revPerc) + ARROW_SIZEDIV2;
32 |
33 | curPos.y = FlxMath.lerp(startY, endY, perc);
34 | return curPos;
35 | }
36 |
37 | override public function visuals(data:Visuals, params:RenderParams):Visuals {
38 | final perc = getPercent('receptorScroll', params.player);
39 | if (perc == 0)
40 | return data;
41 |
42 | var bar = params.songTime / (Adapter.instance.getStaticCrochet() * .25);
43 | var hitTime = params.distance;
44 |
45 | data.alpha = FlxMath.bound((1400 - hitTime) / 200, 0, 0.3) * perc;
46 | if ((params.distance + params.songTime) < Math.floor(bar + 1) * Adapter.instance.getStaticCrochet() * 4)
47 | data.alpha = 1;
48 |
49 | return data;
50 | }
51 |
52 | override public function shouldRun(params:RenderParams):Bool
53 | return true;
54 | }
55 |
--------------------------------------------------------------------------------
/modchart/modifiers/Reverse.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import flixel.math.FlxMath;
5 | import modchart.Manager;
6 | import modchart.core.util.Constants.ArrowData;
7 | import modchart.core.util.Constants.RenderParams;
8 | import modchart.core.util.ModchartUtil;
9 | import openfl.geom.Vector3D;
10 |
11 | // Default modifier
12 | // Handles scroll speed, scroll angle and reverse modifiers
13 | class Reverse extends Modifier {
14 | public function new(pf) {
15 | super(pf);
16 |
17 | setPercent('xmod', 1, -1);
18 | }
19 |
20 | public function getReverseValue(dir:Int, player:Int) {
21 | var kNum = getKeyCount();
22 | var val:Float = 0;
23 | if (dir >= Math.floor(kNum * 0.5))
24 | val = val + getPercent("split", player);
25 |
26 | if ((dir % 2) == 1)
27 | val = val + getPercent("alternate", player);
28 |
29 | var first = kNum * 0.25;
30 | var last = kNum - 1 - first;
31 |
32 | if (dir >= first && dir <= last)
33 | val = val + getPercent("cross", player);
34 |
35 | val = val + getPercent('reverse', player) + getPercent("reverse" + Std.string(dir), player);
36 |
37 | if (getPercent("unboundedReverse", player) == 0) {
38 | val %= 2;
39 | if (val > 1)
40 | val = 2 - val;
41 | }
42 |
43 | // downscroll
44 | if (Adapter.instance.getDownscroll())
45 | val = 1 - val;
46 | return val;
47 | }
48 |
49 | override public function render(curPos:Vector3D, params:RenderParams) {
50 | var player = params.player;
51 | var initialY = Adapter.instance.getDefaultReceptorY(params.lane, player) + ARROW_SIZEDIV2;
52 | var reversePerc = getReverseValue(params.lane, player);
53 | var shift = FlxMath.lerp(initialY, HEIGHT - initialY, reversePerc);
54 |
55 | var centerPercent = getPercent('centered', params.player);
56 | shift = FlxMath.lerp(shift, (HEIGHT * 0.5) - ARROW_SIZEDIV2, centerPercent);
57 |
58 | // TODO: long, straight and short holds
59 | var distance = params.distance;
60 |
61 | distance *= 0.45 * Adapter.instance.getCurrentScrollSpeed();
62 |
63 | var scroll = new Vector3D(0, FlxMath.lerp(distance, -distance, reversePerc));
64 | scroll = applyScrollMods(scroll, params);
65 |
66 | curPos.x = curPos.x + scroll.x;
67 | curPos.y = shift + scroll.y;
68 | curPos.z = curPos.z + scroll.z;
69 |
70 | return curPos;
71 | }
72 |
73 | function applyScrollMods(scroll:Vector3D, params:RenderParams) {
74 | var player = params.player;
75 | var angleX = 0.;
76 | var angleY = 0.;
77 | var angleZ = 0.;
78 |
79 | // Speed
80 | scroll.y = scroll.y * (getPercent('xmod', player))
81 | + (1 + getPercent('scrollSpeed', player) + getPercent('scrollSpeed' + Std.string(params.lane), player));
82 |
83 | // Main
84 | angleX = angleX + getPercent('scrollAngleX', player);
85 | angleY = angleY + getPercent('scrollAngleY', player);
86 | angleZ = angleZ + getPercent('scrollAngleZ', player);
87 |
88 | // Curved
89 | final shift:Float = params.distance * 0.25 * (1 + getPercent('curvedScrollPeriod', player));
90 |
91 | angleX = angleX + shift * getPercent('curvedScrollX', player);
92 | angleY = angleY + shift * getPercent('curvedScrollY', player);
93 | angleZ = angleZ + shift * getPercent('curvedScrollZ', player);
94 |
95 | // angleY doesnt do anything if angleX and angleZ are disabled
96 | if (angleX == 0 && angleZ == 0)
97 | return scroll;
98 |
99 | scroll = ModchartUtil.rotate3DVector(scroll, angleX, angleY, angleZ);
100 |
101 | return scroll;
102 | }
103 |
104 | override public function shouldRun(params:RenderParams):Bool
105 | return true;
106 | }
107 |
--------------------------------------------------------------------------------
/modchart/modifiers/Rotate.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class Rotate extends Modifier {
10 | override public function render(curPos:Vector3D, params:RenderParams) {
11 | var rotateName = getRotateName();
12 | var player = params.player;
13 |
14 | var angleX = getPercent(rotateName + 'X', player);
15 | var angleY = getPercent(rotateName + 'Y', player);
16 | var angleZ = getPercent(rotateName + 'Z', player);
17 |
18 | // does angleY work here if angleX and angleZ are disabled? - ye
19 | if (angleX == 0 && angleY == 0 && angleZ == 0)
20 | return curPos;
21 |
22 | final origin:Vector3D = getOrigin(curPos, params);
23 | final diff = curPos.subtract(origin);
24 | final out = ModchartUtil.rotate3DVector(diff, angleX, angleY, angleZ);
25 | curPos.copyFrom(origin.add(out));
26 | return curPos;
27 | }
28 |
29 | public function getOrigin(curPos:Vector3D, params:RenderParams):Vector3D {
30 | var fixedLane = Math.round(getKeyCount(params.player) * .5);
31 | return new Vector3D(getReceptorX(fixedLane, params.player), getReceptorY(fixedLane, params.player) + ARROW_SIZEDIV2);
32 | }
33 |
34 | public function getRotateName():String
35 | return 'rotate';
36 |
37 | override public function shouldRun(params:RenderParams):Bool
38 | return true;
39 | }
40 |
--------------------------------------------------------------------------------
/modchart/modifiers/SawTooth.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class SawTooth extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var player = params.player;
10 | final period = 1 + getPercent("sawtoothPeriod", player);
11 | curPos.x += (getPercent('sawtooth',
12 | player) * ARROW_SIZE) * ((0.5 / period * params.distance) / ARROW_SIZE - Math.floor((0.5 / period * params.distance) / ARROW_SIZE));
13 |
14 | return curPos;
15 | }
16 |
17 | override public function shouldRun(params:RenderParams):Bool
18 | return true;
19 | }
20 |
--------------------------------------------------------------------------------
/modchart/modifiers/Scale.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.Constants.Visuals;
6 | import openfl.geom.Vector3D;
7 |
8 | class Scale extends Modifier {
9 | public function new(pf) {
10 | super(pf);
11 |
12 | setPercent('scale', 1, -1);
13 | setPercent('scaleX', 1, -1);
14 | setPercent('scaleY', 1, -1);
15 | }
16 |
17 | public function applyScale(vis:Visuals, params:RenderParams, axis:String, realAxis:String) {
18 | var receptorName = Std.string(params.lane);
19 | var player = params.player;
20 |
21 | var scale = 1.;
22 | var tinyPow = Math.pow(0.5, getPercent('tinyPow', player)); // does not need "lane" variant
23 |
24 | // Scale
25 | scale *= getPercent('scale' + axis, player) + getPercent('scale' + axis + receptorName, player);
26 |
27 | // NotITG Scale (aka Tiny)
28 | scale *= Math.pow(0.5, getPercent('tiny' + axis, player) + getPercent('tiny' + axis + receptorName, player)) * tinyPow;
29 |
30 | switch (realAxis) {
31 | case 'x':
32 | vis.scaleX *= scale;
33 | case 'y':
34 | vis.scaleY *= scale;
35 | default:
36 | vis.scaleX *= scale;
37 | vis.scaleY *= scale;
38 | }
39 | }
40 |
41 | override public function visuals(data:Visuals, params:RenderParams) {
42 | var player = params.player;
43 | var receptorName = Std.string(params.lane);
44 | final scaleForce = getPercent('scaleForce' + receptorName, player) + getPercent('scaleForce' + receptorName, player);
45 |
46 | if (scaleForce != 0) {
47 | data.scaleX = scaleForce;
48 | data.scaleY = scaleForce;
49 | return data;
50 | }
51 |
52 | applyScale(data, params, '', '');
53 | applyScale(data, params, 'x', 'x');
54 | applyScale(data, params, 'y', 'y');
55 |
56 | return data;
57 | }
58 |
59 | override public function shouldRun(params:RenderParams):Bool
60 | return true;
61 | }
62 |
--------------------------------------------------------------------------------
/modchart/modifiers/SchmovinDrunk.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | final thtdiv = 1 / 222;
8 |
9 | class SchmovinDrunk extends Modifier {
10 | override public function render(curPos:Vector3D, params:RenderParams) {
11 | var phaseShift = params.lane * 0.5 + (params.distance * thtdiv) * Math.PI;
12 | curPos.x += sin(params.curBeat * .25 * Math.PI + phaseShift) * ARROW_SIZEDIV2 * getPercent('schmovinDrunk', params.player);
13 |
14 | return curPos;
15 | }
16 |
17 | override public function shouldRun(params:RenderParams):Bool
18 | return true;
19 | }
20 |
--------------------------------------------------------------------------------
/modchart/modifiers/SchmovinTipsy.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class SchmovinTipsy extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | curPos.y += sin(params.curBeat / 4 * Math.PI + params.lane) * ARROW_SIZEDIV2 * getPercent('schmovinTipsy', params.player);
10 | return curPos;
11 | }
12 |
13 | override public function shouldRun(params:RenderParams):Bool
14 | return true;
15 | }
16 |
--------------------------------------------------------------------------------
/modchart/modifiers/SchmovinTornado.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class SchmovinTornado extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | final tord = getPercent('schmovinTornado', params.player);
10 |
11 | if (tord == 0)
12 | return curPos;
13 | final columnShift = params.lane * Math.PI / 3;
14 | final strumNegator = (-cos(-columnShift) + 1) / 2 * ARROW_SIZE * 3;
15 | curPos.x += ((-cos((params.distance / 135) - columnShift) + 1) / 2 * ARROW_SIZE * 3 - strumNegator) * tord;
16 |
17 | return curPos;
18 | }
19 |
20 | override public function shouldRun(params:RenderParams):Bool
21 | return true;
22 | }
23 |
--------------------------------------------------------------------------------
/modchart/modifiers/Skew.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.Constants.Visuals;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class Skew extends Modifier {
10 | override public function visuals(data:Visuals, params:RenderParams):Visuals {
11 | var receptorName = Std.string(params.lane);
12 | var player = params.player;
13 | final x = getPercent('skewX', player) + getPercent('skewX' + receptorName, player);
14 | final y = getPercent('skewY', player) + getPercent('skewY' + receptorName, player);
15 |
16 | data.skewX += x;
17 | data.skewY += y;
18 |
19 | return data;
20 | }
21 |
22 | override public function shouldRun(params:RenderParams):Bool
23 | return true;
24 | }
25 |
--------------------------------------------------------------------------------
/modchart/modifiers/Square.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Square extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var player = params.player;
10 | final squarep = getPercent('square', player);
11 |
12 | if (squarep == 0)
13 | return curPos;
14 |
15 | final offset = getPercent("squareOffset", player);
16 | final period = getPercent("squarePeriod", player);
17 | final amp = (Math.PI * (params.distance + offset) / (ARROW_SIZE + (period * ARROW_SIZE)));
18 |
19 | curPos.x += squarep * square(amp);
20 |
21 | return curPos;
22 | }
23 |
24 | function square(angle:Float):Float {
25 | var fAngle = angle % (Math.PI * 2);
26 | return fAngle >= Math.PI ? -1.0 : 1.0;
27 | }
28 |
29 | override public function shouldRun(params:RenderParams):Bool
30 | return true;
31 | }
32 |
--------------------------------------------------------------------------------
/modchart/modifiers/Stealth.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 | import flixel.math.FlxMath;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import modchart.core.util.Constants.Visuals;
8 | import modchart.core.util.ModchartUtil;
9 | import openfl.geom.Vector3D;
10 |
11 | class Stealth extends Modifier {
12 | public function new(pf) {
13 | super(pf);
14 |
15 | setPercent('alpha', 1, -1);
16 |
17 | setPercent('suddenStart', 5, -1);
18 | setPercent('suddenEnd', 3, -1);
19 | setPercent('suddenGlow', 1, -1);
20 |
21 | setPercent('hiddenStart', 5, -1);
22 | setPercent('hiddenEnd', 3, -1);
23 | setPercent('hiddenGlow', 1, -1);
24 | }
25 |
26 | function computeSudden(data:Visuals, params:RenderParams) {
27 | final player = params.player;
28 |
29 | final sudden = getPercent('sudden', player);
30 |
31 | if (sudden == 0)
32 | return;
33 |
34 | final start = getPercent('suddenStart', player) * 100;
35 | final end = getPercent('suddenEnd', player) * 100;
36 | final glow = getPercent('suddenGlow', player);
37 |
38 | final alpha = FlxMath.remapToRange(FlxMath.bound(params.distance, end, start), end, start, 1, 0);
39 |
40 | if (glow != 0)
41 | data.glow += Math.max(0, (1 - alpha) * sudden * 2) * glow;
42 | data.alpha *= alpha * sudden;
43 | }
44 |
45 | function computeHidden(data:Visuals, params:RenderParams) {
46 | final player = params.player;
47 |
48 | final hidden = getPercent('hidden', player);
49 |
50 | if (hidden == 0)
51 | return;
52 |
53 | final start = getPercent('hiddenStart', player) * 100;
54 | final end = getPercent('hiddenEnd', player) * 100;
55 | final glow = getPercent('hiddenGlow', player);
56 |
57 | final alpha = FlxMath.remapToRange(FlxMath.bound(params.distance, end, start), end, start, 0, 1);
58 |
59 | if (glow != 0)
60 | data.glow += Math.max(0, (1 - alpha) * hidden * 2) * glow;
61 | data.alpha *= alpha * hidden;
62 | }
63 |
64 | override public function visuals(data:Visuals, params:RenderParams) {
65 | final player = params.player;
66 |
67 | final vMod = params.isTapArrow ? 'stealth' : 'dark';
68 | final visibility = getPercent(vMod, player) + getPercent(vMod + Std.string(params.lane), player);
69 | data.alpha = ((getPercent('alpha', player) + getPercent('alpha' + Std.string(params.lane), player)) * (1 - ((Math.max(0.5, visibility) - 0.5) * 2)))
70 | + getPercent('alphaOffset', player);
71 | data.glow += getPercent('flash', player) + (visibility * 2);
72 |
73 | // sudden & hidden
74 | if (params.isTapArrow) // non receptor
75 | {
76 | computeSudden(data, params);
77 | computeHidden(data, params);
78 | }
79 |
80 | return data;
81 | }
82 |
83 | override public function shouldRun(params:RenderParams):Bool
84 | return true;
85 | }
86 |
--------------------------------------------------------------------------------
/modchart/modifiers/Tipsy.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Tipsy extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var player = params.player;
10 | var speed = getPercent('tipsySpeed', player);
11 | var offset = getPercent('tipsyOffset', player);
12 |
13 | var tipsy = (cos((params.songTime * 0.001 * ((speed * 1.2) + 1.2) + params.lane * ((offset * 1.8) + 1.8))) * ARROW_SIZE * .4);
14 |
15 | var tipAddition = new Vector3D(getPercent('tipsyX', player), getPercent('tipsyY', player) + getPercent('tipsy', player), getPercent('tipsyZ', player));
16 | tipAddition.scaleBy(tipsy);
17 |
18 | return curPos.add(tipAddition);
19 | }
20 |
21 | override public function shouldRun(params:RenderParams):Bool
22 | return true;
23 | }
24 |
--------------------------------------------------------------------------------
/modchart/modifiers/Tornado.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.math.FlxMath;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import modchart.core.util.ModchartUtil;
7 | import openfl.geom.Vector3D;
8 |
9 | class Tornado extends Modifier {
10 | // math from open itg
11 | // hmm, looks familiar.... isnt this invert sine?
12 | override public function render(pos:Vector3D, params:RenderParams) {
13 | var tornado = getPercent('tornado', params.player);
14 |
15 | if (tornado == 0)
16 | return pos;
17 |
18 | var keyCount = getKeyCount();
19 | var bWideField = keyCount > 4;
20 | var iTornadoWidth = bWideField ? 4 : 3;
21 |
22 | var iColNum = params.lane;
23 | var iStartCol = iColNum - iTornadoWidth;
24 | var iEndCol = iColNum + iTornadoWidth;
25 | iStartCol = Math.round(ModchartUtil.clamp(iStartCol, 0, keyCount));
26 | iEndCol = Math.round(ModchartUtil.clamp(iEndCol, 0, keyCount));
27 |
28 | var fXOffset = ((ARROW_SIZE * 1.5) - (ARROW_SIZE * params.lane));
29 |
30 | var fMinX = -fXOffset;
31 | var fMaxX = fXOffset;
32 |
33 | final fRealPixelOffset = fXOffset;
34 | var fPositionBetween = scale(fRealPixelOffset, fMinX, fMaxX, -1, 1);
35 |
36 | var fRads = Math.acos(fPositionBetween);
37 | fRads += (params.distance * 0.8) * 6 / HEIGHT;
38 |
39 | final fAdjustedPixelOffset = scale(cos(fRads), -1, 1, fMinX, fMaxX);
40 |
41 | pos.x -= (fAdjustedPixelOffset - fRealPixelOffset) * tornado;
42 |
43 | return pos;
44 | }
45 |
46 | inline function scale(x:Float, l1:Float, h1:Float, l2:Float, h2:Float) {
47 | return (x - l1) * (h2 - l2) / (h1 - l1) + l2;
48 | }
49 |
50 | override public function shouldRun(params:RenderParams):Bool
51 | return true;
52 | }
53 |
--------------------------------------------------------------------------------
/modchart/modifiers/Transform.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Transform extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var receptorName = Std.string(params.lane);
10 | var player = params.player;
11 | var val = 1;
12 | if (Adapter.instance.getDownscroll())
13 | val *= -1;
14 |
15 | curPos.x += getPercent('x', player) + getPercent('x' + receptorName, player) + getPercent('xoffset', player)
16 | + getPercent('xoffset' + receptorName, player);
17 |
18 | // Y poss adds this "YD" modifier, modifier used for when you need like "downscroll and upscroll movement" but not like the same, basically, if upscroll goes down, if down, then goes up.
19 | curPos.y += getPercent('y', player) + getPercent('y' + receptorName, player) + (getPercent('yd', player) * val)
20 | + (getPercent('yd' + receptorName, player) * val) + getPercent('yoffset', player) + getPercent('yoffset' + receptorName, player)
21 | + (getPercent('ydoffset', player) * val) + (getPercent('ydoffset' + receptorName, player) * val);
22 |
23 | curPos.z += getPercent('z', player) + getPercent('z' + receptorName, player) + getPercent('zoffset', player)
24 | + getPercent('zoffset' + receptorName, player);
25 |
26 | return curPos;
27 | }
28 |
29 | override public function shouldRun(params:RenderParams):Bool
30 | return true;
31 | }
32 |
--------------------------------------------------------------------------------
/modchart/modifiers/ZigZag.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class ZigZag extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | final zigzag = getPercent('zigZag', params.player);
10 |
11 | if (zigzag == 0)
12 | return curPos;
13 |
14 | var theta = -params.distance / ARROW_SIZE * Math.PI;
15 | var outRelative = Math.acos(cos(theta + Math.PI / 2)) / Math.PI * 2 - 1;
16 |
17 | curPos.x += outRelative * ARROW_SIZEDIV2 * zigzag;
18 |
19 | return curPos;
20 | }
21 |
22 | override public function shouldRun(params:RenderParams):Bool
23 | return true;
24 | }
25 |
--------------------------------------------------------------------------------
/modchart/modifiers/Zoom.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers;
2 |
3 | import flixel.FlxG;
4 |
5 | class Zoom extends Modifier {
6 | var __curPercent:Null = -1;
7 |
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | updatePercent(params);
10 |
11 | if (__curPercent == 1)
12 | return curPos;
13 |
14 | var origin = new Vector3D(FlxG.width * .5, FlxG.height * .5);
15 | var diff = curPos.subtract(origin);
16 | diff.scaleBy(__curPercent);
17 | return diff.add(origin);
18 | }
19 |
20 | override public function visuals(data:Visuals, params:RenderParams):Visuals {
21 | if (__curPercent == null)
22 | updatePercent(params);
23 |
24 | data.scaleX = data.scaleX * __curPercent;
25 | data.scaleY = data.scaleY * __curPercent;
26 |
27 | __curPercent = null;
28 |
29 | return data;
30 | }
31 |
32 | inline function updatePercent(params:RenderParams) {
33 | __curPercent = 1 + ((getPercent('zoom', params.player) + -getPercent('mini', params.player)) * 0.5);
34 | }
35 | override public function shouldRun(params:RenderParams):Bool
36 | return true;
37 | }
38 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/CounterClockWise.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.ModchartUtil;
6 | import openfl.geom.Vector3D;
7 |
8 | class CounterClockWise extends Modifier {
9 | override public function render(curPos:Vector3D, params:RenderParams) {
10 | var strumTime = params.songTime + params.distance;
11 | var centerX = WIDTH * .5;
12 | var centerY = HEIGHT * .5;
13 | var radiusOffset = ARROW_SIZE * (params.lane - 1.5);
14 |
15 | var crochet = Adapter.instance.getStaticCrochet();
16 |
17 | var radius = 200 + radiusOffset * cos(strumTime / crochet * .25 / 16 * Math.PI);
18 | var outX = centerX + cos(strumTime / crochet / 4 * Math.PI) * radius;
19 | var outY = centerY + sin(strumTime / crochet / 4 * Math.PI) * radius;
20 |
21 | return ModchartUtil.lerpVector3D(curPos, new Vector3D(outX, outY, 0, 0), getPercent('counterClockWise', params.player));
22 | }
23 |
24 | override public function shouldRun(params:RenderParams):Bool
25 | return getPercent('counterclockwise', params.player) != 0;
26 | }
27 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/EyeShape.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import flixel.math.FlxMath;
4 | import haxe.ds.Vector;
5 | import modchart.core.util.Constants.ArrowData;
6 | import modchart.core.util.Constants.RenderParams;
7 | import modchart.core.util.ModchartUtil;
8 | import openfl.geom.Vector3D;
9 |
10 | private class TimeVector extends Vector3D {
11 | public var startDist = 0.0;
12 | public var endDist = 0.0;
13 | public var next:TimeVector;
14 | }
15 |
16 | class EyeShape extends Modifier {
17 | var _path:Vector;
18 | var _pathDistance:Float = 0;
19 |
20 | var SCALE:Float = 600;
21 |
22 | function getDistancesOf(path:Vector) {
23 | var index:Int = 0;
24 | var last = path[index];
25 | last.startDist = 0;
26 | var dist:Float = 0;
27 |
28 | while (index < path.length) {
29 | final current = path[index];
30 | final diff = current.subtract(last);
31 |
32 | current.startDist = (dist += diff.length);
33 | last.next = current;
34 | last.endDist = current.startDist;
35 | last = current;
36 |
37 | index++;
38 | }
39 | return dist;
40 | }
41 |
42 | function getPositionAt(distance:Float):Null {
43 | for (i in 0..._path.length) {
44 | final vec = _path[i];
45 |
46 | if (FlxMath.inBounds(distance, vec.startDist, vec.endDist) && vec.next != null) {
47 | var ratio = (distance - vec.startDist) / vec.next.subtract(vec).length;
48 | return ModchartUtil.lerpVector3D(vec, vec.next, ratio);
49 | }
50 | }
51 | return _path[0];
52 | }
53 |
54 | function loadPath():Vector {
55 | var pathArray:Array = [];
56 |
57 | for (node in ModchartUtil.coolTextFile('assets/modchart/eyeShape.csv')) {
58 | final coords = node.split(';');
59 | pathArray.push(new TimeVector(Std.parseFloat(coords[0]) * SCALE, Std.parseFloat(coords[1]) * SCALE, Std.parseFloat(coords[2]) * SCALE,
60 | Std.parseFloat(coords[3]) * SCALE));
61 | }
62 |
63 | var pathIterable = Vector.fromArrayCopy(pathArray);
64 | pathArray.resize(0);
65 |
66 | _pathDistance = getDistancesOf(pathIterable);
67 | return pathIterable;
68 | }
69 |
70 | override public function render(curPos:Vector3D, params:RenderParams) {
71 | if (_path == null)
72 | _path = loadPath();
73 |
74 | final perc = getPercent('eyeShape', params.player);
75 |
76 | if (perc == 0)
77 | return curPos;
78 |
79 | var path = getPositionAt(params.distance / 2000.0 * _pathDistance);
80 |
81 | return ModchartUtil.lerpVector3D(curPos, path.add(new Vector3D(WIDTH * .5 - 264 - 272, HEIGHT * .5 + 280 - 260)), perc);
82 | }
83 |
84 | override public function shouldRun(params:RenderParams):Bool
85 | return true;
86 | }
87 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/SchmovinArrowShape.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import flixel.math.FlxMath;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import openfl.geom.Vector3D;
7 |
8 | private class TimeVector extends Vector3D {
9 | public var startDist = 0.0;
10 | public var endDist = 0.0;
11 | public var next:TimeVector;
12 | }
13 |
14 | class SchmovinArrowShape extends Modifier {
15 | var _path:List;
16 | var _pathDistance:Float = 0;
17 |
18 | static inline var SCALE:Float = 200;
19 |
20 | function CalculatePathDistances(path:List) {
21 | var iterator = path.iterator();
22 | var last = iterator.next();
23 | last.startDist = 0;
24 | var dist = 0.0;
25 | var iteratorHasNext = iterator.hasNext;
26 | var iteratorNext = iterator.next;
27 | while (iteratorHasNext()) {
28 | var current = iteratorNext();
29 | var differential = current.subtract(last);
30 | dist += differential.length;
31 | current.startDist = dist;
32 | last.next = current;
33 | last.endDist = current.startDist;
34 | last = current;
35 | }
36 | return dist;
37 | }
38 |
39 | function GetPointAlongPath(distance:Float):Null {
40 | for (vec in _path) {
41 | if (FlxMath.inBounds(distance, vec.startDist, vec.endDist) && vec.next != null) {
42 | var ratio = (distance - vec.startDist) / vec.next.subtract(vec).length;
43 | return ModchartUtil.lerpVector3D(vec, vec.next, ratio);
44 | }
45 | }
46 | return _path.first();
47 | }
48 |
49 | function LoadPath():List {
50 | var file = ModchartUtil.coolTextFile('assets/modchart/arrowShape.csv');
51 | var path = new List();
52 | for (line in file) {
53 | var coords = line.split(';');
54 | var vec = new TimeVector(Std.parseFloat(coords[0]), Std.parseFloat(coords[1]), Std.parseFloat(coords[2]), Std.parseFloat(coords[3]));
55 | vec.scaleBy(SCALE);
56 | path.add(vec);
57 | }
58 | _pathDistance = CalculatePathDistances(path);
59 | return path;
60 | }
61 |
62 | override public function render(curPos:Vector3D, params:RenderParams) {
63 | if (_path == null)
64 | _path = LoadPath();
65 |
66 | final perc = getPercent('schmovinArrowShape', params.player);
67 |
68 | if (perc == 0)
69 | return curPos;
70 |
71 | var path = GetPointAlongPath(params.distance / 1500.0 * _pathDistance);
72 |
73 | return ModchartUtil.lerpVector3D(curPos,
74 | path.add(new Vector3D(WIDTH * .5, HEIGHT * .5 + 280, params.lane * getPercent('schmovinArrowShapeOffset', params.player) + curPos.z)), perc);
75 | }
76 |
77 | override public function shouldRun(params:RenderParams):Bool
78 | return true;
79 | }
80 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/Spiral.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import modchart.core.util.ModchartUtil;
6 | import openfl.geom.Vector3D;
7 |
8 | class Spiral extends Modifier {
9 | override public function render(curPos:Vector3D, params:RenderParams) {
10 | var player = params.player;
11 | var PI = Math.PI;
12 | var centerX = WIDTH * .5;
13 | var centerY = HEIGHT * .5;
14 | var radiusOffset = -params.distance * .25;
15 | var crochet = Adapter.instance.getStaticCrochet();
16 | var radius = radiusOffset + getPercent('spiralDist', player) * params.lane;
17 | var outX = centerX + cos(-params.distance / crochet * PI + params.curBeat * (PI * .25)) * radius;
18 | var outY = centerY + sin(-params.distance / crochet * PI - params.curBeat * (PI * .25)) * radius;
19 |
20 | return ModchartUtil.lerpVector3D(curPos, new Vector3D(outX, outY, radius / (centerY * 4) - 1, 0), getPercent('spiral', player));
21 | }
22 |
23 | override public function shouldRun(params:RenderParams):Bool
24 | return getPercent('spiral', params.player) != 0;
25 | }
26 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/Vibrate.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import modchart.core.util.Constants.ArrowData;
4 | import modchart.core.util.Constants.RenderParams;
5 | import openfl.geom.Vector3D;
6 |
7 | class Vibrate extends Modifier {
8 | override public function render(curPos:Vector3D, params:RenderParams) {
9 | var vib = getPercent('vibrate', params.player);
10 | curPos.x += (Math.random() - 0.5) * vib * 20;
11 | curPos.y += (Math.random() - 0.5) * vib * 20;
12 |
13 | return curPos;
14 | }
15 |
16 | override public function shouldRun(params:RenderParams):Bool
17 | return getPercent('vibrate', params.player) != 0;
18 | }
19 |
--------------------------------------------------------------------------------
/modchart/modifiers/false_paradise/Wiggle.hx:
--------------------------------------------------------------------------------
1 | package modchart.modifiers.false_paradise;
2 |
3 | import flixel.math.FlxAngle;
4 | import modchart.core.util.Constants.ArrowData;
5 | import modchart.core.util.Constants.RenderParams;
6 | import openfl.geom.Vector3D;
7 |
8 | class Wiggle extends Modifier {
9 | override public function render(curPos:Vector3D, params:RenderParams) {
10 | var wiggle = getPercent('wiggle', params.player);
11 | curPos.x += sin(params.curBeat) * wiggle * 20;
12 | curPos.y += sin(params.curBeat + 1) * wiggle * 20;
13 |
14 | setPercent('rotateZ', (sin(params.curBeat) * 0.2 * wiggle) * FlxAngle.TO_DEG);
15 |
16 | return curPos;
17 | }
18 |
19 | override public function shouldRun(params:RenderParams):Bool
20 | return getPercent('wiggle', params.player) != 0;
21 | }
22 |
--------------------------------------------------------------------------------
/modchart/standalone/Adapter.hx:
--------------------------------------------------------------------------------
1 | package modchart.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.standalone.adapters.${ENGINE_NAME.toLowerCase()}.${possibleClientName}'), []);
15 |
16 | #if FM_VERBOSE
17 | trace('[FunkinModchart Verbose] Finding possible adapter from "modchart.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/standalone/IAdapter.hx:
--------------------------------------------------------------------------------
1 | package modchart.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 getStaticCrochet():Float; // Beat crochet without bpm changes
14 | public function getCurrentBeat():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 | public function getLaneFromArrow(sprite:FlxSprite):Int; // Get lane/note data from arrow
28 | public function getPlayerFromArrow(sprite:FlxSprite):Int; // Get player from arrow
29 |
30 | public function getKeyCount(?player:Int):Int; // Get total key count (4 for almost every engine)
31 | public function getPlayerCount():Int; // Get total player count (2 for almost every engine)
32 |
33 | // Get cameras to render the arrows (camHUD for almost every engine)
34 | public function getArrowCamera():Array;
35 |
36 | // Options section
37 | public function getHoldSubdivisions():Int; // Hold resolution
38 | public function getDownscroll():Bool; // Get if it is downscroll
39 |
40 | /**
41 | * Get the every arrow/lane indexed by player.
42 | * Example:
43 | * [
44 | * [ // Player 0
45 | * [strum1, strum2...],
46 | * [arrow1, arrow2...],
47 | * [hold1, hold2....]
48 | * ],
49 | * [ // Player 2
50 | * [strum1, strum2...],
51 | * [arrow1, arrow2...],
52 | * [hold1, hold2....]
53 | * ]
54 | * ]
55 | * @return Array>>
56 | */
57 | public function getArrowItems():Array>>;
58 | }
59 |
--------------------------------------------------------------------------------
/modchart/standalone/adapters/codename/Codename.hx:
--------------------------------------------------------------------------------
1 | package modchart.standalone.adapters.codename;
2 |
3 | import flixel.FlxCamera;
4 | import flixel.FlxSprite;
5 | import funkin.backend.system.Conductor;
6 | import funkin.game.Note;
7 | import funkin.game.PlayState;
8 | import funkin.game.Strum;
9 | import funkin.options.Options;
10 | import modchart.standalone.IAdapter;
11 |
12 | class Codename implements IAdapter {
13 | private var __fCrochet:Float = 0;
14 |
15 | public function new() {}
16 |
17 | public function onModchartingInitialization() {
18 | __fCrochet = Conductor.crochet;
19 |
20 | for (strumLine in PlayState.instance.strumLines.members) {
21 | strumLine.forEach(strum -> {
22 | strum.extra.set('player', strumLine.ID);
23 | // i guess ???
24 | strum.extra.set('lane', strumLine.members.indexOf(strum));
25 | });
26 | }
27 | }
28 |
29 | public function isTapNote(sprite:FlxSprite) {
30 | return sprite is Note;
31 | }
32 |
33 | // Song related
34 | public function getSongPosition():Float {
35 | return Conductor.songPosition;
36 | }
37 |
38 | public function getCurrentBeat():Float {
39 | return Conductor.curBeatFloat;
40 | }
41 |
42 | public function getStaticCrochet():Float {
43 | return __fCrochet;
44 | }
45 |
46 | public function getBeatFromStep(step:Float):Float {
47 | return step * Conductor.stepsPerBeat;
48 | }
49 |
50 | public function arrowHit(arrow:FlxSprite) {
51 | if (arrow is Note) {
52 | final note:Note = cast arrow;
53 | return note.wasGoodHit;
54 | }
55 | return false;
56 | }
57 |
58 | public function isHoldEnd(arrow:FlxSprite) {
59 | if (arrow is Note) {
60 | final note:Note = cast arrow;
61 | return note.nextSustain == null;
62 | }
63 | return false;
64 | }
65 |
66 | public function getLaneFromArrow(arrow:FlxSprite) {
67 | if (arrow is Note) {
68 | final note:Note = cast arrow;
69 | return note.strumID;
70 | } else if (arrow is Strum) {
71 | final strum:Strum = cast arrow;
72 | return strum.extra.get('lane');
73 | }
74 |
75 | return 0;
76 | }
77 |
78 | public function getPlayerFromArrow(arrow:FlxSprite) {
79 | if (arrow is Note) {
80 | final note:Note = cast arrow;
81 | return note.strumLine.ID;
82 | } else if (arrow is Strum) {
83 | final strum:Strum = cast arrow;
84 | return strum.extra.get('player');
85 | }
86 |
87 | return 0;
88 | }
89 |
90 | public function getHoldParentTime(arrow:FlxSprite) {
91 | final note:Note = cast arrow;
92 | return note.strumTime;
93 | }
94 |
95 | // im so fucking sorry for those conditionals
96 | public function getKeyCount(?player:Int = 0):Int {
97 | return PlayState.instance != null
98 | && PlayState.instance.strumLines != null
99 | && PlayState.instance.strumLines.members != null
100 | && PlayState.instance.strumLines.members[player] != null
101 | && PlayState.instance.strumLines.members[player].members != 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():Int {
118 | final val = Options.modchartingHoldSubdivisions;
119 | return val < 1 ? 1 : val;
120 | }
121 |
122 | public function getDownscroll():Bool {
123 | return Options.downscroll;
124 | }
125 |
126 | public function getDefaultReceptorX(lane:Int, player:Int):Float {
127 | @:privateAccess
128 | return PlayState.instance.strumLines.members[player].members[lane].x;
129 | }
130 |
131 | public function getDefaultReceptorY(lane:Int, player:Int):Float {
132 | @:privateAccess
133 | return PlayState.instance.strumLines.members[player].members[lane].y;
134 | }
135 |
136 | public function getArrowCamera():Array
137 | return [PlayState.instance.camHUD];
138 |
139 | public function getCurrentScrollSpeed():Float {
140 | return PlayState.instance.scrollSpeed;
141 | }
142 |
143 | // 0 receptors
144 | // 1 tap arrows
145 | // 2 hold arrows
146 | // 3 lane attachments
147 | public function getArrowItems() {
148 | var pspr:Array>> = [];
149 |
150 | var strumLineMembers = PlayState.instance.strumLines.members;
151 |
152 | for (i in 0...strumLineMembers.length) {
153 | final sl = strumLineMembers[i];
154 |
155 | if (!sl.visible)
156 | continue;
157 |
158 | final splashHandler = PlayState.instance.splashHandler;
159 |
160 | // this is somehow more optimized than how i used to do it (thanks neeo for the code!!)
161 | pspr[i] = [];
162 | pspr[i][0] = cast sl.members.copy();
163 | pspr[i][1] = [];
164 | pspr[i][2] = [];
165 |
166 | var st = 0;
167 | var nt = 0;
168 | sl.notes.forEachAlive((spr) -> {
169 | spr.isSustainNote ? st++ : nt++;
170 | });
171 |
172 | pspr[i][1].resize(nt);
173 | pspr[i][2].resize(st);
174 |
175 | var si = 0;
176 | var ni = 0;
177 | sl.notes.forEachAlive((spr) -> pspr[i][spr.isSustainNote ? 2 : 1][spr.isSustainNote ? si++ : ni++] = spr);
178 | }
179 |
180 | return pspr;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/modchart/standalone/adapters/fpsplus/Fpsplus.hx:
--------------------------------------------------------------------------------
1 | package modchart.standalone.adapters.fpsplus;
2 |
3 | import flixel.FlxCamera;
4 | import flixel.FlxSprite;
5 | import modchart.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;
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 | public function getStaticCrochet():Float {
31 | return __fCrochet;
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 getHoldParentTime(arrow:FlxSprite) {
73 | final note:Note = cast arrow;
74 | return note.strumTime;
75 | }
76 |
77 | // im so fucking sorry for those conditionals
78 | public function getKeyCount(?player:Int = 0):Int {
79 | return 4;
80 | }
81 |
82 | public function getPlayerCount():Int {
83 | return 2;
84 | }
85 |
86 | public function getTimeFromArrow(arrow:FlxSprite) {
87 | if (arrow is Note) {
88 | final note:Note = cast arrow;
89 | return note.strumTime;
90 | }
91 |
92 | return 0;
93 | }
94 |
95 | public function getHoldSubdivisions():Int {
96 | return 3;
97 | }
98 |
99 | public function getDownscroll():Bool {
100 | return config.Config.downscroll;
101 | }
102 |
103 | public function getDefaultReceptorX(lane:Int, player:Int):Float {
104 | return __getStrumGroupFromPlayer(player).members[lane].x;
105 | }
106 |
107 | public function getDefaultReceptorY(lane:Int, player:Int):Float {
108 | return __getStrumGroupFromPlayer(player).members[lane].y;
109 | }
110 |
111 | public function getArrowCamera():Array
112 | return [PlayState.instance.camHUD];
113 |
114 | public function getCurrentScrollSpeed():Float {
115 | return PlayState.SONG.speed * PlayState.instance.scrollSpeedMultiplier;
116 | }
117 |
118 | // 0 receptors
119 | // 1 tap arrows
120 | // 2 hold arrows
121 | // 3 lane attachments
122 | public function getArrowItems() {
123 | var pspr:Array>> = [[[], [], []], [[], [], []]];
124 |
125 | @:privateAccess
126 | final strums = [PlayState.instance.enemyStrums, PlayState.instance.playerStrums];
127 | for (i in 0...strums.length){
128 | strums[i].forEachAlive(strumNote -> {
129 | if (pspr[i] == null)
130 | pspr[i] = [];
131 |
132 | pspr[i][0].push(strumNote);
133 | });
134 | }
135 | PlayState.instance.notes.forEachAlive(strumNote -> {
136 | final player = Adapter.instance.getPlayerFromArrow(strumNote);
137 | if (pspr[player] == null)
138 | pspr[player] = [];
139 |
140 | pspr[player][strumNote.isSustainNote ? 2 : 1].push(strumNote);
141 | });
142 |
143 | return pspr;
144 | }
145 |
146 | private function __getStrumGroupFromPlayer(player:Int):flixel.group.FlxGroup.FlxTypedGroup
147 | {
148 | return player == 1 ? PlayState.instance.playerStrums : PlayState.instance.enemyStrums;
149 | }
150 | }
--------------------------------------------------------------------------------
/modchart/standalone/adapters/psych/Psych.hx:
--------------------------------------------------------------------------------
1 | package modchart.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.StrumNote as Strum;
8 | import states.PlayState;
9 | #else
10 | import ClientPrefs;
11 | import Conductor;
12 | import Note;
13 | import PlayState;
14 | import StrumNote as Strum;
15 | #end
16 | import flixel.FlxCamera;
17 | import flixel.FlxG;
18 | import flixel.FlxSprite;
19 | import modchart.Manager;
20 | import modchart.standalone.IAdapter;
21 |
22 | class Psych implements IAdapter {
23 | private var __fCrochet:Float = 0;
24 |
25 | private var __receptorXs:Array>;
26 | private var __receptorYs:Array>;
27 |
28 | public function new() {
29 | try {
30 | setupLuaFunctions();
31 | } catch (e) {
32 | trace('[FunkinModchart Psych Adapter] Failed while adding lua functions: $e');
33 | }
34 | }
35 |
36 | public function onModchartingInitialization() {
37 | __fCrochet = Conductor.crochet;
38 |
39 | __receptorXs = [];
40 | __receptorYs = [];
41 |
42 | @:privateAccess
43 | PlayState.instance.strumLineNotes.forEachAlive(strumNote -> {
44 | if (__receptorXs[strumNote.player] == null)
45 | __receptorXs[strumNote.player] = [];
46 | if (__receptorYs[strumNote.player] == null)
47 | __receptorYs[strumNote.player] = [];
48 |
49 | __receptorXs[strumNote.player][strumNote.noteData] = strumNote.x;
50 | __receptorYs[strumNote.player][strumNote.noteData] = getDownscroll() ? FlxG.height - strumNote.y - Manager.ARROW_SIZE : strumNote.y;
51 | });
52 | }
53 |
54 | private function setupLuaFunctions() {
55 | #if LUA_ALLOWED
56 | // todo
57 | #end
58 | }
59 |
60 | public function isTapNote(sprite:FlxSprite) {
61 | return sprite is Note;
62 | }
63 |
64 | // Song related
65 | public function getSongPosition():Float {
66 | return Conductor.songPosition;
67 | }
68 |
69 | public function getCurrentBeat():Float {
70 | @:privateAccess
71 | return PlayState.instance.curDecBeat;
72 | }
73 |
74 | public function getStaticCrochet():Float {
75 | return __fCrochet + 8;
76 | }
77 |
78 | public function getBeatFromStep(step:Float)
79 | return step * .25;
80 |
81 | public function arrowHit(arrow:FlxSprite) {
82 | if (arrow is Note)
83 | return cast(arrow, Note).wasGoodHit;
84 | return false;
85 | }
86 |
87 | public function isHoldEnd(arrow:FlxSprite) {
88 | if (arrow is Note) {
89 | final castedNote = cast(arrow, Note);
90 |
91 | if (castedNote.nextNote != null)
92 | return !castedNote.nextNote.isSustainNote;
93 | }
94 | return false;
95 | }
96 |
97 | public function getLaneFromArrow(arrow:FlxSprite) {
98 | if (arrow is Note)
99 | return cast(arrow, Note).noteData;
100 | else if (arrow is Strum) @:privateAccess
101 | return cast(arrow, Strum).noteData;
102 |
103 | return 0;
104 | }
105 |
106 | public function getPlayerFromArrow(arrow:FlxSprite) {
107 | if (arrow is Note)
108 | return cast(arrow, Note).mustPress ? 1 : 0;
109 | else if (arrow is Strum) @:privateAccess
110 | return cast(arrow, Strum).player;
111 |
112 | return 0;
113 | }
114 |
115 | public function getKeyCount(?player:Int = 0):Int {
116 | return 4;
117 | }
118 |
119 | public function getPlayerCount():Int {
120 | return 2;
121 | }
122 |
123 | public function getTimeFromArrow(arrow:FlxSprite) {
124 | if (arrow is Note)
125 | return cast(arrow, Note).strumTime;
126 |
127 | return 0;
128 | }
129 |
130 | public function getHoldSubdivisions():Int {
131 | return 4;
132 | }
133 |
134 | // psych adjust the strum pos at the begin of playstate
135 | public function getDownscroll():Bool {
136 | return ClientPrefs.data.downScroll;
137 | }
138 |
139 | public function getDefaultReceptorX(lane:Int, player:Int):Float {
140 | return __receptorXs[player][lane];
141 | }
142 |
143 | public function getDefaultReceptorY(lane:Int, player:Int):Float {
144 | return __receptorYs[player][lane];
145 | }
146 |
147 | public function getArrowCamera():Array
148 | return [PlayState.instance.camHUD];
149 |
150 | public function getCurrentScrollSpeed():Float {
151 | return PlayState.instance.songSpeed;
152 | }
153 |
154 | // 0 receptors
155 | // 1 tap arrows
156 | // 2 hold arrows
157 | public function getArrowItems() {
158 | var pspr:Array>> = [[[], [], []], [[], [], []]];
159 |
160 | @:privateAccess
161 | PlayState.instance.strumLineNotes.forEachAlive(strumNote -> {
162 | if (pspr[strumNote.player] == null)
163 | pspr[strumNote.player] = [];
164 |
165 | pspr[strumNote.player][0].push(strumNote);
166 | });
167 | PlayState.instance.notes.forEachAlive(strumNote -> {
168 | final player = Adapter.instance.getPlayerFromArrow(strumNote);
169 | if (pspr[player] == null)
170 | pspr[player] = [];
171 |
172 | pspr[player][strumNote.isSustainNote ? 2 : 1].push(strumNote);
173 | });
174 |
175 | return pspr;
176 | }
177 |
178 | public function getHoldParentTime(arrow:FlxSprite) {
179 | final note:Note = cast arrow;
180 | return note.parent.strumTime;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------