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