[
  {
    "path": ".editorconfig",
    "content": "# NOTE: Requires **VS2019 16.3** or later\n\n# Code files\n[*.{cs,vb}]\n\n\ndotnet_diagnostic.CA1001.severity = warning\n\ndotnet_diagnostic.CA1009.severity = warning\n\ndotnet_diagnostic.CA1016.severity = warning\n\ndotnet_diagnostic.CA1033.severity = warning\n\ndotnet_diagnostic.CA1049.severity = warning\n\ndotnet_diagnostic.CA1060.severity = warning\n\ndotnet_diagnostic.CA1061.severity = warning\n\ndotnet_diagnostic.CA1063.severity = warning\n\ndotnet_diagnostic.CA1065.severity = warning\n\ndotnet_diagnostic.CA1301.severity = warning\n\ndotnet_diagnostic.CA1400.severity = warning\n\ndotnet_diagnostic.CA1401.severity = warning\n\ndotnet_diagnostic.CA1403.severity = warning\n\ndotnet_diagnostic.CA1404.severity = warning\n\ndotnet_diagnostic.CA1405.severity = warning\n\ndotnet_diagnostic.CA1410.severity = warning\n\ndotnet_diagnostic.CA1415.severity = warning\n\ndotnet_diagnostic.CA1821.severity = warning\n\ndotnet_diagnostic.CA1900.severity = warning\n\ndotnet_diagnostic.CA1901.severity = warning\n\ndotnet_diagnostic.CA2002.severity = warning\n\ndotnet_diagnostic.CA2100.severity = warning\n\ndotnet_diagnostic.CA2101.severity = warning\n\ndotnet_diagnostic.CA2108.severity = warning\n\ndotnet_diagnostic.CA2111.severity = warning\n\ndotnet_diagnostic.CA2112.severity = warning\n\ndotnet_diagnostic.CA2114.severity = warning\n\ndotnet_diagnostic.CA2116.severity = warning\n\ndotnet_diagnostic.CA2117.severity = warning\n\ndotnet_diagnostic.CA2122.severity = warning\n\ndotnet_diagnostic.CA2123.severity = warning\n\ndotnet_diagnostic.CA2124.severity = warning\n\ndotnet_diagnostic.CA2126.severity = warning\n\ndotnet_diagnostic.CA2131.severity = warning\n\ndotnet_diagnostic.CA2132.severity = warning\n\ndotnet_diagnostic.CA2133.severity = warning\n\ndotnet_diagnostic.CA2134.severity = warning\n\ndotnet_diagnostic.CA2137.severity = warning\n\ndotnet_diagnostic.CA2138.severity = warning\n\ndotnet_diagnostic.CA2140.severity = warning\n\ndotnet_diagnostic.CA2141.severity = warning\n\ndotnet_diagnostic.CA2146.severity = warning\n\ndotnet_diagnostic.CA2147.severity = warning\n\ndotnet_diagnostic.CA2149.severity = warning\n\ndotnet_diagnostic.CA2200.severity = warning\n\ndotnet_diagnostic.CA2202.severity = warning\n\ndotnet_diagnostic.CA2207.severity = warning\n\ndotnet_diagnostic.CA2212.severity = warning\n\ndotnet_diagnostic.CA2213.severity = warning\n\ndotnet_diagnostic.CA2214.severity = warning\n\ndotnet_diagnostic.CA2216.severity = warning\n\ndotnet_diagnostic.CA2220.severity = warning\n\ndotnet_diagnostic.CA2229.severity = warning\n\ndotnet_diagnostic.CA2231.severity = warning\n\ndotnet_diagnostic.CA2232.severity = warning\n\ndotnet_diagnostic.CA2235.severity = warning\n\ndotnet_diagnostic.CA2236.severity = warning\n\ndotnet_diagnostic.CA2237.severity = warning\n\ndotnet_diagnostic.CA2238.severity = warning\n\ndotnet_diagnostic.CA2240.severity = warning\n\ndotnet_diagnostic.CA2241.severity = warning\n\ndotnet_diagnostic.CA2242.severity = warning\n\ndotnet_diagnostic.CS8019.severity = warning\n\ndotnet_diagnostic.CS8020.severity = warning\n\ndotnet_diagnostic.IDE0001.severity = warning\n\ndotnet_diagnostic.IDE0002.severity = warning\n\ndotnet_diagnostic.IDE0060.severity = warning\n\ndotnet_diagnostic.SA0001.severity = none\n\ndotnet_diagnostic.SA1101.severity = none\n\ndotnet_diagnostic.SA1309.severity = none\n\ndotnet_diagnostic.SA1310.severity = none\n\ndotnet_diagnostic.SA1313.severity = none\n\ndotnet_diagnostic.SA1402.severity = none\n\ndotnet_diagnostic.SA1600.severity = none\n\ndotnet_diagnostic.SA1601.severity = none\n\ndotnet_diagnostic.SA1602.severity = none\n\ndotnet_diagnostic.SA1611.severity = none\n\ndotnet_diagnostic.SA1615.severity = none\n\ndotnet_diagnostic.SA1618.severity = none\n\ndotnet_diagnostic.SA1619.severity = none\n\ndotnet_diagnostic.SA1629.severity = none\n\ndotnet_diagnostic.SA1633.severity = none\n\ndotnet_diagnostic.SA1649.severity = none\n\ndotnet_diagnostic.SX1101.severity = warning\n\ndotnet_diagnostic.SX1309.severity = warning\n\ndotnet_diagnostic.SX1309S.severity = warning\n"
  },
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: aeroluna\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- Backup*.rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb"
  },
  {
    "path": "Documentation/AnimationDocs.md",
    "content": "These docs have moved! https://github.com/Aeroluna/Heck/wiki/Animation"
  },
  {
    "path": "Documentation/examples/documentationMap/README.md",
    "content": "# Documentation map\nThis is a JS script used to generate the map seen in the documentation of noodle extensions. Most of it should be self explanatory, it requires nodeJS to run. \n\nIf you have any simple questions feel free to dm me on discord `Reaxt#0690`"
  },
  {
    "path": "Documentation/examples/documentationMap/count.txt",
    "content": "124"
  },
  {
    "path": "Documentation/examples/documentationMap/demo.js",
    "content": "'use strict'\n\nconst fs = require('fs');\n\nconst INPUT = \"ExpertPlusStandard.dat\"\nconst OUTPUT = \"ExpertStandard.dat\"\n\nlet difficulty = JSON.parse(fs.readFileSync(INPUT));\n\n\n//#region this just counts how many time you ran it for fun, feel free to remove.\nif(!(fs.existsSync(\"count.txt\"))) {\n    fs.writeFileSync(\"count.txt\", parseInt(\"0\").toString())\n}\nlet count = parseInt(fs.readFileSync(\"count.txt\"))\ncount++\nfs.writeFileSync(\"count.txt\", count.toString())\nconsole.log(\"GIVE IT UP FOR RUN \" + count)\n//#endregion\n\n\ndifficulty._customData = { _pointDefinitions: [], _customEvents: [] };\n\nconst _customData = difficulty._customData;\nconst _obstacles = difficulty._obstacles;\nconst _notes = difficulty._notes;\nconst _customEvents = _customData._customEvents;\nconst _pointDefinitions = _customData._pointDefinitions;\n\nlet filterednotes\n\n_obstacles.forEach(wall => {\n    if (!wall._customData) {\n        wall._customData = {}\n    }\n})\n\n_notes.forEach(note => {\n    if (!note._customData) {\n        note._customData = {}\n    }\n})\n//#region helper functions\nfunction round(value, decimals) {\n  return Number(Math.round(value+'e'+decimals)+'e-'+decimals);\n}\nfunction getJumps(njs, offset) {\n    const _startHalfJumpDurationInBeats = 4\n    const _maxHalfJumpDistance = 18\n    const _startBPM = 170\n    const bpm = 170\n    const _startNoteJumpMovementSpeed = njs\n    const _noteJumpStartBeatOffset = offset\n\n    let _noteJumpMovementSpeed = (_startNoteJumpMovementSpeed * bpm) / _startBPM\n    let num = 60 / bpm\n    let num2 = _startHalfJumpDurationInBeats\n    while (_noteJumpMovementSpeed * num * num2 > _maxHalfJumpDistance) {\n        num2 /= 2\n    }\n    num2 += _noteJumpStartBeatOffset\n    if (num2 < 1) {\n        num2 = 1\n    }\n    const _jumpDuration = num * num2 * 2\n    const _jumpDistance = _noteJumpMovementSpeed * _jumpDuration\n    return {half: num2, dist: _jumpDistance}\n}\n\nfunction offestOnNotesBetween(p1,p2,offset) {\n\tfilterednotes = _notes.filter(n => n._time >= p1 && n._time <= p2)\n\tfilterednotes.forEach(object =>\n\t\t{\n\t\t\t//always worth having.\n\t\t\t//man this shit BETTER not be undefined.\n\t\t\tif(typeof offset !== \"undefined\") {object._customData._noteJumpStartBeatOffset = offset;}\n\n\t\t})\n\treturn filterednotes;\n}\n\nfunction lerp(v0,v1,t) {\n\treturn v0*(1-t)+v1*t;\n}\nfunction trackOnNotesBetween(track, p1,p2,potentialOffset) {\n  filterednotes = _notes.filter(n => n._time >= p1 && n._time <= p2)\n  filterednotes.forEach(object =>\n  {\n    object._customData._track = track;\n    if(typeof potentialOffset !== \"undefined\") {object._customData._noteJumpStartBeatOffset = potentialOffset;}\n\n  })\n  return filterednotes;\n}\n//applies a track to notes on two tracks between two times based on the color of the notes\n//IT GONNA FUCK UP WITH BOMBS I TELL YOU HWAT BOI\n//red, blue, p1, p2, potentialOffset\nfunction trackOnNotesBetweenRBSep(trackR, trackB, p1, p2, potentialOffset){\n\tfilterednotes = _notes.filter(n => n._time >= p1 && n._time <= p2)\n\tfilterednotes.forEach(object =>\n\t\t{\n\t\t\tif(typeof potentialOffset !== \"undefined\") {object._customData._noteJumpStartBeatOffset = potentialOffset;}\n\t\t\tif(object._type == 0) {object._customData._track = trackR};\n\t\t\tif(object._type == 1) {object._customData._track = trackB};\n\t  \n\t\t})\n\treturn filterednotes;\n}\n//p1, p2, potentialoffset, up, down, left, right, \n//TODO: ADD OTHER DIRS\nfunction trackOnNotesBetweenDirSep(p1, p2, potentialOffset, trackUp, trackDown, trackLeft, trackRight) {\n\tfilterednotes = _notes.filter(n => n._time >= p1 && n._time <= p2)\n\tfilterednotes.forEach(object =>\n\t\t{\n\t\t\tif(object._cutDirection == 0 && typeof trackUp !== \"undefined\") {object._customData._track = trackUp}\n\t\t\tif(object._cutDirection == 1 && typeof trackUp !== \"undefined\") {object._customData._track = trackDown}\n\t\t\tif(object._cutDirection == 2 && typeof trackUp !== \"undefined\") {object._customData._track = trackLeft}\n\t\t\tif(object._cutDirection == 3 && typeof trackUp !== \"undefined\") {object._customData._track = trackRight}\n\t\t\t//i might want to make this only run if I assign a track...\n\t\t\tif(typeof potentialOffset !== \"undefined\") {object._customData._noteJumpStartBeatOffset = potentialOffset;}\n\n\t\t})\n\treturn filterednotes;\n}\n\n//#endregion\n\n//#region use this area to do your stuff\n\n//#region 4-10 _position demo\n\ntrackOnNotesBetween(\"firstPositionDemo\", 4, 20) //this function achieves the same as the lines below, its just a simpler/easier way to do so\nfilterednotes = _notes.filter(n=> n._time >= 4 && n._time <= 25)\nfilterednotes.forEach(note => {\n\tnote._customData._track = \"firstPositionDemo\"\n})\n\n//push demo point definitions\n_pointDefinitions.push({\n\t\"_name\":\"examplePositionPointDef\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[0,5,0,0.5,\"splineCatmullRom\"],\n\t\t[0,0,0,1,\"splineCatmullRom\"]\n\t  ]\n}, {\n\t\"_name\":\"examplePositionPath\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[0,5,0,0.25,\"splineCatmullRom\"],\n\t\t[0,0,0,0.5,\"splineCatmullRom\"]\n\t  ]\n})\n\n//animate track\n_customEvents.push({\n\t\"_time\":4,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"firstPositionDemo\",\n\t\t\"_position\":\"examplePositionPointDef\",\n\t\t\"_duration\":8\n\t}\n})\n//AssignPath\n_customEvents.push({\n\t\"_time\":12,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"firstPositionDemo\",\n\t\t\"_position\":\"examplePositionPath\",\n\t\t\"_duration\":4,\n\t\t\"_easing\":\"easeInBounce\"\n\t}\n}, {\n\t\"_time\":16,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"firstPositionDemo\",\n\t\t\"_position\":[[0,0,0,0],[0,0,0,1]],\n\t\t\"_duration\":4,\n\t\t\"_easing\":\"easeOutBounce\"\n\t}\n})\n//#endregion\n\n//#region _localRotation 20-40\n\ntrackOnNotesBetween(\"localRotationDemo\", 20, 40)\n_pointDefinitions.push({\n\t\"_name\":\"localSpinDemoAnimate\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[90,0,0,0.25],\n\t\t[180,0,0,0.5],\n\t\t[270,0,0,0.75],\n\t\t[360,0,0,1]\n\t  ]\n}, {\n\t\"_name\":\"localSpinDemoAnimateRev\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[-90,0,0,0.25],\n\t\t[-180,0,0,0.5],\n\t\t[-270,0,0,0.75],\n\t\t[-360,0,0,1]\n\t  ]\n}, {\n\t\"_name\":\"localSpinDemoPath\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[0,0,90,0.125],\n\t\t[0,0,180,0.25],\n\t\t[0,0,270,0.375],\n\t\t[0,0,360,0.5]\n\t  ]\n}\n)\n//AnimateTrack,\n_customEvents.push({\n\t\"_time\":20,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"localRotationDemo\",\n\t\t\"_duration\":5,\n\t\t\"_localRotation\":\"localSpinDemoAnimate\",\n\t\t\"_easing\":\"easeInOutExpo\"\n\t}\n}, {\n\t\"_time\":25,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"localRotationDemo\",\n\t\t\"_duration\":5,\n\t\t\"_localRotation\":\"localSpinDemoAnimateRev\",\n\t\t\"_easing\":\"easeInOutExpo\"\n\t}\n}, {\n\t\"_time\":30,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"localRotationDemo\",\n\t\t\"_duration\":0,\n\t\t\"_localRotation\":\"localSpinDemoPath\"\n\t}\n})\n\n\n//#endregion\n\n//#region _rotation 40-60\n\ntrackOnNotesBetween(\"RotationDemo\", 40, 60)\n_pointDefinitions.push({\n\t\"_name\":\"RotationPointsAnimate\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[0,90,0,0.25],\n\t\t[0,180,0,0.5],\n\t\t[0,270,0,0.75],\n\t\t[0,360,0,1]\n\t]\n}, {\n\t\"_name\":\"RotationPointsPath\",\n\t\"_points\":[\n\t\t[0,0,0,0],\n\t\t[0,45,0,0.125, \"splineCatmullRom\"],\n\t\t[0,-45,0,0.25,\"splineCatmullRom\"],\n\t\t[0,22.5,0,0.375,\"splineCatmullRom\"],\n\t\t[0,-22.5,0,0.5,\"splineCatmullRom\"],\n\t\t[0,0,0,0.625,\"splineCatmullRom\"]\n\t]\n}\n)\n//AnimateTrack\n_customEvents.push({\n\t\"_time\":40,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"RotationDemo\",\n\t\t\"_rotation\":\"RotationPointsAnimate\",\n\t\t\"_duration\":10\n\t}\n},\n {\n\t\"_time\":50,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"RotationDemo\",\n\t\t\"_rotation\":\"RotationPointsPath\",\n\t\t\"_duration\":5\n\t}\n}, {\n\t\"_time\":55,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"RotationDemo\",\n\t\t\"_rotation\":[[0,0,0,0]],\n\t\t\"_duration\":5\n\t}\n})\n\n//#endregion\n\n//#region _dissolve 60-80\n\ntrackOnNotesBetween(\"dissolveDemo\", 60, 80)\n_pointDefinitions.push({\n\t\"_name\":\"dissolveDemoAnimate\",\n\t\"_points\":[\n\t\t[1,0],\n\t\t[0,0.25],\n\t\t[0.5,0.50],\n\t\t[0,0.75],\n\t\t[1,1]\n\t]\n}, {\n\t\"_name\":\"dissolveDemoPath\",\n\t\"_points\":[\n\t\t[0,0],\n\t\t[1,0.125],\n\t\t[1, 0.30],\n\t\t[0,0.35]\n\t]\n}\n)\n\n_customEvents.push({\n\t\"_time\":60,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"dissolveDemo\",\n\t\t\"_dissolve\":\"dissolveDemoAnimate\",\n\t\t\"_duration\":10\n\t}\n}, {\n\t\"_time\":70,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"dissolveDemo\",\n\t\t\"_dissolve\":\"dissolveDemoPath\",\n\t\t\"_duration\":0\n\t}\n})\n//#endregion\n\n//#region _dissolve 80-100\ntrackOnNotesBetween(\"dissolveArrowDemo\", 80, 100)\n\n_pointDefinitions.push({\n\t\"_name\":\"dissolveArrowDemoAnimate\",\n\t\"_points\":[\n\t\t[1,0],\n\t\t[0,1]\n\t]\n}, {\n\t\"_name\":\"dissolveArrowDemoPath\",\n\t\"_points\":[\n\t\t[0,0.10],\n\t\t[1,0.20],\n\t\t[1, 0.30],\n\t\t[0,0.35]\n\t]\n}\n)\n_customEvents.push({\n\t\"_time\":80,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"dissolveArrowDemo\",\n\t\t\"_dissolveArrow\":\"dissolveArrowDemoAnimate\",\n\t\t\"_duration\":5\n\t}\n}, {\n\t\"_time\":85,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"dissolveArrowDemo\",\n\t\t\"_dissolveArrow\":[[0,0],[1,1]],\n\t\t\"_duration\":5\n\t}\n},\n {\n\t\"_time\":90,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"dissolveArrowDemo\",\n\t\t\"_dissolveArrow\":\"dissolveArrowDemoPath\"\n\t}\n})\n\n//#endregion\n\n//#region color walls 100-120\n\n_obstacles.push({\n\t\"_time\":100,\n\t\"_duration\":9,\n\t\"_lineindex\":0,\n\t\"_type\":0,\n\t\"_width\":1,\n\t\"_customData\":{\n\t\t\"_track\":\"LeftColorWall\"\n\t}\n}, {\n\t\"_time\":100,\n\t\"_duration\":9,\n\t\"_lineindex\":3,\n\t\"_type\":0,\n\t\"_width\":1,\n\t\"_customData\":{\n\t\t\"_track\":\"RightColorWall\"\n\t}\n})\n_pointDefinitions.push({\n\t\"_name\":\"RightColorWallAnimate\",\n\t\"_points\":[\n\t\t[1,0,0,1,0.2],\n\t\t[0,1,0,1,0.4],\n\t\t[0,0,1,1,0.6],\n\t\t[0,1,1,1,0.8],\n\t\t[1,1,1,1,1],\n\t]\n}, {\n\t\"_name\":\"LeftColorWallAnimate\",\n\t\"_points\":[\n\t\t[1,0,0,0,0.2],\n\t\t[0,1,0,0,0.4],\n\t\t[0,0,1,0,0.6],\n\t\t[0,1,1,0,0.8],\n\t\t[1,1,1,0,1],\n\t]\n}\n)\n\n_customEvents.push({\n\t\"_time\":98,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"RightColorWall\",\n\t\t\"_color\":\"RightColorWallAnimate\",\n\t\t\"_duration\":10\n\t}\n}, {\n\t\"_time\":98,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"LeftColorWall\",\n\t\t\"_color\":\"LeftColorWallAnimate\",\n\t\t\"_duration\":10\n\t}\n}\n)\nfor (let i = 0; i < 400; i++) {\n\tlet startTime = 110;\n\tlet interval = 0.025;\n\tlet duration = 0.01;\n\t\n\t_obstacles.push({\n\t\t\"_time\":startTime+(interval*i),\n\t\t\"_duration\":duration,\n\t\t\"_lineindex\":0,\n\t\t\"_type\":1,\n\t\t\"_width\":1,\n\t\t\"_customData\":{\n\t\t\t\"_track\":\"LeftColorWallStatic\"\n\t\t}\n\t}, {\n\t\t\"_time\":startTime+(interval*i),\n\t\t\"_duration\":duration,\n\t\t\"_lineindex\":3,\n\t\t\"_type\":1,\n\t\t\"_width\":1,\n\t\t\"_customData\":{\n\t\t\t\"_track\":\"RightColorWallStatic\"\n\t\t}\n\t})\n}\n\n_pointDefinitions.push({\n\t\"_name\":\"GradientPathOne\",\n\t\"_points\":[\n\t\t[1,0,0,0.5,0.0416],\n\t\t[0,1,0,0.5,0.0832],\n\t\t[0,0,1,0.5,0.1248],\n\t\t[1,0,0,0.5,0.1664],\n\t\t[0,1,0,0.5,0.208],\n\t\t[0,0,1,0.5,0.2496],\n\t\t[1,0,0,0.5,0.2912],\n\t\t[0,1,0,0.5,0.3328],\n\t\t[0,0,1,0.5,0.3743],\n\t\t[1,0,0,0.5,0.416],\n\t\t[0,1,0,0.5,0.4576],\n\t\t[0,0,1,0.5,0.4992]\n\t]\n}, {\n\t\"_name\":\"GradientPathTwo\",\n\t\"_points\":[\n\t\t[0,1,0,0.5,0.0416],\n\t\t[0,0,1,0.5,0.0832],\n\t\t[1,0,0,0.5,0.1248],\n\t\t[0,1,0,0.5,0.1664],\n\t\t[0,0,1,0.5,0.208],\n\t\t[1,0,0,0.5,0.2496],\n\t\t[0,1,0,0.5,0.2912],\n\t\t[0,0,1,0.5,0.3328],\n\t\t[1,0,0,0.5,0.3743],\n\t\t[0,1,0,0.5,0.416],\n\t\t[0,0,1,0.5,0.4576],\n\t\t[1,0,0,0.5,0.4992]\n\t]\n}\n)\n\n_customEvents.push(\n\t{\n\t\"_time\":110,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"RightColorWallStatic\",\n\t\t\"_color\":\"GradientPathOne\",\n\t\t\"_duration\":2\n\t}\n}, {\n\t\"_time\":114,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"RightColorWallStatic\",\n\t\t\"_color\":\"GradientPathTwo\",\n\t\t\"_duration\":6,\n\t\t\"_easing\":\"easeOutElastic\",\n\t\t\n\t}\n}, {\n\t\"_time\":110,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"LeftColorWallStatic\",\n\t\t\"_color\":\"GradientPathTwo\",\n\t\t\"_duration\":2\n\t}\n}, {\n\t\"_time\":114,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"LeftColorWallStatic\",\n\t\t\"_color\":\"GradientPathOne\",\n\t\t\"_duration\":6,\n\t\t\"_easing\":\"easeOutElastic\"\n}\n})\n//#endregion\n\n//#region definitePosition 130-150\n\ntrackOnNotesBetween(\"definitePosDemo\", 130, 150)\n_pointDefinitions.push({\n\t\"_name\":\"defPosPath\",\n\t\"_points\":[\n\t\t[0, 0, 20, 0],\n\t\t[10, 0, 20, 0.1],\n\t\t[10, 10, 20, 0.2],\n\t\t[0, 10, 20, 0.3],\n\t\t[0, 0, 20, 0.4],\n\t\t[0, 0, 10, 0.5],\n\t\t[-20, 0, 10, 1.0]\n\t  ]\n}, {\n\t\"_name\":\"defPosNormal\",\n\t\"_points\":[\n\t\t[0,0,23,0],\n\t\t[0,0,0,0.5],\n\t\t[0,0,-23,1]\n\t]\n}\n)\n\n_customEvents.push({\n\t\"_time\":132,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"definitePosDemo\",\n\t\t\"_definitePosition\":\"defPosPath\",\n\t\t\"_duration\":3\n\t}\n}, {\n\t\"_time\":0,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"definitePosDemo\",\n\t\t\"_definitePosition\":\"defPosNormal\",\n\t\t\"_duration\":0\n\t}\n})\n//#endregion\n//#region time 155 (this example is bad, you basically need to script time. Better example coming someday)\n_pointDefinitions.push({\n\t\"_name\":\"SingleNoteTime\",\n\t\"_points\":[\n\t\t[0,0],\n\t\t[0.45, 0.15],\n\t\t[0.15, 0.30],\n\t\t[0.5, 0.5],\n\t\t[1,1]\n\t]\n})\ntrackOnNotesBetween(\"singleNoteTimeTrack\", 155, 155)\n_customEvents.push({\n\t\"_time\":153,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_time\":\"SingleNoteTime\",\n\t\t\"_duration\":10,\n\t\t\"_track\":\"singleNoteTimeTrack\"\n\t}\n})\n//#endregion \n\n//#region scale 165-190\ntrackOnNotesBetween(\"scaleTrack\", 165, 195)\nfor (let i = 0; i < 30; i++) {\n\tlet mult = 1\n\tlet dur = 0.5\n\tlet scaleVar = 3\n\t_obstacles.push({\n\t\t\"_time\":165+(i*mult),\n\t\t\"_duration\":dur,\n\t\t\"_lineIndex\":0,\n\t\t\"_type\":0,\n\t\t\"_width\":0,\n\t\t\"_customData\":{\n\t\t\t\"_position\":[-6,1],\n\t\t\t\"_scale\":[1+(Math.random()*scaleVar),1+(Math.random()*scaleVar)],\n\t\t\t\"_track\":\"scaleTrack\"\n\t\t}\n\t})\n\t\n}\n_pointDefinitions.push({\n\t\"_name\":\"AnimateTrackScale\",\n\t\"_points\":[\n\t\t[1,1,1,0],\n\t\t[0.80,0.80,0.80,0.15,\"easeOutCirc\"],\n\t\t[2,2,2,0.5,\"easeOutBounce\"],\n\t\t[2,2,2,0.6],\n\t\t[2.5,1,1,0.8,\"easeOutExpo\"],\n\t\t[1,1,1,1,\"easeOutBounce\"]\n\n\t]\n}, {\n\t\"_name\":\"PathScale\",\n\t\"_points\":[\n\t\t[1,1,1,0],\n\t\t[4,0.5,1,0.20,\"easeInElastic\"],\n\t\t[1,1,1,0.50,\"easeOutElastic\"]\n\n\t]\n})\n_customEvents.push({\n\t\"_time\":165,\n\t\"_type\":\"AnimateTrack\",\n\t\"_data\":{\n\t\t\"_track\":\"scaleTrack\",\n\t\t\"_scale\":\"AnimateTrackScale\",\n\t\t\"_duration\":5\n\t}\n}, {\n\t\"_time\":175,\n\t\"_type\":\"AssignPathAnimation\",\n\t\"_data\":{\n\t\t\"_track\":\"scaleTrack\",\n\t\t\"_scale\":\"PathScale\"\n\t}\n})\n\n\n//#endregion\n\n//#region write file\nconst precision = 4 //decimals to round to\n\nconst jsonP = Math.pow(10, precision)\nconst sortP = Math.pow(10, 2)\nfunction deeperDaddy(obj) {\n\tif (obj)\n\t\tfor (const key in obj) {\n\t\t\tif (obj[key] == null) {\n\t\t\t\tdelete obj[key]\n\t\t\t} else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) {\n\t\t\t\tdeeperDaddy(obj[key])\n\t\t\t} else if (typeof obj[key] == 'number') {\n\t\t\t\tobj[key] = parseFloat(Math.round((obj[key] + Number.EPSILON) * jsonP) / jsonP)\n\t\t\t}\n\t\t}\n}\ndeeperDaddy(difficulty)\n\ndifficulty._notes.sort(\n\t(a, b) =>\n\t\tparseFloat(Math.round((a._time + Number.EPSILON) * sortP) / sortP) - parseFloat(Math.round((b._time + Number.EPSILON) * sortP) / sortP) ||\n\t\tparseFloat(Math.round((a._lineIndex + Number.EPSILON) * sortP) / sortP) - parseFloat(Math.round((b._lineIndex + Number.EPSILON) * sortP) / sortP) ||\n\t\tparseFloat(Math.round((a._lineLayer + Number.EPSILON) * sortP) / sortP) - parseFloat(Math.round((b._lineLayer + Number.EPSILON) * sortP) / sortP)\n)\ndifficulty._obstacles.sort((a, b) => a._time - b._time)\ndifficulty._events.sort((a, b) => a._time - b._time)\n\nfs.writeFileSync(OUTPUT, JSON.stringify(difficulty, null, 0));\n\n//#endregion"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Aeroluna\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NoodleExtensions/Animation/AnimationController.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using CustomJSONData;\n    using UnityEngine;\n\n    public class AnimationController : MonoBehaviour\n    {\n        public static AnimationController? Instance { get; private set; }\n\n        public CustomEventCallbackController? CustomEventCallbackController { get; private set; }\n\n        internal static void CustomEventCallbackInit(CustomEventCallbackController customEventCallbackController)\n        {\n            if (customEventCallbackController.BeatmapData?.customData.Get<bool>(\"isMultiplayer\") ?? false)\n            {\n                return;\n            }\n\n            if (Instance != null)\n            {\n                Destroy(Instance);\n            }\n\n            Instance = customEventCallbackController.gameObject.AddComponent<AnimationController>();\n\n            Instance.CustomEventCallbackController = customEventCallbackController;\n            Instance.CustomEventCallbackController.AddCustomEventCallback(AssignPlayerToTrack.Callback);\n            Instance.CustomEventCallbackController.AddCustomEventCallback(AssignTrackParent.Callback);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/AnimationHelper.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using Heck.Animation;\n    using UnityEngine;\n    using static Heck.Animation.AnimationHelper;\n    using static Heck.NullableExtensions;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n    using static NoodleExtensions.Plugin;\n\n    public static class AnimationHelper\n    {\n        private static BasicBeatmapObjectManager? BeatmapObjectManager => HarmonyPatches.BeatmapObjectSpawnControllerStart.BeatmapObjectManager;\n\n        internal static void OnTrackCreated(Track track)\n        {\n            track.AddProperty(POSITION, PropertyType.Vector3);\n            track.AddProperty(ROTATION, PropertyType.Quaternion);\n            track.AddProperty(SCALE, PropertyType.Vector3);\n            track.AddProperty(LOCALROTATION, PropertyType.Quaternion);\n            track.AddProperty(DISSOLVE, PropertyType.Linear);\n            track.AddProperty(DISSOLVEARROW, PropertyType.Linear);\n            track.AddProperty(TIME, PropertyType.Linear);\n            track.AddProperty(CUTTABLE, PropertyType.Linear);\n\n            track.AddPathProperty(POSITION, PropertyType.Vector3);\n            track.AddPathProperty(ROTATION, PropertyType.Quaternion);\n            track.AddPathProperty(SCALE, PropertyType.Vector3);\n            track.AddPathProperty(LOCALROTATION, PropertyType.Quaternion);\n            track.AddPathProperty(DEFINITEPOSITION, PropertyType.Vector3);\n            track.AddPathProperty(DISSOLVE, PropertyType.Linear);\n            track.AddPathProperty(DISSOLVEARROW, PropertyType.Linear);\n            track.AddPathProperty(CUTTABLE, PropertyType.Linear);\n        }\n\n        internal static void GetDefinitePositionOffset(NoodleObjectData.AnimationObjectData? animationObject, IEnumerable<Track>? tracks, float time, out Vector3? definitePosition)\n        {\n            Vector3? pathDefinitePosition = animationObject?.LocalDefinitePosition?.Interpolate(time);\n\n            if (!pathDefinitePosition.HasValue && tracks != null)\n            {\n                if (tracks.Count() > 1)\n                {\n                    pathDefinitePosition = SumVectorNullables(tracks.Select(n => TryGetVector3PathProperty(n, DEFINITEPOSITION, time)));\n                }\n                else\n                {\n                    pathDefinitePosition = TryGetVector3PathProperty(tracks.First(), DEFINITEPOSITION, time);\n                }\n            }\n\n            if (pathDefinitePosition.HasValue)\n            {\n                Vector3? pathPosition = animationObject?.LocalPosition?.Interpolate(time);\n                Vector3? positionOffset = null;\n                if (tracks != null)\n                {\n                    if (tracks.Count() > 1)\n                    {\n                        pathPosition ??= SumVectorNullables(tracks.Select(n => TryGetVector3PathProperty(n, POSITION, time)));\n                        positionOffset = SumVectorNullables(SumVectorNullables(tracks.Select(n => TryGetProperty<Vector3?>(n, POSITION))), pathPosition);\n                    }\n                    else\n                    {\n                        Track track = tracks.First();\n                        pathPosition ??= TryGetVector3PathProperty(track, POSITION, time);\n                        positionOffset = SumVectorNullables(TryGetProperty<Vector3?>(track, POSITION), pathPosition);\n                    }\n                }\n                else\n                {\n                    positionOffset = pathPosition;\n                }\n\n                definitePosition = SumVectorNullables(positionOffset, pathDefinitePosition) * NoteLinesDistance;\n\n                if (LeftHandedMode)\n                {\n                    MirrorVectorNullable(ref definitePosition);\n                }\n            }\n            else\n            {\n                definitePosition = null;\n            }\n        }\n\n        internal static void GetObjectOffset(\n            NoodleObjectData.AnimationObjectData? animationObject,\n            IEnumerable<Track>? tracks,\n            float time,\n            out Vector3? positionOffset,\n            out Quaternion? rotationOffset,\n            out Vector3? scaleOffset,\n            out Quaternion? localRotationOffset,\n            out float? dissolve,\n            out float? dissolveArrow,\n            out float? cuttable)\n        {\n            /*\n             * position = SumVectorNullables\n             * rotation = MultQuaternionNullables\n             * scale = MultVectorNullables\n             * localRotation = MultQuaternionNullables\n             * dissolve = MultFloatNullables\n             * dissolveArrow = MultFloatNullables\n             * cuttable = MultFloatNullables\n             */\n            Vector3? pathPosition = null;\n            Quaternion? pathRotation = null;\n            Vector3? pathScale = null;\n            Quaternion? pathLocalRotation = null;\n            float? pathDissolve = null;\n            float? pathDissolveArrow = null;\n            float? pathCuttable = null;\n\n            if (animationObject != null)\n            {\n                pathPosition = animationObject.LocalPosition?.Interpolate(time);\n                pathRotation = animationObject.LocalRotation?.InterpolateQuaternion(time);\n                pathScale = animationObject.LocalScale?.Interpolate(time);\n                pathLocalRotation = animationObject.LocalLocalRotation?.InterpolateQuaternion(time);\n                pathDissolve = animationObject.LocalDissolve?.InterpolateLinear(time);\n                pathDissolveArrow = animationObject.LocalDissolveArrow?.InterpolateLinear(time);\n                pathCuttable = animationObject.LocalCuttable?.InterpolateLinear(time);\n            }\n\n            if (tracks != null)\n            {\n                Vector3? trackPosition;\n                Quaternion? trackRotation;\n                Vector3? trackScale;\n                Quaternion? trackLocalRotation;\n                float? trackDissolve;\n                float? trackDissolveArrow;\n                float? trackCuttable;\n\n                if (tracks.Count() > 1)\n                {\n                    pathPosition ??= SumVectorNullables(tracks.Select(n => TryGetVector3PathProperty(n, POSITION, time)));\n                    pathRotation ??= MultQuaternionNullables(tracks.Select(n => TryGetQuaternionPathProperty(n, ROTATION, time)));\n                    pathScale ??= MultVectorNullables(tracks.Select(n => TryGetVector3PathProperty(n, SCALE, time)));\n                    pathLocalRotation ??= MultQuaternionNullables(tracks.Select(n => TryGetQuaternionPathProperty(n, LOCALROTATION, time)));\n                    pathDissolve ??= MultFloatNullables(tracks.Select(n => TryGetLinearPathProperty(n, DISSOLVE, time)));\n                    pathDissolveArrow ??= MultFloatNullables(tracks.Select(n => TryGetLinearPathProperty(n, DISSOLVEARROW, time)));\n                    pathCuttable ??= MultFloatNullables(tracks.Select(n => TryGetLinearPathProperty(n, CUTTABLE, time)));\n\n                    trackPosition = SumVectorNullables(tracks.Select(n => TryGetProperty<Vector3?>(n, POSITION)));\n                    trackRotation = MultQuaternionNullables(tracks.Select(n => TryGetProperty<Quaternion?>(n, ROTATION)));\n                    trackScale = MultVectorNullables(tracks.Select(n => TryGetProperty<Vector3?>(n, SCALE)));\n                    trackLocalRotation = MultQuaternionNullables(tracks.Select(n => TryGetProperty<Quaternion?>(n, LOCALROTATION)));\n                    trackDissolve = MultFloatNullables(tracks.Select(n => TryGetProperty<float?>(n, DISSOLVE)));\n                    trackDissolveArrow = MultFloatNullables(tracks.Select(n => TryGetProperty<float?>(n, DISSOLVEARROW)));\n                    trackCuttable = MultFloatNullables(tracks.Select(n => TryGetProperty<float?>(n, CUTTABLE)));\n                }\n                else\n                {\n                    Track track = tracks.First();\n                    pathPosition ??= TryGetVector3PathProperty(track, POSITION, time);\n                    pathRotation ??= TryGetQuaternionPathProperty(track, ROTATION, time);\n                    pathScale ??= TryGetVector3PathProperty(track, SCALE, time);\n                    pathLocalRotation ??= TryGetQuaternionPathProperty(track, LOCALROTATION, time);\n                    pathDissolve ??= TryGetLinearPathProperty(track, DISSOLVE, time);\n                    pathDissolveArrow ??= TryGetLinearPathProperty(track, DISSOLVEARROW, time);\n                    pathCuttable ??= TryGetLinearPathProperty(track, CUTTABLE, time);\n\n                    trackPosition = TryGetProperty<Vector3?>(track, POSITION);\n                    trackRotation = TryGetProperty<Quaternion?>(track, ROTATION);\n                    trackScale = TryGetProperty<Vector3?>(track, SCALE);\n                    trackLocalRotation = TryGetProperty<Quaternion?>(track, LOCALROTATION);\n                    trackDissolve = TryGetProperty<float?>(track, DISSOLVE);\n                    trackDissolveArrow = TryGetProperty<float?>(track, DISSOLVEARROW);\n                    trackCuttable = TryGetProperty<float?>(track, CUTTABLE);\n                }\n\n                positionOffset = SumVectorNullables(trackPosition, pathPosition) * NoteLinesDistance;\n                rotationOffset = MultQuaternionNullables(trackRotation, pathRotation);\n                scaleOffset = MultVectorNullables(trackScale, pathScale);\n                localRotationOffset = MultQuaternionNullables(trackLocalRotation, pathLocalRotation);\n                dissolve = MultFloatNullables(trackDissolve, pathDissolve);\n                dissolveArrow = MultFloatNullables(trackDissolveArrow, pathDissolveArrow);\n                cuttable = MultFloatNullables(trackCuttable, pathCuttable);\n            }\n            else\n            {\n                positionOffset = pathPosition * NoteLinesDistance;\n                rotationOffset = pathRotation;\n                scaleOffset = pathScale;\n                localRotationOffset = pathLocalRotation;\n                dissolve = pathDissolve;\n                dissolveArrow = pathDissolveArrow;\n                cuttable = pathCuttable;\n            }\n\n            if (LeftHandedMode)\n            {\n                MirrorVectorNullable(ref positionOffset);\n                MirrorQuaternionNullable(ref rotationOffset);\n                MirrorQuaternionNullable(ref localRotationOffset);\n            }\n        }\n\n        internal static void GetAllPointData(\n            Dictionary<string, object?> customData,\n            Dictionary<string, PointDefinition> pointDefinitions,\n            out PointDefinition? position,\n            out PointDefinition? rotation,\n            out PointDefinition? scale,\n            out PointDefinition? localRotation,\n            out PointDefinition? dissolve,\n            out PointDefinition? dissolveArrow,\n            out PointDefinition? cuttable,\n            out PointDefinition? definitePosition)\n        {\n            TryGetPointData(customData, POSITION, out position, pointDefinitions);\n            TryGetPointData(customData, ROTATION, out rotation, pointDefinitions);\n            TryGetPointData(customData, SCALE, out scale, pointDefinitions);\n            TryGetPointData(customData, LOCALROTATION, out localRotation, pointDefinitions);\n            TryGetPointData(customData, DISSOLVE, out dissolve, pointDefinitions);\n            TryGetPointData(customData, DISSOLVEARROW, out dissolveArrow, pointDefinitions);\n            TryGetPointData(customData, CUTTABLE, out cuttable, pointDefinitions);\n            TryGetPointData(customData, DEFINITEPOSITION, out definitePosition, pointDefinitions);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/Events/AssignPlayerToTrack.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using System.Collections.Generic;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using Heck.Animation;\n    using static NoodleExtensions.Animation.NoodleEventDataManager;\n    using static NoodleExtensions.Plugin;\n\n    internal static class AssignPlayerToTrack\n    {\n        internal static void OnTrackManagerCreated(TrackBuilder trackManager, CustomBeatmapData customBeatmapData)\n        {\n            List<CustomEventData> customEventsData = customBeatmapData.customEventsData;\n            foreach (CustomEventData customEventData in customEventsData)\n            {\n                if (customEventData.type == ASSIGNPLAYERTOTRACK)\n                {\n                    string? trackName = customEventData.data.Get<string>(TRACK);\n                    if (trackName != null)\n                    {\n                        trackManager.AddTrack(trackName);\n                    }\n                }\n            }\n        }\n\n        internal static void Callback(CustomEventData customEventData)\n        {\n            if (customEventData.type == ASSIGNPLAYERTOTRACK)\n            {\n                NoodlePlayerTrackEventData? noodleData = TryGetEventData<NoodlePlayerTrackEventData>(customEventData);\n                if (noodleData != null)\n                {\n                    Track track = noodleData.Track;\n                    if (track != null)\n                    {\n                        PlayerTrack.AssignTrack(track);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/Events/AssignTrackParent.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using System.Collections.Generic;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using Heck.Animation;\n    using static NoodleExtensions.Animation.NoodleEventDataManager;\n    using static NoodleExtensions.Plugin;\n\n    internal class AssignTrackParent\n    {\n        internal static void OnTrackManagerCreated(TrackBuilder trackManager, CustomBeatmapData customBeatmapData)\n        {\n            List<CustomEventData> customEventsData = customBeatmapData.customEventsData;\n            foreach (CustomEventData customEventData in customEventsData)\n            {\n                if (customEventData.type == ASSIGNTRACKPARENT)\n                {\n                    string? trackName = customEventData.data.Get<string>(\"_parentTrack\");\n                    if (trackName != null)\n                    {\n                        trackManager.AddTrack(trackName);\n                    }\n                }\n            }\n        }\n\n        internal static void Callback(CustomEventData customEventData)\n        {\n            if (customEventData.type == ASSIGNTRACKPARENT)\n            {\n                NoodleParentTrackEventData? noodleData = TryGetEventData<NoodleParentTrackEventData>(customEventData);\n                if (noodleData != null)\n                {\n                    IEnumerable<Track> tracks = noodleData.ChildrenTracks;\n                    Track parentTrack = noodleData.ParentTrack;\n                    if (tracks != null && parentTrack != null)\n                    {\n                        ParentObject.AssignTrack(tracks, parentTrack, noodleData.Position, noodleData.Rotation, noodleData.LocalRotation, noodleData.Scale);\n                    }\n                    else\n                    {\n                        Logger.Log($\"Missing _parentTrack or _childrenTracks!\", IPA.Logging.Logger.Level.Error);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/Events/NoodleEventData.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using Heck.Animation;\n    using UnityEngine;\n    using static Heck.Animation.AnimationHelper;\n    using static NoodleExtensions.Plugin;\n\n    internal static class NoodleEventDataManager\n    {\n        private static Dictionary<CustomEventData, NoodleEventData> _noodleEventDatas = new Dictionary<CustomEventData, NoodleEventData>();\n\n        internal static T? TryGetEventData<T>(CustomEventData customEventData)\n        {\n            if (_noodleEventDatas.TryGetValue(customEventData, out NoodleEventData noodleEventData))\n            {\n                if (noodleEventData is T t)\n                {\n                    return t;\n                }\n                else\n                {\n                    throw new InvalidOperationException($\"NoodleEventData was not of correct type. Expected: {typeof(T).Name}, was: {noodleEventData.GetType().Name}\");\n                }\n            }\n\n            return default;\n        }\n\n        internal static void DeserializeBeatmapData(IReadonlyBeatmapData beatmapData)\n        {\n            _noodleEventDatas = new Dictionary<CustomEventData, NoodleEventData>();\n            foreach (CustomEventData customEventData in ((CustomBeatmapData)beatmapData).customEventsData)\n            {\n                try\n                {\n                    NoodleEventData noodleEventData;\n\n                    switch (customEventData.type)\n                    {\n                        case ASSIGNPLAYERTOTRACK:\n                            noodleEventData = new NoodlePlayerTrackEventData(GetTrack(customEventData.data, beatmapData) ?? throw new InvalidOperationException(\"Track was not defined.\"));\n                            break;\n\n                        case ASSIGNTRACKPARENT:\n                            noodleEventData = ProcessParentTrackEvent(customEventData.data, beatmapData);\n                            break;\n\n                        default:\n                            continue;\n                    }\n\n                    if (noodleEventData != null)\n                    {\n                        _noodleEventDatas.Add(customEventData, noodleEventData);\n                    }\n                }\n                catch (Exception e)\n                {\n                    Plugin.Logger.Log($\"Could not create NoodleEventData for event {customEventData.type} at {customEventData.time}\", IPA.Logging.Logger.Level.Error);\n                    Plugin.Logger.Log(e, IPA.Logging.Logger.Level.Error);\n                }\n            }\n        }\n\n        private static NoodleParentTrackEventData ProcessParentTrackEvent(Dictionary<string, object?> customData, IReadonlyBeatmapData beatmapData)\n        {\n            IEnumerable<float>? position = customData.Get<List<object>>(POSITION)?.Select(n => Convert.ToSingle(n));\n            Vector3? posVector = null;\n            if (position != null)\n            {\n                posVector = new Vector3(position.ElementAt(0), position.ElementAt(1), position.ElementAt(2));\n            }\n\n            IEnumerable<float>? rotation = customData.Get<List<object>>(ROTATION)?.Select(n => Convert.ToSingle(n));\n            Quaternion? rotQuaternion = null;\n            if (rotation != null)\n            {\n                rotQuaternion = Quaternion.Euler(rotation.ElementAt(0), rotation.ElementAt(1), rotation.ElementAt(2));\n            }\n\n            IEnumerable<float>? localrot = customData.Get<List<object>>(LOCALROTATION)?.Select(n => Convert.ToSingle(n));\n            Quaternion? localRotQuaternion = null;\n            if (localrot != null)\n            {\n                localRotQuaternion = Quaternion.Euler(localrot.ElementAt(0), localrot.ElementAt(1), localrot.ElementAt(2));\n            }\n\n            IEnumerable<float>? scale = customData.Get<List<object>>(SCALE)?.Select(n => Convert.ToSingle(n));\n            Vector3? scaleVector = null;\n            if (scale != null)\n            {\n                scaleVector = new Vector3(scale.ElementAt(0), scale.ElementAt(1), scale.ElementAt(2));\n            }\n\n            return new NoodleParentTrackEventData(\n                GetTrack(customData, beatmapData, \"_parentTrack\") ?? throw new InvalidOperationException(\"Parent track was not defined.\"),\n                GetTrackArray(customData, beatmapData, \"_childrenTracks\") ?? throw new InvalidOperationException(\"Children track was not defined.\"),\n                posVector,\n                rotQuaternion,\n                localRotQuaternion,\n                scaleVector);\n        }\n    }\n\n    internal record NoodlePlayerTrackEventData : NoodleEventData\n    {\n        internal NoodlePlayerTrackEventData(Track track)\n        {\n            Track = track;\n        }\n\n        internal Track Track { get; set; }\n    }\n\n    internal record NoodleParentTrackEventData : NoodleEventData\n    {\n        internal NoodleParentTrackEventData(Track parentTrack, IEnumerable<Track> childrenTracks, Vector3? position, Quaternion? rotation, Quaternion? localRotation, Vector3? scale)\n        {\n            ParentTrack = parentTrack;\n            ChildrenTracks = childrenTracks;\n            Position = position;\n            Rotation = rotation;\n            LocalRotation = localRotation;\n            Scale = scale;\n        }\n\n        internal Track ParentTrack { get; }\n\n        internal IEnumerable<Track> ChildrenTracks { get; }\n\n        internal Vector3? Position { get; }\n\n        internal Quaternion? Rotation { get; }\n\n        internal Quaternion? LocalRotation { get; }\n\n        internal Vector3? Scale { get; }\n    }\n\n    internal record NoodleEventData\n    {\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/ParentObject.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using Heck.Animation;\n    using UnityEngine;\n    using static Heck.Animation.AnimationHelper;\n    using static Heck.NullableExtensions;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n    using static NoodleExtensions.Plugin;\n\n    internal class ParentObject : MonoBehaviour\n    {\n        private Track? _track;\n        private Transform? _origin;\n        private Vector3 _startPos = Vector3.zero;\n        private Quaternion _startRot = Quaternion.identity;\n        private Quaternion _startLocalRot = Quaternion.identity;\n        private Vector3 _startScale = Vector3.one;\n\n        internal static ParentController? Controller { get; private set; }\n\n        internal HashSet<Track> ChildrenTracks { get; } = new HashSet<Track>();\n\n        internal static void AssignTrack(IEnumerable<Track> tracks, Track parentTrack, Vector3? startPos, Quaternion? startRot, Quaternion? startLocalRot, Vector3? startScale)\n        {\n            if (tracks.Contains(parentTrack))\n            {\n                throw new System.InvalidOperationException(\"How could a track contain itself?\");\n            }\n\n            if (Controller == null)\n            {\n                GameObject gameObject = new GameObject(\"ParentController\");\n                Controller = gameObject.AddComponent<ParentController>();\n            }\n\n            GameObject parentGameObject = new GameObject(\"ParentObject\");\n            ParentObject instance = parentGameObject.AddComponent<ParentObject>();\n            instance._origin = parentGameObject.transform;\n            instance._track = parentTrack;\n\n            Transform transform = instance.transform;\n            if (startPos.HasValue)\n            {\n                instance._startPos = startPos.Value;\n                transform.localPosition = instance._startPos * NoteLinesDistance;\n            }\n\n            if (startRot.HasValue)\n            {\n                instance._startRot = startRot.Value;\n                instance._startLocalRot = instance._startRot;\n                transform.localPosition = instance._startRot * transform.localPosition;\n                transform.localRotation = instance._startRot;\n            }\n\n            if (startLocalRot.HasValue)\n            {\n                instance._startLocalRot = instance._startRot * startLocalRot.Value;\n                transform.localRotation *= instance._startLocalRot;\n            }\n\n            if (startScale.HasValue)\n            {\n                instance._startScale = startScale.Value;\n                transform.localScale = instance._startScale;\n            }\n\n            parentTrack.AddGameObject(parentGameObject);\n\n            foreach (Track track in tracks)\n            {\n                foreach (ParentObject parentObject in Controller.ParentObjects)\n                {\n                    track.OnGameObjectAdded -= parentObject.OnTrackGameObjectAdded;\n                    track.OnGameObjectRemoved -= parentObject.OnTrackGameObjectRemoved;\n                    parentObject.ChildrenTracks.Remove(track);\n                }\n\n                foreach (GameObject gameObject in track.GameObjects)\n                {\n                    instance.ParentToObject(gameObject.transform);\n                }\n\n                instance.ChildrenTracks.Add(track);\n\n                track.OnGameObjectAdded += instance.OnTrackGameObjectAdded;\n                track.OnGameObjectRemoved += instance.OnTrackGameObjectRemoved;\n            }\n\n            Controller.ParentObjects.Add(instance);\n        }\n\n        private static void ResetTransformParent(Transform transform)\n        {\n            transform.SetParent(null, false);\n        }\n\n        private void OnTrackGameObjectAdded(GameObject gameObject)\n        {\n            ParentToObject(gameObject.transform);\n        }\n\n        private void OnTrackGameObjectRemoved(GameObject gameObject)\n        {\n            ResetTransformParent(gameObject.transform);\n        }\n\n        private void ParentToObject(Transform transform)\n        {\n            transform.SetParent(_origin!.transform, false);\n        }\n\n        private void OnDestroy()\n        {\n            foreach (Track track in ChildrenTracks)\n            {\n                track.OnGameObjectAdded -= OnTrackGameObjectAdded;\n                track.OnGameObjectRemoved -= OnTrackGameObjectRemoved;\n            }\n        }\n\n        private void Update()\n        {\n            Quaternion? rotation = TryGetProperty<Quaternion?>(_track, ROTATION);\n            if (rotation.HasValue)\n            {\n                if (LeftHandedMode)\n                {\n                    MirrorQuaternionNullable(ref rotation);\n                }\n            }\n\n            Vector3? position = TryGetProperty<Vector3?>(_track, POSITION);\n            if (position.HasValue)\n            {\n                if (LeftHandedMode)\n                {\n                    MirrorVectorNullable(ref position);\n                }\n            }\n\n            Quaternion worldRotationQuatnerion = _startRot;\n            Vector3 positionVector = worldRotationQuatnerion * (_startPos * NoteLinesDistance);\n            if (rotation.HasValue || position.HasValue)\n            {\n                Quaternion rotationOffset = rotation ?? Quaternion.identity;\n                worldRotationQuatnerion *= rotationOffset;\n                Vector3 positionOffset = position ?? Vector3.zero;\n                positionVector = worldRotationQuatnerion * ((positionOffset + _startPos) * NoteLinesDistance);\n            }\n\n            worldRotationQuatnerion *= _startLocalRot;\n            Quaternion? localRotation = TryGetProperty<Quaternion?>(_track, LOCALROTATION);\n            if (localRotation.HasValue)\n            {\n                if (LeftHandedMode)\n                {\n                    MirrorQuaternionNullable(ref localRotation);\n                }\n\n                worldRotationQuatnerion *= localRotation!.Value;\n            }\n\n            Vector3 scaleVector = _startScale;\n            Vector3? scale = TryGetProperty<Vector3?>(_track, SCALE);\n            if (scale.HasValue)\n            {\n                scaleVector = Vector3.Scale(_startScale, scale.Value);\n            }\n\n            if (_origin!.localRotation != worldRotationQuatnerion)\n            {\n                _origin.localRotation = worldRotationQuatnerion;\n            }\n\n            if (_origin.localPosition != positionVector)\n            {\n                _origin.localPosition = positionVector;\n            }\n\n            if (_origin.localScale != scaleVector)\n            {\n                _origin.localScale = scaleVector;\n            }\n        }\n    }\n\n    internal class ParentController : MonoBehaviour\n    {\n        internal HashSet<ParentObject> ParentObjects { get; } = new HashSet<ParentObject>();\n\n        internal ParentObject? GetParentObjectTrack(Track track)\n        {\n            ParentObject filteredParent = ParentObjects.FirstOrDefault(n => n.ChildrenTracks.Contains(track));\n            if (filteredParent != null)\n            {\n                return filteredParent;\n            }\n            else\n            {\n                return null;\n            }\n        }\n\n        internal ParentObject? GetParentObjectTrackArray(IEnumerable<Track> tracks)\n        {\n            ParentObject filteredParent = ParentObjects.FirstOrDefault(n => n.ChildrenTracks.Intersect(tracks).Any());\n            if (filteredParent != null)\n            {\n                return filteredParent;\n            }\n            else\n            {\n                return null;\n            }\n        }\n\n        private void OnDestroy()\n        {\n            ParentObjects.Clear();\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Animation/PlayerTrack.cs",
    "content": "﻿namespace NoodleExtensions.Animation\n{\n    using Heck.Animation;\n    using IPA.Utilities;\n    using UnityEngine;\n    using static Heck.Animation.AnimationHelper;\n    using static Heck.NullableExtensions;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n    using static NoodleExtensions.Plugin;\n\n    internal class PlayerTrack : MonoBehaviour\n    {\n        private static readonly FieldAccessor<PauseController, bool>.Accessor _pausedAccessor = FieldAccessor<PauseController, bool>.GetAccessor(\"_paused\");\n\n        private static PlayerTrack? _instance;\n        private static Track? _track;\n        private static Vector3 _startPos = Vector3.zero;\n        private static Quaternion _startRot = Quaternion.identity;\n        private static Quaternion _startLocalRot = Quaternion.identity;\n        private static Transform? _origin;\n        private static PauseController? _pauseController;\n\n        internal static void AssignTrack(Track track)\n        {\n            if (_instance == null)\n            {\n                GameObject gameObject = GameObject.Find(\"LocalPlayerGameCore\");\n                GameObject noodleObject = new GameObject(\"NoodlePlayerTrack\");\n                _origin = noodleObject.transform;\n                _origin.SetParent(gameObject.transform.parent, true);\n                gameObject.transform.SetParent(_origin, true);\n\n                _instance = noodleObject.AddComponent<PlayerTrack>();\n                _pauseController = FindObjectOfType<PauseController>();\n                if (_pauseController != null)\n                {\n                    _pauseController.didPauseEvent += _instance.OnDidPauseEvent;\n                }\n\n                _startLocalRot = _origin.localRotation;\n                _startPos = _origin.localPosition;\n            }\n\n            _track = track;\n        }\n\n        private void OnDidPauseEvent()\n        {\n            _origin!.localRotation = _startLocalRot;\n            _origin!.localPosition = _startPos;\n        }\n\n        private void OnDestroy()\n        {\n            if (_pauseController != null)\n            {\n                _pauseController.didPauseEvent -= OnDidPauseEvent;\n            }\n        }\n\n        private void Update()\n        {\n            bool paused = false;\n            if (_pauseController != null)\n            {\n                paused = _pausedAccessor(ref _pauseController);\n            }\n\n            if (!paused)\n            {\n                Quaternion? rotation = TryGetProperty<Quaternion?>(_track, ROTATION);\n                if (rotation.HasValue)\n                {\n                    if (LeftHandedMode)\n                    {\n                        MirrorQuaternionNullable(ref rotation);\n                    }\n                }\n\n                Vector3? position = TryGetProperty<Vector3?>(_track, POSITION);\n                if (position.HasValue)\n                {\n                    if (LeftHandedMode)\n                    {\n                        MirrorVectorNullable(ref position);\n                    }\n                }\n\n                Quaternion worldRotationQuatnerion = _startRot;\n                Vector3 positionVector = _startPos;\n                if (rotation.HasValue || position.HasValue)\n                {\n                    Quaternion finalRot = rotation ?? Quaternion.identity;\n                    worldRotationQuatnerion *= finalRot;\n                    Vector3 finalPos = position ?? Vector3.zero;\n                    positionVector = worldRotationQuatnerion * ((finalPos * NoteLinesDistance) + _startPos);\n                }\n\n                worldRotationQuatnerion *= _startLocalRot;\n                Quaternion? localRotation = TryGetProperty<Quaternion?>(_track, LOCALROTATION);\n                if (localRotation.HasValue)\n                {\n                    if (LeftHandedMode)\n                    {\n                        MirrorQuaternionNullable(ref localRotation);\n                    }\n\n                    worldRotationQuatnerion *= localRotation!.Value;\n                }\n\n                if (_origin!.localRotation != worldRotationQuatnerion)\n                {\n                    _origin.localRotation = worldRotationQuatnerion;\n                }\n\n                if (_origin.localPosition != positionVector)\n                {\n                    _origin.localPosition = positionVector;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/CutoutManager.cs",
    "content": "﻿namespace NoodleExtensions\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using UnityEngine;\n\n    internal static class CutoutManager\n    {\n        internal static Dictionary<ObstacleControllerBase, CutoutAnimateEffectWrapper> ObstacleCutoutEffects { get; } = new Dictionary<ObstacleControllerBase, CutoutAnimateEffectWrapper>();\n\n        internal static Dictionary<NoteControllerBase, CutoutEffectWrapper> NoteCutoutEffects { get; } = new Dictionary<NoteControllerBase, CutoutEffectWrapper>();\n\n        internal static Dictionary<NoteControllerBase, DisappearingArrowWrapper> NoteDisappearingArrowWrappers { get; } = new Dictionary<NoteControllerBase, DisappearingArrowWrapper>();\n    }\n\n    internal abstract class CutoutWrapper\n    {\n        internal float Cutout { get; private set; } = 1;\n\n        internal virtual void SetCutout(float cutout)\n        {\n            Cutout = cutout;\n        }\n    }\n\n    internal class CutoutEffectWrapper : CutoutWrapper\n    {\n        private readonly CutoutEffect _cutoutEffect;\n\n        internal CutoutEffectWrapper(CutoutEffect cutoutEffect)\n        {\n            _cutoutEffect = cutoutEffect;\n        }\n\n        internal override void SetCutout(float cutout)\n        {\n            base.SetCutout(cutout);\n            _cutoutEffect.SetCutout(1 - cutout);\n        }\n    }\n\n    internal class CutoutAnimateEffectWrapper : CutoutWrapper\n    {\n        private readonly CutoutAnimateEffect _cutoutAnimateEffect;\n\n        internal CutoutAnimateEffectWrapper(CutoutAnimateEffect cutoutAnimateEffect)\n        {\n            _cutoutAnimateEffect = cutoutAnimateEffect;\n        }\n\n        internal override void SetCutout(float cutout)\n        {\n            base.SetCutout(cutout);\n            _cutoutAnimateEffect.SetCutout(1 - cutout);\n        }\n    }\n\n    internal class DisappearingArrowWrapper : CutoutWrapper\n    {\n        private readonly MonoBehaviour _disappearingArrowController;\n\n        private readonly MethodInfo _method;\n\n        internal DisappearingArrowWrapper(MonoBehaviour disappearingArrowController, MethodInfo method)\n        {\n            _disappearingArrowController = disappearingArrowController;\n            _method = method;\n        }\n\n        internal override void SetCutout(float cutout)\n        {\n            base.SetCutout(cutout);\n\n            // gross nasty reflection\n            _method.Invoke(_disappearingArrowController, new object[] { cutout });\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Directory.Build.props",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- This file contains project properties used by the build. -->\n<Project>\n  <ItemGroup>\n    <SourceRoot Include=\"$(MSBuildThisFileDirectory)/\"/>\n  </ItemGroup>\n  <PropertyGroup Condition=\"'$(GITHUB_ACTIONS)' == 'true'\">\n    <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>\n    <DisableCopyToPlugins>true</DisableCopyToPlugins>\n    <DisableZipRelease>true</DisableZipRelease>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(NCrunch)' == '1'\">\n    <ContinuousIntegrationBuild>false</ContinuousIntegrationBuild>\n    <DisableCopyToPlugins>true</DisableCopyToPlugins>\n    <DisableZipRelease>true</DisableZipRelease>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "NoodleExtensions/Directory.Build.targets",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- This file contains the build tasks and targets for verifying the manifest, zipping Release builds,\n     and copying the plugin to to your Beat Saber folder. Only edit this if you know what you are doing. -->\n<Project>\n  <PropertyGroup>\n    <BuildTargetsVersion>2.0</BuildTargetsVersion>\n    <!--Set this to true if you edit this file to prevent automatic updates-->\n    <BuildTargetsModified>false</BuildTargetsModified>\n    <!--Output assembly path without extension-->\n    <OutputAssemblyName>$(OutputPath)$(AssemblyName)</OutputAssemblyName>\n    <!--Path to folder to be zipped. Needs to be relative to the project directory to work without changes to the 'BuildForCI' target.-->\n    <ArtifactDestination>$(OutputPath)Final</ArtifactDestination>\n    <ErrorOnMismatchedVersions Condition=\"'$(Configuration)' == 'Release'\">True</ErrorOnMismatchedVersions>\n  </PropertyGroup>\n  <!--Build Targets-->\n  <!--Displays a warning if BeatSaberModdingTools.Tasks is not installed.-->\n  <Target Name=\"CheckBSMTInstalled\" AfterTargets=\"BeforeBuild\" Condition=\"'$(BSMTTaskAssembly)' == ''\">\n    <Warning Text=\"The BeatSaberModdingTools.Tasks nuget package doesn't seem to be installed, advanced build targets will not work.\" />\n  </Target>\n  <!--Runs a build task to get info about the project used by later targets.-->\n  <Target Name=\"GetProjectInfo\" AfterTargets=\"CheckBSMTInstalled\" DependsOnTargets=\"CheckBSMTInstalled\" Condition=\"'$(BSMTTaskAssembly)' != ''\">\n    <Message Text=\"Using AssemblyVersion defined in project instead of 'Properties\\AssemblyInfo.cs'\" Importance=\"high\" Condition=\"'$(AssemblyVersion)' != ''\" />\n    <GetManifestInfo FailOnError=\"$(ErrorOnMismatchedVersions)\">\n      <Output TaskParameter=\"PluginVersion\" PropertyName=\"PluginVersion\" />\n      <Output TaskParameter=\"BasePluginVersion\" PropertyName=\"BasePluginVersion\" />\n      <Output TaskParameter=\"GameVersion\" PropertyName=\"GameVersion\" />\n    </GetManifestInfo>\n    <PropertyGroup>\n      <AssemblyVersion>$(BasePluginVersion)</AssemblyVersion>\n      <FileVersion>$(BasePluginVersion)</FileVersion>\n      <InformationalVersion>$(BasePluginVersion)</InformationalVersion>\n    </PropertyGroup>\n    <GetCommitInfo ProjectDir=\"$(ProjectDir)\">\n      <Output TaskParameter=\"CommitHash\" PropertyName=\"CommitHash\" />\n      <Output TaskParameter=\"Branch\" PropertyName=\"Branch\" />\n      <Output TaskParameter=\"Modified\" PropertyName=\"GitModified\" />\n    </GetCommitInfo>\n    <PropertyGroup>\n      <!--Build name for artifact/zip file-->\n      <ArtifactName>$(AssemblyName)</ArtifactName>\n      <ArtifactName Condition=\"'$(PluginVersion)' != ''\">$(ArtifactName)-$(PluginVersion)</ArtifactName>\n      <ArtifactName Condition=\"'$(GameVersion)' != ''\">$(ArtifactName)-bs$(GameVersion)</ArtifactName>\n      <ArtifactName Condition=\"'$(CommitHash)' != '' AND '$(CommitHash)' != 'local'\">$(ArtifactName)-$(CommitHash)</ArtifactName>\n    </PropertyGroup>\n  </Target>\n  <!--Build target for Continuous Integration builds. Set up for GitHub Actions.-->\n  <Target Name=\"BuildForCI\" AfterTargets=\"Build\" DependsOnTargets=\"GetProjectInfo\" Condition=\"'$(ContinuousIntegrationBuild)' == 'True' AND '$(BSMTTaskAssembly)' != ''\">\n    <PropertyGroup>\n      <!--Set 'ArtifactName' if it failed before.-->\n      <ArtifactName Condition=\"'$(ArtifactName)' == ''\">$(AssemblyName)</ArtifactName>\n    </PropertyGroup>\n    <Message Text=\"Building for CI\" Importance=\"high\" />\n    <Message Text=\"PluginVersion: $(PluginVersion), AssemblyVersion: $(AssemblyVersion), GameVersion: $(GameVersion)\" Importance=\"high\" />\n    <Message Text=\"::set-output name=filename::$(ArtifactName)\" Importance=\"high\" />\n    <Message Text=\"::set-output name=assemblyname::$(AssemblyName)\" Importance=\"high\" />\n    <Message Text=\"::set-output name=artifactpath::$(ProjectDir)$(ArtifactDestination)\" Importance=\"high\" />\n    <Message Text=\"Copying '$(OutputAssemblyName).dll' to '$(ProjectDir)$(ArtifactDestination)\\Plugins\\$(AssemblyName).dll'\" Importance=\"high\" />\n    <Copy SourceFiles=\"$(OutputAssemblyName).dll\" DestinationFiles=\"$(ProjectDir)$(ArtifactDestination)\\Plugins\\$(AssemblyName).dll\" />\n  </Target>\n  <!--Creates a BeatMods compliant zip file with the release.-->\n  <Target Name=\"ZipRelease\" AfterTargets=\"Build\" Condition=\"'$(DisableZipRelease)' != 'True' AND '$(Configuration)' == 'Release' AND '$(BSMTTaskAssembly)' != ''\">\n    <PropertyGroup>\n      <!--Set 'ArtifactName' if it failed before.-->\n      <ArtifactName Condition=\"'$(ArtifactName)' == ''\">$(AssemblyName)</ArtifactName>\n      <DestinationDirectory>$(OutDir)zip\\</DestinationDirectory>\n    </PropertyGroup>\n    <ItemGroup>\n      <OldZips Include=\"$(DestinationDirectory)$(AssemblyName)*.zip\"/>\n    </ItemGroup>\n    <Copy SourceFiles=\"$(OutputAssemblyName).dll\" DestinationFiles=\"$(ArtifactDestination)\\Plugins\\$(AssemblyName).dll\" />\n    <Message Text=\"PluginVersion: $(PluginVersion), AssemblyVersion: $(AssemblyVersion), GameVersion: $(GameVersion)\" Importance=\"high\" />\n    <Delete Files=\"@(OldZips)\" TreatErrorsAsWarnings=\"true\" ContinueOnError=\"true\" />\n    <ZipDir SourceDirectory=\"$(ArtifactDestination)\" DestinationFile=\"$(DestinationDirectory)$(ArtifactName).zip\" />\n  </Target>\n  <!--Copies the assembly and pdb to the Beat Saber folder.-->\n  <Target Name=\"CopyToPlugins\" AfterTargets=\"Build\" Condition=\"'$(DisableCopyToPlugins)' != 'True' AND '$(ContinuousIntegrationBuild)' != 'True'\">\n    <PropertyGroup>\n      <PluginDir>$(BeatSaberDir)\\Plugins</PluginDir>\n      <CanCopyToPlugins>True</CanCopyToPlugins>\n      <CopyToPluginsError Condition=\"!Exists('$(PluginDir)')\">Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'.</CopyToPluginsError>\n      <!--Error if 'BeatSaberDir' does not have 'Beat Saber.exe'-->\n      <CopyToPluginsError Condition=\"!Exists('$(BeatSaberDir)\\Beat Saber.exe')\">Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install.</CopyToPluginsError>\n      <!--Error if 'BeatSaberDir' is the same as 'LocalRefsDir'-->\n      <CopyToPluginsError Condition=\"'$(BeatSaberDir)' == '$(LocalRefsDir)' OR '$(BeatSaberDir)' == ''\">Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file.</CopyToPluginsError>\n      <CanCopyToPlugins Condition=\"'$(CopyToPluginsError)' != ''\">False</CanCopyToPlugins>\n    </PropertyGroup>\n    <!--Check if Beat Saber is running-->\n    <IsProcessRunning ProcessName=\"Beat Saber\" Condition=\"'$(BSMTTaskAssembly)' != ''\">\n      <Output TaskParameter=\"IsRunning\" PropertyName=\"IsRunning\" />\n    </IsProcessRunning>\n    <PropertyGroup>\n      <!--If Beat Saber is running, output to the Pending folder-->\n      <PluginDir Condition=\"'$(IsRunning)' == 'True'\">$(BeatSaberDir)\\IPA\\Pending\\Plugins</PluginDir>\n    </PropertyGroup>\n    <Warning Text=\"$(CopyToPluginsError)\" Condition=\"'$(CopyToPluginsError)' != ''\" />\n    <Message Text=\"Copying '$(OutputAssemblyName).dll' to '$(PluginDir)'.\" Importance=\"high\" Condition=\"$(CanCopyToPlugins)\" />\n    <Copy SourceFiles=\"$(OutputAssemblyName).dll\" DestinationFiles=\"$(PluginDir)\\$(AssemblyName).dll\" Condition=\"$(CanCopyToPlugins)\"  />\n    <Copy SourceFiles=\"$(OutputAssemblyName).pdb\" DestinationFiles=\"$(PluginDir)\\$(AssemblyName).pdb\" Condition=\"'$(CanCopyToPlugins)' == 'True' AND Exists('$(OutputAssemblyName).pdb')\"  />\n    <Warning Text=\"Beat Saber is running, restart the game to use the latest build.\" Condition=\"'$(IsRunning)' == 'True'\" />\n  </Target>\n</Project>"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapDataLoader.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n\n    [HarmonyPatch(typeof(BeatmapDataLoader))]\n    [HarmonyPatch(\"GetBeatmapDataFromBeatmapSaveData\")]\n    internal static class BeatmapDataLoaderGetBeatmapDataFromBeatmapSaveData\n    {\n        private static void Postfix(BeatmapData __result, float startBpm)\n        {\n            if (__result is CustomBeatmapData customBeatmapData)\n            {\n                foreach (BeatmapLineData beatmapLineData in customBeatmapData.beatmapLinesData)\n                {\n                    foreach (BeatmapObjectData beatmapObjectData in beatmapLineData.beatmapObjectsData)\n                    {\n                        Dictionary<string, object?> dynData = beatmapObjectData.GetDataForObject();\n\n                        // TODO: account for base game bpm changes\n                        // for per object njs and spawn offset\n                        float bpm = startBpm;\n                        dynData[\"bpm\"] = bpm;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapDataTransformHelper.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using IPA.Utilities;\n    using static NoodleExtensions.Plugin;\n\n    [HeckPatch(typeof(BeatmapDataTransformHelper))]\n    [HeckPatch(\"CreateTransformedBeatmapData\")]\n    internal static class BeatmapDataTransformHelperCreateTransformedBeatmapData\n    {\n        private static readonly MethodInfo _reorderLineData = AccessTools.Method(typeof(BeatmapDataTransformHelperCreateTransformedBeatmapData), nameof(ReorderLineData));\n\n        private static readonly FieldAccessor<BeatmapLineData, List<BeatmapObjectData>>.Accessor _beatmapObjectsDataAccessor = FieldAccessor<BeatmapLineData, List<BeatmapObjectData>>.GetAccessor(\"_beatmapObjectsData\");\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(\n                    false,\n                    new CodeMatch(OpCodes.Ldloc_0),\n                    new CodeMatch(OpCodes.Ldarg_0),\n                    new CodeMatch(OpCodes.Bne_Un))\n                .Advance(1)\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Call, _reorderLineData),\n                    new CodeInstruction(OpCodes.Stloc_0),\n                    new CodeInstruction(OpCodes.Ldloc_0)) // Replace the opcode we replace\n                .InstructionEnumeration();\n        }\n\n        private static IReadonlyBeatmapData ReorderLineData(IReadonlyBeatmapData beatmapData)\n        {\n            if (beatmapData is CustomBeatmapData)\n            {\n                CustomBeatmapData customBeatmapData = (CustomBeatmapData)beatmapData.GetCopy();\n\n                // there is some ambiguity with these variables but who frikkin cares\n                float startHalfJumpDurationInBeats = 4;\n                float maxHalfJumpDistance = 18;\n                float moveDuration = 0.5f;\n\n                for (int i = 0; i < customBeatmapData.beatmapLinesData.Count; i++)\n                {\n                    BeatmapLineData beatmapLineData = (BeatmapLineData)customBeatmapData.beatmapLinesData[i];\n                    foreach (BeatmapObjectData beatmapObjectData in beatmapLineData.beatmapObjectsData)\n                    {\n                        Dictionary<string, object?> dynData = beatmapObjectData.GetDataForObject();\n\n                        float noteJumpMovementSpeed = dynData.Get<float?>(NOTEJUMPSPEED) ?? GameplayCoreInstallerInstallBindings.CachedNoteJumpMovementSpeed;\n                        float noteJumpStartBeatOffset = dynData.Get<float?>(NOTESPAWNOFFSET) ?? GameplayCoreInstallerInstallBindings.CachedNoteJumpStartBeatOffset;\n\n                        // how do i not repeat this in a reasonable way\n                        float num = 60f / dynData.Get<float>(\"bpm\");\n                        float num2 = startHalfJumpDurationInBeats;\n                        while (noteJumpMovementSpeed * num * num2 > maxHalfJumpDistance)\n                        {\n                            num2 /= 2f;\n                        }\n\n                        num2 += noteJumpStartBeatOffset;\n                        if (num2 < 1f)\n                        {\n                            num2 = 1f;\n                        }\n\n                        float jumpDuration = num * num2 * 2f;\n                        dynData[\"aheadTime\"] = moveDuration + (jumpDuration * 0.5f);\n                    }\n\n                    _beatmapObjectsDataAccessor(ref beatmapLineData) = beatmapLineData.beatmapObjectsData\n                        .OrderBy(n => n.time - (float)(n.GetDataForObject()[\"aheadTime\"] ?? throw new System.InvalidOperationException($\"Could not get aheadTime for [{n.GetType().FullName}] at time [{n.time}].\")))\n                        .ToList();\n                }\n\n                return customBeatmapData;\n            }\n\n            Logger.Log(\"beatmapData was not CustomBeatmapData\", IPA.Logging.Logger.Level.Error);\n            return beatmapData;\n        }\n\n        private static void Postfix(IReadonlyBeatmapData __result)\n        {\n            // Skip if calling class is MultiplayerConnectPlayerInstaller\n            StackTrace stackTrace = new StackTrace();\n            if (!stackTrace.GetFrame(2).GetMethod().Name.Contains(\"MultiplayerConnectedPlayerInstaller\"))\n            {\n                NoodleObjectDataManager.DeserializeBeatmapData(__result);\n                Animation.NoodleEventDataManager.DeserializeBeatmapData(__result);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapObjectCallBackController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    [HeckPatch(typeof(BeatmapObjectCallbackController))]\n    [HeckPatch(\"LateUpdate\")]\n    internal static class BeatmapObjectCallBackControllerLateUpdate\n    {\n        private static readonly FieldInfo _aheadTimeField = AccessTools.Field(typeof(BeatmapObjectCallbackData), nameof(BeatmapObjectCallbackData.aheadTime));\n\n        private static readonly MethodInfo _getAheadTime = AccessTools.Method(typeof(BeatmapObjectCallBackControllerLateUpdate), nameof(GetAheadTime));\n        private static readonly MethodInfo _beatmapObjectSpawnControllerCallback = AccessTools.Method(typeof(BeatmapObjectSpawnController), nameof(BeatmapObjectSpawnController.HandleBeatmapObjectCallback));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Ldfld, _aheadTimeField))\n                .Advance(-1)\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldloc_1),\n                    new CodeInstruction(OpCodes.Ldloc_3))\n                .Advance(2)\n                .Insert(new CodeInstruction(OpCodes.Call, _getAheadTime))\n                .InstructionEnumeration();\n        }\n\n        private static float GetAheadTime(BeatmapObjectCallbackData beatmapObjectCallbackData, BeatmapObjectData beatmapObjectData, float @default)\n        {\n            if (beatmapObjectCallbackData.callback.Method == _beatmapObjectSpawnControllerCallback &&\n                (beatmapObjectData is CustomObstacleData || beatmapObjectData is CustomNoteData))\n            {\n                NoodleObjectData? noodleData = TryGetObjectData<NoodleObjectData>(beatmapObjectData);\n                if (noodleData != null)\n                {\n                    float? aheadTime = noodleData.AheadTimeInternal;\n                    if (aheadTime.HasValue)\n                    {\n                        return aheadTime.Value;\n                    }\n                }\n            }\n\n            return @default;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapObjectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n    using Heck.Animation;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    [HeckPatch(typeof(BeatmapObjectManager))]\n    [HeckPatch(\"HandleNoteControllerNoteDidFinishJump\")]\n    internal static class BeatmapObjectManagerHandleNoteControllerNoteDidFinishJump\n    {\n        private static void Prefix(NoteController noteController)\n        {\n            NoodleObjectData? noodleData = TryGetObjectData<NoodleObjectData>(noteController.noteData);\n            if (noodleData?.Track != null)\n            {\n                foreach (Track track in noodleData.Track)\n                {\n                    track.RemoveGameObject(noteController.gameObject);\n                }\n            }\n        }\n    }\n\n    [HeckPatch(typeof(BeatmapObjectManager))]\n    [HeckPatch(\"HandleObstacleFinishedMovement\")]\n    internal static class BeatmapObjectManagerHandleObstacleFinishedMovement\n    {\n        private static void Prefix(ObstacleController obstacleController)\n        {\n            NoodleObjectData? noodleData = TryGetObjectData<NoodleObjectData>(obstacleController.obstacleData);\n            if (noodleData?.Track != null)\n            {\n                foreach (Track track in noodleData.Track)\n                {\n                    track.RemoveGameObject(obstacleController.gameObject);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapObjectSpawnController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(BeatmapObjectSpawnController))]\n    [HeckPatch(\"Start\")]\n    internal static class BeatmapObjectSpawnControllerStart\n    {\n        internal static BasicBeatmapObjectManager? BeatmapObjectManager { get; private set; }\n\n        private static void Postfix(IBeatmapObjectSpawner ____beatmapObjectSpawner, BeatmapObjectSpawnMovementData ____beatmapObjectSpawnMovementData)\n        {\n            if (____beatmapObjectSpawner is BasicBeatmapObjectManager basicBeatmapObjectManager)\n            {\n                BeatmapObjectManager = basicBeatmapObjectManager;\n                SpawnDataHelper.InitBeatmapObjectSpawnController(____beatmapObjectSpawnMovementData);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapObjectSpawnMovementData.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n    using UnityEngine;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    [HeckPatch(typeof(BeatmapObjectSpawnMovementData))]\n    [HeckPatch(\"GetObstacleSpawnData\")]\n    internal static class BeatmapObjectSpawnMovementDataGetObstacleSpawnData\n    {\n        private static void Postfix(Vector3 ____centerPos, ObstacleData obstacleData, ref BeatmapObjectSpawnMovementData.ObstacleSpawnData __result)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n            if (noodleData == null)\n            {\n                return;\n            }\n\n            float? njs = noodleData.NJS;\n            float? spawnoffset = noodleData.SpawnOffset;\n\n            float? startX = noodleData.StartX;\n            float? startY = noodleData.StartY;\n\n            float? height = noodleData.Height;\n\n            Vector3? finalNoteOffset = null;\n\n            Vector3 moveStartPos = __result.moveStartPos;\n            Vector3 moveEndPos = __result.moveEndPos;\n            Vector3 jumpEndPos = __result.jumpEndPos;\n            float obstacleHeight = __result.obstacleHeight;\n            GetNoteJumpValues(njs, spawnoffset, out float jumpDuration, out float _, out Vector3 localMoveStartPos, out Vector3 localMoveEndPos, out Vector3 localJumpEndPos);\n\n            // Actual wall stuff\n            if (startX.HasValue || startY.HasValue || njs.HasValue || spawnoffset.HasValue)\n            {\n                // Ripped from base game\n                Vector3 noteOffset = GetNoteOffset(obstacleData, startX, null);\n                noteOffset.y = startY.HasValue ? VerticalObstaclePosY + (startY.GetValueOrDefault(0) * NoteLinesDistance) : ((obstacleData.obstacleType == ObstacleType.Top)\n                    ? (TopObstaclePosY + JumpOffsetY) : VerticalObstaclePosY);\n\n                finalNoteOffset = noteOffset;\n\n                moveStartPos = localMoveStartPos + noteOffset;\n                moveEndPos = localMoveEndPos + noteOffset;\n                jumpEndPos = localJumpEndPos + noteOffset;\n            }\n\n            if (height.HasValue)\n            {\n                obstacleHeight = height.Value * NoteLinesDistance;\n            }\n\n            __result = new BeatmapObjectSpawnMovementData.ObstacleSpawnData(moveStartPos, moveEndPos, jumpEndPos, obstacleHeight, __result.moveDuration, jumpDuration, __result.noteLinesDistance);\n\n            if (!finalNoteOffset.HasValue)\n            {\n                Vector3 noteOffset = GetNoteOffset(obstacleData, startX, null);\n                noteOffset.y = (obstacleData.obstacleType == ObstacleType.Top) ? (TopObstaclePosY + JumpOffsetY) : VerticalObstaclePosY;\n                finalNoteOffset = noteOffset;\n            }\n\n            noodleData.NoteOffset = ____centerPos + finalNoteOffset.Value;\n            float? width = noodleData.Width;\n            noodleData.XOffset = ((width.GetValueOrDefault(obstacleData.lineIndex) / 2f) - 0.5f) * NoteLinesDistance;\n        }\n    }\n\n    [HeckPatch(typeof(BeatmapObjectSpawnMovementData))]\n    [HeckPatch(\"GetJumpingNoteSpawnData\")]\n    internal static class BeatmapObjectSpawnMovementDataGetJumpingNoteSpawnData\n    {\n        private static void Postfix(BeatmapObjectSpawnMovementData __instance, Vector3 ____centerPos, float ____jumpDuration, NoteData noteData, ref BeatmapObjectSpawnMovementData.NoteSpawnData __result)\n        {\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteData);\n            if (noodleData == null)\n            {\n                return;\n            }\n\n            float? flipLineIndex = noodleData.FlipLineIndexInternal;\n            float? njs = noodleData.NJS;\n            float? spawnoffset = noodleData.SpawnOffset;\n            float? startlinelayer = noodleData.StartNoteLineLayerInternal;\n\n            bool gravityOverride = noodleData.DisableGravity;\n\n            float? startRow = noodleData.StartX;\n            float? startHeight = noodleData.StartY;\n\n            float jumpDuration = ____jumpDuration;\n\n            ////Vector3 moveStartPos = __result.moveStartPos;\n            ////Vector3 moveEndPos = __result.moveEndPos;\n            ////Vector3 jumpEndPos = __result.jumpEndPos;\n            float jumpGravity = __result.jumpGravity;\n\n            Vector3 noteOffset = GetNoteOffset(noteData, startRow, startlinelayer ?? (float)noteData.beforeJumpNoteLineLayer);\n\n            if (startRow.HasValue || startHeight.HasValue || flipLineIndex.HasValue || njs.HasValue || spawnoffset.HasValue || startlinelayer.HasValue || gravityOverride)\n            {\n                GetNoteJumpValues(njs, spawnoffset, out jumpDuration, out float localJumpDistance, out Vector3 localMoveStartPos, out Vector3 localMoveEndPos, out Vector3 localJumpEndPos);\n\n                float localNoteJumpMovementSpeed = njs ?? NoteJumpMovementSpeed;\n\n                float startLayerLineYPos = LineYPosForLineLayer(noteData, startlinelayer ?? (float)noteData.beforeJumpNoteLineLayer);\n                float lineYPos = LineYPosForLineLayer(noteData, startHeight);\n\n                // Magic numbers below found with linear regression y=mx+b using existing HighestJumpPosYForLineLayer values\n                float highestJump = startHeight.HasValue ? (0.875f * lineYPos) + 0.639583f + JumpOffsetY :\n                    __instance.HighestJumpPosYForLineLayer(noteData.noteLineLayer);\n\n                float GetJumpGravity(float lineYPos) => 2f * (highestJump - (gravityOverride ? lineYPos : startLayerLineYPos)) /\n                    Mathf.Pow(localJumpDistance / localNoteJumpMovementSpeed * 0.5f, 2f);\n\n                jumpGravity = GetJumpGravity(startLayerLineYPos);\n\n                float newJumpGravity = gravityOverride ? GetJumpGravity(lineYPos) : jumpGravity;\n\n                Vector3 jumpEndPos = localJumpEndPos + noteOffset;\n\n                // IsBasicNote() check is skipped so bombs can flip too\n                Vector3 noteOffset2 = GetNoteOffset(noteData, flipLineIndex ?? startRow, gravityOverride ? startHeight : startlinelayer ?? (float)noteData.beforeJumpNoteLineLayer);\n                Vector3 moveStartPos = localMoveStartPos + noteOffset2;\n                Vector3 moveEndPos = localMoveEndPos + noteOffset2;\n\n                __result = new BeatmapObjectSpawnMovementData.NoteSpawnData(moveStartPos, moveEndPos, jumpEndPos, newJumpGravity, __result.moveDuration, jumpDuration);\n            }\n\n            // DEFINITE POSITION IS WEIRD, OK?\n            // fuck\n            float num = jumpDuration * 0.5f;\n            float startVerticalVelocity = jumpGravity * num;\n            float yOffset = (startVerticalVelocity * num) - (jumpGravity * num * num * 0.5f);\n            noodleData.NoteOffset = ____centerPos + noteOffset + new Vector3(0, yOffset, 0);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/BeatmapObjectsInTimeRowProcessor.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using static NoodleExtensions.Plugin;\n\n    [HarmonyPatch(typeof(BeatmapObjectsInTimeRowProcessor))]\n    [HarmonyPatch(\"ProcessAllNotesInTimeRow\")]\n    internal static class NotesInTimeRowProcessorProcessAllNotesInTimeRow\n    {\n        private static void Prefix(List<NoteData> notesInTimeRow)\n        {\n            if (notesInTimeRow.FirstOrDefault() is CustomNoteData)\n            {\n                List<CustomNoteData> notesToSetFlip = new List<CustomNoteData>();\n\n                Dictionary<float, List<CustomNoteData>> notesInColumns = new Dictionary<float, List<CustomNoteData>>();\n                for (int i = 0; i < notesInTimeRow.Count; i++)\n                {\n                    CustomNoteData noteData = (CustomNoteData)notesInTimeRow[i];\n                    Dictionary<string, object?> dynData = noteData.customData;\n\n                    IEnumerable<float?>? position = dynData.GetNullableFloats(POSITION);\n                    float lineIndex = position?.ElementAtOrDefault(0) ?? (noteData.lineIndex - 2);\n                    float lineLayer = position?.ElementAtOrDefault(1) ?? (float)noteData.noteLineLayer;\n\n                    if (!notesInColumns.TryGetValue(lineIndex, out List<CustomNoteData> list))\n                    {\n                        list = new List<CustomNoteData>(1);\n                        notesInColumns.Add(lineIndex, list);\n                    }\n\n                    bool flag = false;\n                    for (int k = 0; k < list.Count; k++)\n                    {\n                        IEnumerable<float?>? listPosition = list[k].customData.GetNullableFloats(POSITION);\n                        float listLineLayer = listPosition?.ElementAtOrDefault(1) ?? (float)list[k].noteLineLayer;\n                        if (listLineLayer > lineLayer)\n                        {\n                            list.Insert(k, noteData);\n                            flag = true;\n                            break;\n                        }\n                    }\n\n                    if (!flag)\n                    {\n                        list.Add(noteData);\n                    }\n\n                    // Flippy stuff\n                    IEnumerable<float?>? flip = dynData.GetNullableFloats(FLIP);\n                    float? flipX = flip?.ElementAtOrDefault(0);\n                    float? flipY = flip?.ElementAtOrDefault(1);\n                    if (flipX.HasValue || flipY.HasValue)\n                    {\n                        if (flipX.HasValue)\n                        {\n                            dynData[\"flipLineIndex\"] = flipX.Value;\n                        }\n\n                        if (flipY.HasValue)\n                        {\n                            dynData[\"flipYSide\"] = flipY.Value;\n                        }\n                    }\n                    else if (!dynData.ContainsKey(\"flipYSide\"))\n                    {\n                        notesToSetFlip.Add(noteData);\n                    }\n                }\n\n                foreach (KeyValuePair<float, List<CustomNoteData>> keyValue in notesInColumns)\n                {\n                    List<CustomNoteData> list2 = keyValue.Value;\n                    for (int m = 0; m < list2.Count; m++)\n                    {\n                        list2[m].customData[\"startNoteLineLayer\"] = m;\n                    }\n                }\n\n                // Process flip data\n                notesToSetFlip.ForEach(c => c.customData[\"flipYSide\"] = 0);\n            }\n        }\n    }\n\n    [HarmonyPatch(typeof(BeatmapObjectsInTimeRowProcessor))]\n    [HarmonyPatch(\"ProcessColorNotesInTimeRow\")]\n    internal static class NotesInTimeRowProcessorProcessColorNotesInTimeRow\n    {\n        private static void Prefix(List<NoteData> colorNotesData)\n        {\n            if (colorNotesData.FirstOrDefault() is CustomNoteData)\n            {\n                int customNoteCount = colorNotesData.Count;\n                if (customNoteCount == 2)\n                {\n                    float[] lineIndexes = new float[2];\n                    float[] lineLayers = new float[2];\n                    for (int i = 0; i < customNoteCount; i++)\n                    {\n                        Dictionary<string, object?> dynData = ((CustomNoteData)colorNotesData[i]).customData;\n                        IEnumerable<float?>? position = dynData.GetNullableFloats(POSITION);\n                        lineIndexes[i] = position?.ElementAtOrDefault(0) ?? (colorNotesData[i].lineIndex - 2);\n                        lineLayers[i] = position?.ElementAtOrDefault(1) ?? (float)colorNotesData[i].noteLineLayer;\n                    }\n\n                    if (colorNotesData[0].colorType != colorNotesData[1].colorType && ((colorNotesData[0].colorType == ColorType.ColorA && lineIndexes[0] > lineIndexes[1]) ||\n                        (colorNotesData[0].colorType == ColorType.ColorB && lineIndexes[0] < lineIndexes[1])))\n                    {\n                        for (int i = 0; i < customNoteCount; i++)\n                        {\n                            // apparently I can use customData to store my own variables in noteData, neat\n                            // ^ comment from a very young and naive aero\n                            Dictionary<string, object?> dynData = ((CustomNoteData)colorNotesData[i]).customData;\n                            dynData[\"flipLineIndex\"] = lineIndexes[1 - i];\n\n                            float flipYSide = (lineIndexes[i] > lineIndexes[1 - i]) ? 1 : -1;\n                            if ((lineIndexes[i] > lineIndexes[1 - i] && lineLayers[i] < lineLayers[1 - i]) || (lineIndexes[i] < lineIndexes[1 - i] &&\n                                lineLayers[i] > lineLayers[1 - i]))\n                            {\n                                flipYSide *= -1f;\n                            }\n\n                            dynData[\"flipYSide\"] = flipYSide;\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/Cutout/BaseNoteVisual.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using Heck;\n    using IPA.Utilities;\n    using UnityEngine;\n\n    [HeckPatch(typeof(BaseNoteVisuals))]\n    [HeckPatch(\"Awake\")]\n    internal static class BaseNoteVisualsAwake\n    {\n        private static readonly Dictionary<Type, MethodInfo> _setArrowTransparencyMethods = new Dictionary<Type, MethodInfo>();\n        private static readonly FieldAccessor<BaseNoteVisuals, CutoutAnimateEffect>.Accessor _noteCutoutAnimateEffectAccessor = FieldAccessor<BaseNoteVisuals, CutoutAnimateEffect>.GetAccessor(\"_cutoutAnimateEffect\");\n        private static readonly FieldAccessor<CutoutAnimateEffect, CutoutEffect[]>.Accessor _cutoutEffectAccessor = FieldAccessor<CutoutAnimateEffect, CutoutEffect[]>.GetAccessor(\"_cuttoutEffects\");\n\n        private static void Postfix(BaseNoteVisuals __instance, NoteControllerBase ____noteController)\n        {\n            GameObject gameObject = __instance.gameObject;\n\n            BaseNoteVisuals baseNoteVisuals = gameObject.GetComponent<BaseNoteVisuals>();\n            CutoutAnimateEffect cutoutAnimateEffect = _noteCutoutAnimateEffectAccessor(ref baseNoteVisuals);\n            CutoutEffect[] cutoutEffects = _cutoutEffectAccessor(ref cutoutAnimateEffect);\n            CutoutEffect cutoutEffect = cutoutEffects.First(n => n.name != \"NoteArrow\"); // 1.11 NoteArrow has been added to the CutoutAnimateEffect and we don't want that\n            CutoutManager.NoteCutoutEffects.Add(____noteController, new CutoutEffectWrapper(cutoutEffect));\n\n            if (____noteController is ICubeNoteTypeProvider)\n            {\n                Type constructed = typeof(DisappearingArrowControllerBase<>).MakeGenericType(____noteController.GetType());\n                MonoBehaviour disappearingArrowController = (MonoBehaviour)gameObject.GetComponent(constructed);\n                MethodInfo method = GetSetArrowTransparency(disappearingArrowController.GetType());\n                DisappearingArrowWrapper wrapper = new DisappearingArrowWrapper(disappearingArrowController, method);\n                wrapper.SetCutout(1); // i have no fucking idea how this fixes the weird ghost arrow bug\n                CutoutManager.NoteDisappearingArrowWrappers.Add(____noteController, wrapper);\n            }\n        }\n\n        private static MethodInfo GetSetArrowTransparency(Type type)\n        {\n            if (type == null)\n            {\n                throw new ArgumentNullException(nameof(type));\n            }\n\n            if (_setArrowTransparencyMethods.TryGetValue(type, out MethodInfo value))\n            {\n                return value;\n            }\n\n            Type baseType = type.BaseType;\n            ////NoodleLogger.IPAlogger.Debug($\"Base type is {baseType.Name}<{string.Join(\", \", baseType.GenericTypeArguments.Select(t => t.Name))}>\");\n            MethodInfo method = baseType.GetMethod(\"SetArrowTransparency\", BindingFlags.NonPublic | BindingFlags.Instance);\n            _setArrowTransparencyMethods[type] = method ?? throw new InvalidOperationException($\"Type [{type.FullName}] does not contain method [SetArrowTransparency]\");\n            return method;\n        }\n    }\n\n    [HeckPatch(typeof(BaseNoteVisuals))]\n    [HeckPatch(\"OnDestroy\")]\n    internal static class DisappearingArrowControllerBaseGameNoteControllerOnDestroy\n    {\n        private static void Postfix(NoteControllerBase ____noteController)\n        {\n            CutoutManager.NoteCutoutEffects.Remove(____noteController);\n            CutoutManager.NoteDisappearingArrowWrappers.Remove(____noteController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/Cutout/ObstacleDissolve.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(ObstacleDissolve))]\n    [HeckPatch(\"Awake\")]\n    internal static class ObstacleDissolveAwake\n    {\n        private static void Postfix(ObstacleController ____obstacleController, CutoutAnimateEffect ____cutoutAnimateEffect)\n        {\n            CutoutManager.ObstacleCutoutEffects.Add(____obstacleController, new CutoutAnimateEffectWrapper(____cutoutAnimateEffect));\n        }\n    }\n\n    [HeckPatch(typeof(ObstacleDissolve))]\n    [HeckPatch(\"OnDestroy\")]\n    internal static class ObstacleDissolveOnDestroy\n    {\n        private static void Postfix(ObstacleController ____obstacleController)\n        {\n            CutoutManager.ObstacleCutoutEffects.Remove(____obstacleController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BadNoteCutEffectSpawner.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(BadNoteCutEffectSpawner))]\n    [HeckPatch(\"HandleNoteWasCut\")]\n    internal static class BadNoteCutEffectSpawnerHandleNoteWasCut\n    {\n        private static bool Prefix(NoteController noteController)\n        {\n            return FakeNoteHelper.GetFakeNote(noteController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BeatmapData.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n\n    [HarmonyPatch(typeof(BeatmapData))]\n    [HarmonyPatch(MethodType.Constructor)]\n    [HarmonyPatch(new Type[] { typeof(int) })]\n    internal static class BeatmapDataCtor\n    {\n        private static void Postfix()\n        {\n            BeatmapDataAddBeatmapObjectData.NeedsCheck = true;\n        }\n    }\n\n    [HarmonyPatch(typeof(BeatmapData))]\n    [HarmonyPatch(\"AddBeatmapObjectData\")]\n    internal static class BeatmapDataAddBeatmapObjectData\n    {\n        private static readonly List<object> _countGetters = new List<object>\n        {\n            AccessTools.PropertyGetter(typeof(BeatmapData), nameof(BeatmapData.obstaclesCount)),\n            AccessTools.PropertyGetter(typeof(BeatmapData), nameof(BeatmapData.cuttableNotesCount)),\n            AccessTools.PropertyGetter(typeof(BeatmapData), nameof(BeatmapData.bombsCount)),\n        };\n\n        private static readonly MethodInfo _fakeObjectCheck = AccessTools.Method(typeof(BeatmapDataAddBeatmapObjectData), nameof(FakeObjectCheck));\n        private static bool _noodleRequirement;\n\n        internal static bool NeedsCheck { get; set; }\n\n        private static void Prefix(BeatmapData __instance)\n        {\n            if (NeedsCheck)\n            {\n                NeedsCheck = false;\n                if (__instance is CustomBeatmapData customBeatmapData)\n                {\n                    IEnumerable<string>? requirements = customBeatmapData.beatmapCustomData.Get<List<object>>(\"_requirements\")?.Cast<string>();\n                    _noodleRequirement = requirements?.Contains(Plugin.CAPABILITY) ?? false;\n                }\n                else\n                {\n                    _noodleRequirement = false;\n                }\n            }\n        }\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Call) { operands = _countGetters })\n                .Repeat(n => n\n                    .InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))\n                    .Advance(1)\n                    .InsertAndAdvance(\n                        new CodeInstruction(OpCodes.Ldarg_1),\n                        new CodeInstruction(OpCodes.Call, _fakeObjectCheck))\n                    .RemoveInstructions(5))\n                .InstructionEnumeration();\n        }\n\n        private static int FakeObjectCheck(int objectCount, BeatmapObjectData beatmapObjectData)\n        {\n            if (_noodleRequirement)\n            {\n                bool? fake = beatmapObjectData.GetDataForObject().Get<bool?>(Plugin.FAKENOTE);\n                if (fake.HasValue && fake.Value)\n                {\n                    return objectCount;\n                }\n            }\n\n            return objectCount + 1;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BeatmapObjectExecutionRatingsRecorder.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(BeatmapObjectExecutionRatingsRecorder))]\n    [HeckPatch(\"Update\")]\n    internal static class BeatmapObjectExecutionRatingsRecorderUpdate\n    {\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return FakeNoteHelper.ObstaclesTranspiler(instructions);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BeatmapObjectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Reflection;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(BeatmapObjectManager))]\n    [HeckPatch(\"HandleNoteControllerNoteWasCut\")]\n    internal static class BeatmapObjectManagerHandleNoteWasCut\n    {\n        private static readonly MethodInfo _despawnMethod = AccessTools.Method(typeof(BeatmapObjectManager), \"Despawn\", new Type[] { typeof(NoteController) });\n\n        [HarmonyPriority(Priority.High)]\n        private static bool Prefix(BeatmapObjectManager __instance, NoteController noteController, in NoteCutInfo noteCutInfo)\n        {\n            if (!FakeNoteHelper.GetFakeNote(noteController))\n            {\n                NoteCutCoreEffectsSpawnerStart.NoteCutCoreEffectsSpawner!.HandleNoteWasCut(noteController, noteCutInfo);\n                _despawnMethod.Invoke(__instance, new object[] { noteController });\n\n                return false;\n            }\n\n            return true;\n        }\n    }\n\n    [HeckPatch(typeof(BeatmapObjectManager))]\n    [HeckPatch(\"HandleNoteControllerNoteWasMissed\")]\n    internal static class BeatmapObjectManagerHandleNoteWasMissed\n    {\n        [HarmonyPriority(Priority.High)]\n        private static bool Prefix(NoteController noteController)\n        {\n            return FakeNoteHelper.GetFakeNote(noteController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BombCutSoundEffectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(BombCutSoundEffectManager))]\n    [HeckPatch(\"HandleNoteWasCut\")]\n    internal static class BombCutSoundEffectManagerHandleNoteWasCut\n    {\n        // Do not create a BombCutSoundEffect for fake notes\n        private static bool Prefix(NoteController noteController)\n        {\n            return FakeNoteHelper.GetFakeNote(noteController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/BombNoteController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(BombNoteController))]\n    [HeckPatch(\"Init\")]\n    internal static class BombNoteControllerInit\n    {\n        [HarmonyPriority(Priority.High)]\n        private static void Postfix(NoteData noteData, CuttableBySaber ____cuttableBySaber)\n        {\n            if (!FakeNoteHelper.GetCuttable(noteData))\n            {\n                ____cuttableBySaber.canBeCut = false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/FakeNoteHelper.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using UnityEngine;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    internal static class FakeNoteHelper\n    {\n        internal static readonly MethodInfo _boundsNullCheck = AccessTools.Method(typeof(FakeNoteHelper), nameof(BoundsNullCheck));\n\n        private static readonly MethodInfo _intersectingObstaclesGetter = AccessTools.PropertyGetter(typeof(PlayerHeadAndObstacleInteraction), nameof(PlayerHeadAndObstacleInteraction.intersectingObstacles));\n        private static readonly MethodInfo _obstacleFakeCheck = AccessTools.Method(typeof(FakeNoteHelper), nameof(ObstacleFakeCheck));\n\n        internal static bool GetFakeNote(NoteController noteController)\n        {\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteController.noteData);\n            if (noodleData != null)\n            {\n                bool? fake = noodleData.Fake;\n                if (fake.HasValue && fake.Value)\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        internal static bool GetCuttable(NoteData noteData)\n        {\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteData);\n            if (noodleData != null)\n            {\n                bool? cuttable = noodleData.Cuttable;\n                if (cuttable.HasValue && !cuttable.Value)\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        internal static IEnumerable<CodeInstruction> ObstaclesTranspiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Callvirt, _intersectingObstaclesGetter))\n                .Advance(1)\n                .Insert(new CodeInstruction(OpCodes.Call, _obstacleFakeCheck))\n                .InstructionEnumeration();\n        }\n\n        private static bool BoundsNullCheck(ObstacleController obstacleController)\n        {\n            return obstacleController.bounds.size == Vector3.zero;\n        }\n\n        private static List<ObstacleController> ObstacleFakeCheck(List<ObstacleController> intersectingObstacles)\n        {\n            return intersectingObstacles.Where(n =>\n            {\n                NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(n.obstacleData);\n                if (noodleData != null)\n                {\n                    bool? fake = noodleData.Fake;\n                    if (fake.HasValue && fake.Value)\n                    {\n                        return false;\n                    }\n                }\n\n                return true;\n            }).ToList();\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/GameEnergyCounter.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(GameEnergyCounter))]\n    [HeckPatch(\"LateUpdate\")]\n    internal static class GameEnergyCounterUpdate\n    {\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return FakeNoteHelper.ObstaclesTranspiler(instructions);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/GameNoteController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(GameNoteController))]\n    [HeckPatch(\"NoteDidStartJump\")]\n    internal static class GameNoteControllerNoteDidStartJump\n    {\n        [HarmonyPriority(Priority.High)]\n        private static bool Prefix(GameNoteController __instance)\n        {\n            return FakeNoteHelper.GetCuttable(__instance.noteData);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/NoteCutCoreEffectSpawner.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(NoteCutCoreEffectsSpawner))]\n    [HeckPatch(\"Start\")]\n    internal static class NoteCutCoreEffectsSpawnerStart\n    {\n        internal static NoteCutCoreEffectsSpawner? NoteCutCoreEffectsSpawner { get; private set; }\n\n        private static void Postfix(NoteCutCoreEffectsSpawner __instance)\n        {\n            NoteCutCoreEffectsSpawner = __instance;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/NoteCutScoreSpawner.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(NoteCutScoreSpawner))]\n    [HeckPatch(\"HandleNoteWasCut\")]\n    internal static class NoteCutScoreSpawnerHandleNoteWasCut\n    {\n        private static bool Prefix(NoteController noteController)\n        {\n            return FakeNoteHelper.GetFakeNote(noteController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/NoteCutSoundEffectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using Heck;\n    using UnityEngine;\n\n    [HeckPatch(typeof(NoteCutSoundEffectManager))]\n    [HeckPatch(\"Start\")]\n    internal static class NoteCutSoundEffectManagerStart\n    {\n        private static void Postfix(NoteCutSoundEffectManager __instance)\n        {\n            NoodleCutSoundEffectManager noodleManager = __instance.gameObject.AddComponent<NoodleCutSoundEffectManager>();\n            noodleManager.Init(__instance);\n            NoteCutSoundEffectManagerHandleNoteWasSpawned.NoodleManager = noodleManager;\n        }\n    }\n\n    [HeckPatch(typeof(NoteCutSoundEffectManager))]\n    [HeckPatch(\"HandleNoteWasSpawned\")]\n    internal static class NoteCutSoundEffectManagerHandleNoteWasSpawned\n    {\n        internal static NoodleCutSoundEffectManager? NoodleManager { get; set; }\n\n        // Do not create a NoteCutSoundEffect for fake notes\n        private static bool Prefix(NoteController noteController)\n        {\n            if (FakeNoteHelper.GetFakeNote(noteController))\n            {\n                return NoodleManager!.ProcessHitSound(noteController);\n            }\n\n            return false;\n        }\n    }\n\n    // Weird cut sound shenanigans to prevent unity from crashing.\n    internal class NoodleCutSoundEffectManager : MonoBehaviour\n    {\n        private readonly List<NoteController> _hitsoundQueue = new List<NoteController>();\n\n        private NoteCutSoundEffectManager? _noteCutSoundEffectManager;\n        private int _lastFrame = -1;\n        private int _cutCount = -1;\n\n        internal void Init(NoteCutSoundEffectManager noteCutSoundEffectManager)\n        {\n            _noteCutSoundEffectManager = noteCutSoundEffectManager;\n        }\n\n        internal bool ProcessHitSound(NoteController noteController)\n        {\n            if (Time.frameCount == _lastFrame)\n            {\n                _cutCount++;\n            }\n            else\n            {\n                _lastFrame = Time.frameCount;\n                _cutCount = 1;\n            }\n\n            // We do not allow more than 30 NoteCutSoundEffects to be created in a single frame to prevent unity from dying\n            if (_cutCount < 30)\n            {\n                return true;\n            }\n            else\n            {\n                _hitsoundQueue.Add(noteController);\n                return false;\n            }\n        }\n\n        private void Update()\n        {\n            if (_hitsoundQueue.Count > 0 && Time.frameCount != _lastFrame)\n            {\n                List<NoteController> noteControllers = new List<NoteController>(_hitsoundQueue);\n                _hitsoundQueue.Clear();\n                noteControllers.ForEach(_noteCutSoundEffectManager!.HandleNoteWasSpawned);\n                Plugin.Logger.Log($\"{noteControllers.Count} cut sounds moved to next frame!\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/ObstacleSaberSparkleEffectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(ObstacleSaberSparkleEffectManager))]\n    [HeckPatch(\"Update\")]\n    internal static class ObstacleSaberSparkleEffectManagerUpdate\n    {\n        private static readonly MethodInfo _currentGetter = AccessTools.PropertyGetter(typeof(List<ObstacleController>.Enumerator), nameof(List<ObstacleController>.Enumerator.Current));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            CodeMatcher codeMatcher = new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Br));\n\n            object label = codeMatcher.Operand;\n\n            return codeMatcher\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _currentGetter))\n                .Advance(2)\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldloc_1),\n                    new CodeInstruction(OpCodes.Call, FakeNoteHelper._boundsNullCheck),\n                    new CodeInstruction(OpCodes.Brtrue_S, label))\n                .InstructionEnumeration();\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/FakeNotes/PlayerHeadAndObstacleInteraction.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(PlayerHeadAndObstacleInteraction))]\n    [HeckPatch(\"GetObstaclesContainingPoint\")]\n    internal static class PlayerHeadAndObstacleInteractionGetObstaclesContainingPoint\n    {\n        private static readonly MethodInfo _currentGetter = AccessTools.PropertyGetter(typeof(List<ObstacleController>.Enumerator), nameof(List<ObstacleController>.Enumerator.Current));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            CodeMatcher codeMatcher = new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Br));\n\n            object label = codeMatcher.Operand;\n\n            return codeMatcher\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _currentGetter))\n                .Advance(2)\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldloc_1),\n                    new CodeInstruction(OpCodes.Call, FakeNoteHelper._boundsNullCheck),\n                    new CodeInstruction(OpCodes.Brtrue_S, label))\n                .InstructionEnumeration();\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/GameplayCoreInstaller.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n\n    [HeckPatch(typeof(GameplayCoreInstaller))]\n    [HeckPatch(\"InstallBindings\")]\n    internal static class GameplayCoreInstallerInstallBindings\n    {\n        private static readonly MethodInfo _createTransformedBeatmapData = AccessTools.Method(typeof(BeatmapDataTransformHelper), nameof(BeatmapDataTransformHelper.CreateTransformedBeatmapData));\n\n        private static readonly MethodInfo _cacheNoteJumpValues = AccessTools.Method(typeof(GameplayCoreInstallerInstallBindings), nameof(CacheNoteJumpValues));\n\n        internal static float CachedNoteJumpMovementSpeed { get; private set; }\n\n        internal static float CachedNoteJumpStartBeatOffset { get; private set; }\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _createTransformedBeatmapData))\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldloc_S, 9),\n                    new CodeInstruction(OpCodes.Ldloc_S, 10),\n                    new CodeInstruction(OpCodes.Call, _cacheNoteJumpValues))\n                .InstructionEnumeration();\n        }\n\n        private static void CacheNoteJumpValues(float defaultNoteJumpMovementSpeed, float defaultNoteJumpStartBeatOffset)\n        {\n            CachedNoteJumpMovementSpeed = defaultNoteJumpMovementSpeed;\n            CachedNoteJumpStartBeatOffset = defaultNoteJumpStartBeatOffset;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/LeftHanded/BeatmapDataMirrorTransform.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using static NoodleExtensions.Plugin;\n\n    [HarmonyPatch(typeof(BeatmapDataMirrorTransform))]\n    [HarmonyPatch(\"CreateTransformedData\")]\n    internal static class BeatDataMirrorTransformCreateTransformedData\n    {\n        private static void Postfix(IReadonlyBeatmapData __result)\n        {\n            foreach (BeatmapEventData beatmapEventData in __result.beatmapEventsData)\n            {\n                if (beatmapEventData.type.IsRotationEvent())\n                {\n                    if (beatmapEventData is CustomBeatmapEventData customData)\n                    {\n                        Dictionary<string, object?> dynData = customData.customData;\n                        float? rotation = dynData.Get<float?>(ROTATION);\n\n                        if (rotation.HasValue)\n                        {\n                            dynData[\"_rotation\"] = rotation * -1;\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/LeftHanded/NoteData.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using UnityEngine;\n    using static NoodleExtensions.Plugin;\n\n    [HarmonyPatch(typeof(NoteData))]\n    [HarmonyPatch(\"Mirror\")]\n    internal static class NoteDataMirror\n    {\n        private static void Postfix(NoteData __instance)\n        {\n            if (__instance is CustomNoteData customData)\n            {\n                Dictionary<string, object?> dynData = customData.customData;\n                IEnumerable<float?>? position = dynData.GetNullableFloats(POSITION);\n                float? flipLineIndex = dynData.Get<float?>(\"flipLineIndex\");\n                IEnumerable<float?>? flip = dynData.GetNullableFloats(FLIP);\n                List<float>? localrot = dynData.Get<List<object>>(LOCALROTATION)?.Select(n => Convert.ToSingle(n)).ToList();\n                object? rotation = dynData.Get<object>(ROTATION);\n\n                float? startRow = position?.ElementAtOrDefault(0);\n                float? flipX = flip?.ElementAtOrDefault(0);\n\n                if (startRow.HasValue)\n                {\n                    dynData[POSITION] = new List<object?>() { ((startRow.Value + 0.5f) * -1) - 0.5f, position.ElementAtOrDefault(1) };\n                }\n\n                if (flipLineIndex.HasValue)\n                {\n                    dynData[\"flipLineIndex\"] = ((flipLineIndex.Value + 0.5f) * -1) - 0.5f;\n                }\n\n                if (flipX.HasValue)\n                {\n                    dynData[FLIP] = new List<object?>() { ((flipX.Value + 0.5f) * -1) - 0.5f, flip.ElementAtOrDefault(1) };\n                }\n\n                if (localrot != null)\n                {\n                    List<float> rot = localrot.Select(n => Convert.ToSingle(n)).ToList();\n                    Quaternion modifiedVector = Quaternion.Euler(rot[0], rot[1], rot[2]);\n                    Vector3 vector = new Quaternion(modifiedVector.x, modifiedVector.y * -1, modifiedVector.z * -1, modifiedVector.w).eulerAngles;\n                    dynData[LOCALROTATION] = new List<object> { vector.x, vector.y, vector.z };\n                }\n\n                if (rotation != null)\n                {\n                    if (rotation is List<object> list)\n                    {\n                        List<float> rot = list.Select(n => Convert.ToSingle(n)).ToList();\n                        Quaternion modifiedVector = Quaternion.Euler(rot[0], rot[1], rot[2]);\n                        Vector3 vector = new Quaternion(modifiedVector.x, modifiedVector.y * -1, modifiedVector.z * -1, modifiedVector.w).eulerAngles;\n                        dynData[ROTATION] = new List<object> { vector.x, vector.y, vector.z };\n                    }\n                    else\n                    {\n                        dynData[ROTATION] = Convert.ToSingle(rotation) * -1;\n                    }\n                }\n\n                float? cutDirection = dynData.Get<float?>(CUTDIRECTION);\n\n                if (cutDirection.HasValue)\n                {\n                    dynData[CUTDIRECTION] = 360 - cutDirection.Value;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/LeftHanded/ObstacleData.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using UnityEngine;\n    using static NoodleExtensions.Plugin;\n\n    [HarmonyPatch(typeof(ObstacleData))]\n    [HarmonyPatch(\"Mirror\")]\n    internal static class ObstacleDataMirror\n    {\n        private static void Prefix(ObstacleData __instance) // prefix because we need to know the lineIndex before it gets mirrored\n        {\n            if (__instance is CustomObstacleData customData)\n            {\n                Dictionary<string, object?> dynData = customData.customData;\n                IEnumerable<float?>? position = dynData.GetNullableFloats(POSITION);\n                IEnumerable<float?>? scale = dynData.GetNullableFloats(SCALE);\n                List<float>? localrot = dynData.Get<List<object>>(LOCALROTATION)?.Select(n => Convert.ToSingle(n)).ToList();\n                object? rotation = dynData.Get<object>(ROTATION);\n\n                float? startX = position?.ElementAtOrDefault(0);\n                float? scaleX = scale?.ElementAtOrDefault(0);\n\n                float width = scaleX.GetValueOrDefault(__instance.width);\n                if (startX.HasValue)\n                {\n                    dynData[POSITION] = new List<object?>() { (startX.Value + width) * -1, position.ElementAtOrDefault(1) };\n                }\n                else if (scaleX.HasValue)\n                {\n                    float lineIndex = __instance.lineIndex - 2;\n                    dynData[POSITION] = new List<object?>() { (lineIndex + width) * -1, position?.ElementAtOrDefault(1) ?? 0 };\n                }\n\n                if (localrot != null)\n                {\n                    List<float> rot = localrot.Select(n => Convert.ToSingle(n)).ToList();\n                    Quaternion modifiedVector = Quaternion.Euler(rot[0], rot[1], rot[2]);\n                    Vector3 vector = new Quaternion(modifiedVector.x, modifiedVector.y * -1, modifiedVector.z * -1, modifiedVector.w).eulerAngles;\n                    dynData[LOCALROTATION] = new List<object> { vector.x, vector.y, vector.z };\n                }\n\n                if (rotation != null)\n                {\n                    if (rotation is List<object> list)\n                    {\n                        List<float> rot = list.Select(n => Convert.ToSingle(n)).ToList();\n                        Quaternion modifiedVector = Quaternion.Euler(rot[0], rot[1], rot[2]);\n                        Vector3 vector = new Quaternion(modifiedVector.x, modifiedVector.y * -1, modifiedVector.z * -1, modifiedVector.w).eulerAngles;\n                        dynData[ROTATION] = new List<object> { vector.x, vector.y, vector.z };\n                    }\n                    else\n                    {\n                        dynData[ROTATION] = Convert.ToSingle(rotation) * -1;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/Mirror/MirroredNoteController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using Heck;\n    using Heck.Animation;\n    using UnityEngine;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    internal static class MirroredNoteControllerHelper\n    {\n        internal static void AddToTrack(NoteData noteData, GameObject gameObject)\n        {\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteData);\n            if (noodleData != null)\n            {\n                IEnumerable<Track>? tracks = noodleData.Track;\n                if (tracks != null)\n                {\n                    foreach (Track track in tracks)\n                    {\n                        // add to gameobjects\n                        track.AddGameObject(gameObject);\n                    }\n                }\n            }\n        }\n\n        internal static bool CheckSkip(Transform noteTransform, Transform followedNoteTransform)\n        {\n            if (followedNoteTransform.position.y < 0)\n            {\n                if (noteTransform.gameObject.activeInHierarchy)\n                {\n                    noteTransform.gameObject.SetActive(false);\n                }\n\n                return false;\n            }\n\n            if (!noteTransform.gameObject.activeInHierarchy)\n            {\n                noteTransform.gameObject.SetActive(true);\n            }\n\n            return true;\n        }\n\n        internal static void UpdateMirror(Transform objectTransform, Transform noteTransform, Transform followedObjectTransform, Transform followedNoteTransform, NoteControllerBase noteController, NoteControllerBase followedNote)\n        {\n            if (objectTransform.localScale != followedObjectTransform.localScale)\n            {\n                objectTransform.localScale = followedObjectTransform.localScale;\n            }\n\n            if (noteTransform.localScale != followedNoteTransform.localScale)\n            {\n                noteTransform.localScale = followedNoteTransform.localScale;\n            }\n\n            if (CutoutManager.NoteCutoutEffects.TryGetValue(noteController, out CutoutEffectWrapper cutoutEffect))\n            {\n                if (CutoutManager.NoteCutoutEffects.TryGetValue(followedNote, out CutoutEffectWrapper followedCutoutEffect))\n                {\n                    cutoutEffect.SetCutout(followedCutoutEffect.Cutout);\n                }\n            }\n\n            if (CutoutManager.NoteDisappearingArrowWrappers.TryGetValue(noteController, out DisappearingArrowWrapper disappearingArrow))\n            {\n                if (CutoutManager.NoteDisappearingArrowWrappers.TryGetValue(followedNote, out DisappearingArrowWrapper followedDisappearingArrow))\n                {\n                    disappearingArrow.SetCutout(followedDisappearingArrow.Cutout);\n                }\n            }\n        }\n    }\n\n    // Fuck generics\n    [HeckPatch(typeof(MirroredNoteController<INoteMirrorable>))]\n    [HeckPatch(\"Mirror\")]\n    internal static class MirroredNoteControllerINoteMirrorableMirror\n    {\n        private static void Postfix(MirroredNoteController<INoteMirrorable> __instance)\n        {\n            MirroredNoteControllerHelper.AddToTrack(__instance.noteData, __instance.gameObject);\n        }\n    }\n\n    [HeckPatch(typeof(MirroredNoteController<ICubeNoteMirrorable>))]\n    [HeckPatch(\"Mirror\")]\n    internal static class MirroredNoteControllerICubeNoteMirrorableMirror\n    {\n        private static void Postfix(MirroredNoteController<ICubeNoteMirrorable> __instance)\n        {\n            MirroredNoteControllerHelper.AddToTrack(__instance.noteData, __instance.gameObject);\n        }\n    }\n\n    [HeckPatch(typeof(MirroredNoteController<INoteMirrorable>))]\n    [HeckPatch(\"UpdatePositionAndRotation\")]\n    internal static class MirroredNoteControllerINoteMirrorableUpdatePositionAndRotation\n    {\n        private static bool Prefix(Transform ____noteTransform, Transform ____followedNoteTransform)\n        {\n            return MirroredNoteControllerHelper.CheckSkip(____noteTransform, ____followedNoteTransform);\n        }\n\n        private static void Postfix(MirroredNoteController<INoteMirrorable> __instance, INoteMirrorable ___followedNote, Transform ____objectTransform, Transform ____noteTransform, Transform ____followedObjectTransform, Transform ____followedNoteTransform)\n        {\n            MirroredNoteControllerHelper.UpdateMirror(____objectTransform, ____noteTransform, ____followedObjectTransform, ____followedNoteTransform, __instance, (NoteControllerBase)___followedNote);\n        }\n    }\n\n    [HeckPatch(typeof(MirroredNoteController<ICubeNoteMirrorable>))]\n    [HeckPatch(\"UpdatePositionAndRotation\")]\n    internal static class MirroredNoteControllerICubeNoteMirrorableUpdatePositionAndRotation\n    {\n        private static bool Prefix(Transform ____noteTransform, Transform ____followedNoteTransform)\n        {\n            return MirroredNoteControllerHelper.CheckSkip(____noteTransform, ____followedNoteTransform);\n        }\n\n        private static void Postfix(MirroredNoteController<ICubeNoteMirrorable> __instance, ICubeNoteMirrorable ___followedNote, Transform ____objectTransform, Transform ____noteTransform, Transform ____followedObjectTransform, Transform ____followedNoteTransform)\n        {\n            MirroredNoteControllerHelper.UpdateMirror(____objectTransform, ____noteTransform, ____followedObjectTransform, ____followedNoteTransform, __instance, (NoteControllerBase)___followedNote);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/Mirror/MirroredObstacleController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n    using UnityEngine;\n\n    [HeckPatch(typeof(MirroredObstacleController))]\n    [HeckPatch(\"UpdatePositionAndRotation\")]\n    internal static class MirroredObstacleControllerUpdatePositionAndRotation\n    {\n        // Must be overwritten to compensate for rotation\n        private static bool Prefix(MirroredObstacleController __instance, ObstacleController ____followedObstacle, Transform ____transform, Transform ____followedTransform)\n        {\n            // do not reflection walls above the mirror\n            if (____followedTransform.position.y < 0)\n            {\n                // idk how to hide it without disabling the update\n                ____transform.position = new Vector3(0, 100, 0);\n                return false;\n            }\n\n            Vector3 position = ____followedTransform.position;\n            Quaternion quaternion = ____followedTransform.rotation;\n            position.y = -position.y;\n            quaternion = quaternion.Reflect(Vector3.up);\n            ____transform.SetPositionAndRotation(position, quaternion);\n\n            if (____transform.localScale != ____followedTransform.localScale)\n            {\n                ____transform.localScale = ____followedTransform.localScale;\n            }\n\n            if (CutoutManager.ObstacleCutoutEffects.TryGetValue(__instance, out CutoutAnimateEffectWrapper cutoutAnimateEffect))\n            {\n                if (CutoutManager.ObstacleCutoutEffects.TryGetValue(____followedObstacle, out CutoutAnimateEffectWrapper followedCutoutAnimateEffect))\n                {\n                    cutoutAnimateEffect.SetCutout(followedCutoutAnimateEffect.Cutout);\n                }\n            }\n\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/MultiplayerConnectedPlayerInstaller.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using HarmonyLib;\n    using Heck;\n    using IPA.Utilities;\n    using static NoodleExtensions.Plugin;\n\n    [HeckPatch(typeof(MultiplayerConnectedPlayerInstaller))]\n    [HeckPatch(\"InstallBindings\")]\n    internal static class MultiplayerConnectedPlayerInstallerInstallBindings\n    {\n        private static readonly MethodInfo _createTransformedBeatmapData = AccessTools.Method(typeof(BeatmapDataTransformHelper), nameof(BeatmapDataTransformHelper.CreateTransformedBeatmapData));\n\n        private static readonly MethodInfo _excludeFakeNote = AccessTools.Method(typeof(MultiplayerConnectedPlayerInstallerInstallBindings), nameof(ExcludeFakeNoteAndAllWalls));\n\n        private static readonly FieldAccessor<BeatmapLineData, List<BeatmapObjectData>>.Accessor _beatmapObjectsDataAccessor = FieldAccessor<BeatmapLineData, List<BeatmapObjectData>>.GetAccessor(\"_beatmapObjectsData\");\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _createTransformedBeatmapData))\n                .Advance(1)\n                .Insert(new CodeInstruction(OpCodes.Call, _excludeFakeNote))\n                .InstructionEnumeration();\n        }\n\n        private static IReadonlyBeatmapData ExcludeFakeNoteAndAllWalls(IReadonlyBeatmapData result)\n        {\n            foreach (BeatmapLineData b in result.beatmapLinesData)\n            {\n                BeatmapLineData refBeatmapLineData = b;\n                _beatmapObjectsDataAccessor(ref refBeatmapLineData) = b.beatmapObjectsData.Where(n =>\n                {\n                    Dictionary<string, object?> dynData;\n\n                    switch (n)\n                    {\n                        case CustomNoteData customNoteData:\n                            dynData = customNoteData.customData;\n                            break;\n\n                        case CustomObstacleData customObstacleData:\n                            return false;\n\n                        default:\n                            return true;\n                    }\n\n                    bool? fake = dynData.Get<bool?>(FAKENOTE);\n                    if (fake.HasValue && fake.Value)\n                    {\n                        return false;\n                    }\n\n                    return true;\n                }).ToList();\n            }\n\n            if (result is CustomBeatmapData customBeatmapData)\n            {\n                string[] excludedTypes = new string[]\n                {\n                    ASSIGNPLAYERTOTRACK,\n                    ASSIGNTRACKPARENT,\n                };\n\n                customBeatmapData.customEventsData.RemoveAll(n => excludedTypes.Contains(n.type));\n            }\n\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/NoteController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using Heck.Animation;\n    using IPA.Utilities;\n    using UnityEngine;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    [HeckPatch(typeof(NoteController))]\n    [HeckPatch(\"Init\")]\n    internal static class NoteControllerInit\n    {\n        internal static readonly FieldAccessor<NoteMovement, NoteJump>.Accessor _noteJumpAccessor = FieldAccessor<NoteMovement, NoteJump>.GetAccessor(\"_jump\");\n        internal static readonly FieldAccessor<NoteMovement, NoteFloorMovement>.Accessor _noteFloorMovementAccessor = FieldAccessor<NoteMovement, NoteFloorMovement>.GetAccessor(\"_floorMovement\");\n\n        internal static readonly FieldAccessor<NoteFloorMovement, Quaternion>.Accessor _worldRotationFloorAccessor = FieldAccessor<NoteFloorMovement, Quaternion>.GetAccessor(\"_worldRotation\");\n        internal static readonly FieldAccessor<NoteFloorMovement, Quaternion>.Accessor _inverseWorldRotationFloorAccessor = FieldAccessor<NoteFloorMovement, Quaternion>.GetAccessor(\"_inverseWorldRotation\");\n\n        internal static readonly FieldAccessor<NoteJump, Quaternion>.Accessor _worldRotationJumpAccessor = FieldAccessor<NoteJump, Quaternion>.GetAccessor(\"_worldRotation\");\n        internal static readonly FieldAccessor<NoteJump, Quaternion>.Accessor _inverseWorldRotationJumpAccessor = FieldAccessor<NoteJump, Quaternion>.GetAccessor(\"_inverseWorldRotation\");\n        private static readonly FieldAccessor<NoteJump, Quaternion>.Accessor _endRotationAccessor = FieldAccessor<NoteJump, Quaternion>.GetAccessor(\"_endRotation\");\n        private static readonly FieldAccessor<NoteJump, Quaternion>.Accessor _middleRotationAccessor = FieldAccessor<NoteJump, Quaternion>.GetAccessor(\"_middleRotation\");\n        private static readonly FieldAccessor<NoteJump, Vector3[]>.Accessor _randomRotationsAccessor = FieldAccessor<NoteJump, Vector3[]>.GetAccessor(\"_randomRotations\");\n        private static readonly FieldAccessor<NoteJump, int>.Accessor _randomRotationIdxAccessor = FieldAccessor<NoteJump, int>.GetAccessor(\"_randomRotationIdx\");\n\n        private static readonly FieldInfo _noteDataField = AccessTools.Field(typeof(NoteController), \"_noteData\");\n        private static readonly MethodInfo _flipYSideGetter = AccessTools.PropertyGetter(typeof(NoteData), nameof(NoteData.flipYSide));\n\n        private static readonly MethodInfo _getFlipYSide = AccessTools.Method(typeof(NoteControllerInit), nameof(GetFlipYSide));\n\n        private static void Postfix(NoteController __instance, NoteData noteData, NoteMovement ____noteMovement, Vector3 moveStartPos, Vector3 moveEndPos, Vector3 jumpEndPos, float endRotation)\n        {\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteData);\n            if (noodleData == null)\n            {\n                return;\n            }\n\n            Quaternion? cutQuaternion = noodleData.CutQuaternion;\n\n            NoteJump noteJump = _noteJumpAccessor(ref ____noteMovement);\n            NoteFloorMovement floorMovement = _noteFloorMovementAccessor(ref ____noteMovement);\n\n            if (cutQuaternion.HasValue)\n            {\n                Quaternion quatVal = cutQuaternion.Value;\n                _endRotationAccessor(ref noteJump) = quatVal;\n                Vector3 vector = quatVal.eulerAngles;\n                vector += _randomRotationsAccessor(ref noteJump)[_randomRotationIdxAccessor(ref noteJump)] * 20;\n                Quaternion midrotation = Quaternion.Euler(vector);\n                _middleRotationAccessor(ref noteJump) = midrotation;\n            }\n\n            Quaternion? worldRotationQuaternion = noodleData.WorldRotationQuaternion;\n            Quaternion? localRotationQuaternion = noodleData.LocalRotationQuaternion;\n\n            Transform transform = __instance.transform;\n\n            Quaternion localRotation = Quaternion.identity;\n            if (worldRotationQuaternion.HasValue || localRotationQuaternion.HasValue)\n            {\n                if (localRotationQuaternion.HasValue)\n                {\n                    localRotation = localRotationQuaternion.Value;\n                }\n\n                if (worldRotationQuaternion.HasValue)\n                {\n                    Quaternion quatVal = worldRotationQuaternion.Value;\n                    Quaternion inverseWorldRotation = Quaternion.Inverse(quatVal);\n                    _worldRotationJumpAccessor(ref noteJump) = quatVal;\n                    _inverseWorldRotationJumpAccessor(ref noteJump) = inverseWorldRotation;\n                    _worldRotationFloorAccessor(ref floorMovement) = quatVal;\n                    _inverseWorldRotationFloorAccessor(ref floorMovement) = inverseWorldRotation;\n\n                    quatVal *= localRotation;\n\n                    transform.localRotation = quatVal;\n                }\n                else\n                {\n                    transform.localRotation *= localRotation;\n                }\n            }\n\n            if (transform.localScale != Vector3.one)\n            {\n                transform.localScale = Vector3.one; // This is a fix for animation due to notes being recycled\n            }\n\n            IEnumerable<Track>? tracks = noodleData.Track;\n            if (tracks != null)\n            {\n                foreach (Track track in tracks)\n                {\n                    // add to gameobjects\n                    track.AddGameObject(__instance.gameObject);\n                }\n            }\n\n            noodleData.EndRotation = endRotation;\n            noodleData.MoveStartPos = moveStartPos;\n            noodleData.MoveEndPos = moveEndPos;\n            noodleData.JumpEndPos = jumpEndPos;\n            noodleData.WorldRotation = __instance.worldRotation;\n            noodleData.LocalRotation = localRotation;\n        }\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Callvirt, _flipYSideGetter))\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _noteDataField))\n                .Advance(1)\n                .Insert(new CodeInstruction(OpCodes.Call, _getFlipYSide))\n                .InstructionEnumeration();\n        }\n\n        private static float GetFlipYSide(NoteData noteData, float @default)\n        {\n            float output = @default;\n\n            NoodleNoteData? noodleData = TryGetObjectData<NoodleNoteData>(noteData);\n            if (noodleData != null)\n            {\n                float? flipYSide = noodleData.FlipYSideInternal;\n                if (flipYSide.HasValue)\n                {\n                    output = flipYSide.Value;\n                }\n            }\n\n            return output;\n        }\n    }\n\n    [HeckPatch(typeof(NoteController))]\n    [HeckPatch(\"ManualUpdate\")]\n    internal static class NoteControllerUpdate\n    {\n        internal static readonly FieldAccessor<NoteFloorMovement, Vector3>.Accessor _floorEndPosAccessor = FieldAccessor<NoteFloorMovement, Vector3>.GetAccessor(\"_endPos\");\n        private static readonly FieldAccessor<NoteFloorMovement, Vector3>.Accessor _floorStartPosAccessor = FieldAccessor<NoteFloorMovement, Vector3>.GetAccessor(\"_startPos\");\n        private static readonly FieldAccessor<NoteJump, Vector3>.Accessor _jumpStartPosAccessor = FieldAccessor<NoteJump, Vector3>.GetAccessor(\"_startPos\");\n        private static readonly FieldAccessor<NoteJump, Vector3>.Accessor _jumpEndPosAccessor = FieldAccessor<NoteJump, Vector3>.GetAccessor(\"_endPos\");\n\n        private static readonly FieldAccessor<NoteJump, IAudioTimeSource>.Accessor _audioTimeSyncControllerAccessor = FieldAccessor<NoteJump, IAudioTimeSource>.GetAccessor(\"_audioTimeSyncController\");\n        private static readonly FieldAccessor<NoteJump, float>.Accessor _jumpDurationAccessor = FieldAccessor<NoteJump, float>.GetAccessor(\"_jumpDuration\");\n\n        private static readonly FieldAccessor<GameNoteController, BoxCuttableBySaber[]>.Accessor _gameNoteBigCuttableAccessor = FieldAccessor<GameNoteController, BoxCuttableBySaber[]>.GetAccessor(\"_bigCuttableBySaberList\");\n        private static readonly FieldAccessor<GameNoteController, BoxCuttableBySaber[]>.Accessor _gameNoteSmallCuttableAccessor = FieldAccessor<GameNoteController, BoxCuttableBySaber[]>.GetAccessor(\"_smallCuttableBySaberList\");\n        private static readonly FieldAccessor<BombNoteController, CuttableBySaber>.Accessor _bombNoteCuttableAccessor = FieldAccessor<BombNoteController, CuttableBySaber>.GetAccessor(\"_cuttableBySaber\");\n\n        internal static NoodleObjectData? NoodleData { get; private set; }\n\n        private static void Prefix(NoteController __instance, NoteData ____noteData, NoteMovement ____noteMovement)\n        {\n            NoodleData = TryGetObjectData<NoodleObjectData>(____noteData);\n            if (NoodleData == null)\n            {\n                return;\n            }\n\n            NoodleNoteData noodleData = (NoodleNoteData)NoodleData;\n\n            IEnumerable<Track>? tracks = noodleData.Track;\n            NoodleObjectData.AnimationObjectData? animationObject = noodleData.AnimationObject;\n            if (tracks != null || animationObject != null)\n            {\n                NoteJump noteJump = NoteControllerInit._noteJumpAccessor(ref ____noteMovement);\n                NoteFloorMovement floorMovement = NoteControllerInit._noteFloorMovementAccessor(ref ____noteMovement);\n\n                // idk i just copied base game time\n                float jumpDuration = _jumpDurationAccessor(ref noteJump);\n                float elapsedTime = _audioTimeSyncControllerAccessor(ref noteJump).songTime - (____noteData.time - (jumpDuration * 0.5f));\n                elapsedTime = NoteJumpManualUpdate.NoteJumpTimeAdjust(elapsedTime, jumpDuration);\n                float normalTime = elapsedTime / jumpDuration;\n\n                Animation.AnimationHelper.GetObjectOffset(animationObject, tracks, normalTime, out Vector3? positionOffset, out Quaternion? rotationOffset, out Vector3? scaleOffset, out Quaternion? localRotationOffset, out float? dissolve, out float? dissolveArrow, out float? cuttable);\n\n                if (positionOffset.HasValue)\n                {\n                    Vector3 moveStartPos = noodleData.MoveStartPos;\n                    Vector3 moveEndPos = noodleData.MoveEndPos;\n                    Vector3 jumpEndPos = noodleData.JumpEndPos;\n\n                    Vector3 offset = positionOffset.Value;\n                    _floorStartPosAccessor(ref floorMovement) = moveStartPos + offset;\n                    _floorEndPosAccessor(ref floorMovement) = moveEndPos + offset;\n                    _jumpStartPosAccessor(ref noteJump) = moveEndPos + offset;\n                    _jumpEndPosAccessor(ref noteJump) = jumpEndPos + offset;\n                }\n\n                Transform transform = __instance.transform;\n\n                if (rotationOffset.HasValue || localRotationOffset.HasValue)\n                {\n                    Quaternion worldRotation = noodleData.WorldRotation;\n                    Quaternion localRotation = noodleData.LocalRotation;\n\n                    Quaternion worldRotationQuatnerion = worldRotation;\n                    if (rotationOffset.HasValue)\n                    {\n                        worldRotationQuatnerion *= rotationOffset.Value;\n                        Quaternion inverseWorldRotation = Quaternion.Inverse(worldRotationQuatnerion);\n                        NoteControllerInit._worldRotationJumpAccessor(ref noteJump) = worldRotationQuatnerion;\n                        NoteControllerInit._inverseWorldRotationJumpAccessor(ref noteJump) = inverseWorldRotation;\n                        NoteControllerInit._worldRotationFloorAccessor(ref floorMovement) = worldRotationQuatnerion;\n                        NoteControllerInit._inverseWorldRotationFloorAccessor(ref floorMovement) = inverseWorldRotation;\n                    }\n\n                    worldRotationQuatnerion *= localRotation;\n\n                    if (localRotationOffset.HasValue)\n                    {\n                        worldRotationQuatnerion *= localRotationOffset.Value;\n                    }\n\n                    transform.localRotation = worldRotationQuatnerion;\n                }\n\n                if (scaleOffset.HasValue)\n                {\n                    transform.localScale = scaleOffset.Value;\n                }\n\n                if (dissolve.HasValue)\n                {\n                    if (CutoutManager.NoteCutoutEffects.TryGetValue(__instance, out CutoutEffectWrapper cutoutEffect))\n                    {\n                        cutoutEffect.SetCutout(dissolve.Value);\n                    }\n                }\n\n                if (dissolveArrow.HasValue && __instance.noteData.colorType != ColorType.None)\n                {\n                    if (CutoutManager.NoteDisappearingArrowWrappers.TryGetValue(__instance, out DisappearingArrowWrapper disappearingArrowWrapper))\n                    {\n                        disappearingArrowWrapper.SetCutout(dissolveArrow.Value);\n                    }\n                }\n\n                if (cuttable.HasValue)\n                {\n                    bool enabled = cuttable.Value >= 1;\n\n                    switch (__instance)\n                    {\n                        case GameNoteController gameNoteController:\n                            BoxCuttableBySaber[] bigCuttableBySaberList = _gameNoteBigCuttableAccessor(ref gameNoteController);\n                            foreach (BoxCuttableBySaber bigCuttableBySaber in bigCuttableBySaberList)\n                            {\n                                if (bigCuttableBySaber.canBeCut != enabled)\n                                {\n                                    bigCuttableBySaber.canBeCut = enabled;\n                                }\n                            }\n\n                            BoxCuttableBySaber[] smallCuttableBySaberList = _gameNoteSmallCuttableAccessor(ref gameNoteController);\n                            foreach (BoxCuttableBySaber smallCuttableBySaber in smallCuttableBySaberList)\n                            {\n                                if (smallCuttableBySaber.canBeCut != enabled)\n                                {\n                                    smallCuttableBySaber.canBeCut = enabled;\n                                }\n                            }\n\n                            break;\n\n                        case BombNoteController bombNoteController:\n                            CuttableBySaber boxCuttableBySaber = _bombNoteCuttableAccessor(ref bombNoteController);\n                            if (boxCuttableBySaber.canBeCut != enabled)\n                            {\n                                boxCuttableBySaber.canBeCut = enabled;\n                            }\n\n                            break;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/NoteFloorMovement.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using NoodleExtensions.Animation;\n    using UnityEngine;\n\n    [HeckPatch(typeof(NoteFloorMovement))]\n    [HeckPatch(\"ManualUpdate\")]\n    internal static class NoteFloorMovementManualUpdate\n    {\n        private static readonly FieldInfo _localPosition = AccessTools.Field(typeof(NoteFloorMovement), \"_localPosition\");\n\n        private static readonly MethodInfo _definiteNoteFloorMovement = AccessTools.Method(typeof(NoteFloorMovementManualUpdate), nameof(DefiniteNoteFloorMovement));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Stfld, _localPosition))\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Call, _definiteNoteFloorMovement))\n                .InstructionEnumeration();\n        }\n\n        private static Vector3 DefiniteNoteFloorMovement(Vector3 original, NoteFloorMovement noteFloorMovement)\n        {\n            NoodleObjectData? noodleData = NoteControllerUpdate.NoodleData;\n            if (noodleData != null)\n            {\n                AnimationHelper.GetDefinitePositionOffset(noodleData.AnimationObject, noodleData.Track, 0, out Vector3? position);\n                if (position.HasValue)\n                {\n                    Vector3 noteOffset = noodleData.NoteOffset;\n                    Vector3 endPos = NoteControllerUpdate._floorEndPosAccessor(ref noteFloorMovement);\n                    return original + (position.Value + noteOffset - endPos);\n                }\n            }\n\n            return original;\n        }\n    }\n\n    [HeckPatch(typeof(NoteFloorMovement))]\n    [HeckPatch(\"SetToStart\")]\n    internal static class NoteFloorMovementSetToStart\n    {\n        private static void Postfix(Transform ____rotatedObject)\n        {\n            NoodleNoteData? noodleData = (NoodleNoteData?)NoteControllerUpdate.NoodleData;\n            if (noodleData != null && noodleData.DisableLook)\n            {\n                ____rotatedObject.localRotation = Quaternion.Euler(0, 0, noodleData.EndRotation);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/NoteJump.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using IPA.Utilities;\n    using NoodleExtensions.Animation;\n    using UnityEngine;\n\n    [HeckPatch(typeof(NoteJump))]\n    [HeckPatch(\"ManualUpdate\")]\n    internal static class NoteJumpManualUpdate\n    {\n        private static readonly FieldInfo _threeQuartersMarkReportedField = AccessTools.Field(typeof(NoteJump), \"_threeQuartersMarkReported\");\n        private static readonly MethodInfo _localRotationSetter = AccessTools.PropertySetter(typeof(Transform), nameof(Transform.localRotation));\n\n        private static readonly FieldInfo _jumpDurationField = AccessTools.Field(typeof(NoteJump), \"_jumpDuration\");\n        private static readonly MethodInfo _noteJumpTimeAdjust = AccessTools.Method(typeof(NoteJumpManualUpdate), nameof(NoteJumpTimeAdjust));\n        private static readonly FieldInfo _localPositionField = AccessTools.Field(typeof(NoteJump), \"_localPosition\");\n        private static readonly MethodInfo _definiteNoteJump = AccessTools.Method(typeof(NoteJumpManualUpdate), nameof(DefiniteNoteJump));\n        private static readonly FieldInfo _definitePositionField = AccessTools.Field(typeof(NoteJumpManualUpdate), nameof(_definitePosition));\n        private static readonly MethodInfo _getTransform = AccessTools.PropertyGetter(typeof(Component), nameof(Component.transform));\n        private static readonly MethodInfo _doNoteLook = AccessTools.Method(typeof(NoteJumpManualUpdate), nameof(DoNoteLook));\n        private static readonly FieldInfo _startRotationField = AccessTools.Field(typeof(NoteJump), \"_startRotation\");\n        private static readonly FieldInfo _middleRotationField = AccessTools.Field(typeof(NoteJump), \"_middleRotation\");\n        private static readonly FieldInfo _endRotationField = AccessTools.Field(typeof(NoteJump), \"_endRotation\");\n        private static readonly FieldInfo _playerTransformsField = AccessTools.Field(typeof(NoteJump), \"_playerTransforms\");\n        private static readonly FieldInfo _rotatedObjectField = AccessTools.Field(typeof(NoteJump), \"_rotatedObject\");\n        private static readonly FieldInfo _inverseWorldRotationField = AccessTools.Field(typeof(NoteJump), \"_inverseWorldRotation\");\n        private static readonly FieldAccessor<PlayerTransforms, Transform>.Accessor _headTransformAccessor = FieldAccessor<PlayerTransforms, Transform>.GetAccessor(\"_headTransform\");\n\n        // This field is used by reflection\n#pragma warning disable CS0414\n        private static bool _definitePosition = false;\n#pragma warning restore CS0414\n\n        internal static float NoteJumpTimeAdjust(float original, float jumpDuration)\n        {\n            NoodleObjectData? noodleData = NoteControllerUpdate.NoodleData;\n            if (noodleData != null)\n            {\n                float? time = noodleData.Track?.Select(n => Heck.Animation.AnimationHelper.TryGetProperty<float?>(n, Plugin.TIME)).FirstOrDefault(n => n.HasValue);\n                if (time.HasValue)\n                {\n                    return time.Value * jumpDuration;\n                }\n            }\n\n            return original;\n        }\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            CodeMatcher codeMatcher = new CodeMatcher(instructions);\n            object label;\n\n            // CodeMatcher needs some better label manipulating methods\n            // replace rotation stuff\n            codeMatcher\n                .MatchForward(\n                    false,\n                    new CodeMatch(OpCodes.Callvirt, _localRotationSetter))\n                .Advance(1);\n            int endPos = codeMatcher.Pos;\n            label = codeMatcher.Labels.First();\n            codeMatcher\n                .MatchBack(\n                    false,\n                    new CodeMatch(null, label))\n                .Advance(1)\n                .RemoveInstructions(endPos - codeMatcher.Pos)\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldloc_1),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _startRotationField),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _middleRotationField),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _endRotationField),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _playerTransformsField),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _rotatedObjectField),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Call, _getTransform),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _inverseWorldRotationField),\n                    new CodeInstruction(OpCodes.Call, _doNoteLook))\n\n            // Add addition check to our quirky little variable to skip end position offset when we are using definitePosition\n                .MatchForward(\n                    true,\n                    new CodeMatch(OpCodes.Ldfld, _threeQuartersMarkReportedField),\n                    new CodeMatch(OpCodes.Brfalse));\n            label = codeMatcher.Operand;\n            codeMatcher\n                .Advance(1)\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldsfld, _definitePositionField),\n                    new CodeInstruction(OpCodes.Brtrue_S, label))\n                .Start();\n\n            return codeMatcher\n\n                // time adjust\n                .MatchForward(false, new CodeMatch(OpCodes.Stloc_0))\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _jumpDurationField),\n                    new CodeInstruction(OpCodes.Call, _noteJumpTimeAdjust))\n\n                // final position\n                .MatchForward(false, new CodeMatch(OpCodes.Stind_R4))\n                .Advance(2)\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _localPositionField),\n                    new CodeInstruction(OpCodes.Ldloc_1),\n                    new CodeInstruction(OpCodes.Call, _definiteNoteJump),\n                    new CodeInstruction(OpCodes.Stfld, _localPositionField))\n\n                .InstructionEnumeration();\n        }\n\n        private static Vector3 DefiniteNoteJump(Vector3 original, float time)\n        {\n            NoodleObjectData? noodleData = NoteControllerUpdate.NoodleData;\n            if (noodleData != null)\n            {\n                AnimationHelper.GetDefinitePositionOffset(noodleData.AnimationObject, noodleData.Track, time, out Vector3? position);\n                if (position.HasValue)\n                {\n                    Vector3 noteOffset = noodleData.NoteOffset;\n                    _definitePosition = true;\n                    return position.Value + noteOffset;\n                }\n            }\n\n            _definitePosition = false;\n            return original;\n        }\n\n        // Performs all note look rotation from world space\n        // Never want to touch this again....\n        private static void DoNoteLook(\n            float num2,\n            Quaternion startRotation,\n            Quaternion middleRotation,\n            Quaternion endRotation,\n            PlayerTransforms playerTransforms,\n            Transform rotatedObject,\n            Transform baseTransform,\n            Quaternion inverseWorldRotation)\n        {\n            NoodleNoteData? noodleData = (NoodleNoteData?)NoteControllerUpdate.NoodleData;\n            if (noodleData != null && noodleData.DisableLook)\n            {\n                rotatedObject.localRotation = endRotation;\n                return;\n            }\n\n            Quaternion a;\n            if (num2 < 0.125f)\n            {\n                a = Quaternion.Slerp(baseTransform.rotation * startRotation, baseTransform.rotation * middleRotation, Mathf.Sin(num2 * Mathf.PI * 4f));\n            }\n            else\n            {\n                a = Quaternion.Slerp(baseTransform.rotation * middleRotation, baseTransform.rotation * endRotation, Mathf.Sin((num2 - 0.125f) * Mathf.PI * 2f));\n            }\n\n            Vector3 vector = playerTransforms.headWorldPos;\n\n            // idk whats happening anymore\n            Quaternion worldRot = inverseWorldRotation;\n            if (baseTransform.parent != null)\n            {\n                // Handle parenting\n                worldRot *= Quaternion.Inverse(baseTransform.parent.rotation);\n            }\n\n            // This line but super complicated so that \"y\" = \"originTransform.up\"\n            // vector.y = Mathf.Lerp(vector.y, this._localPosition.y, 0.8f);\n            Transform headTransform = _headTransformAccessor(ref playerTransforms);\n            Quaternion inverse = Quaternion.Inverse(worldRot);\n            Vector3 upVector = inverse * Vector3.up;\n            float baseUpMagnitude = Vector3.Dot(worldRot * baseTransform.position, Vector3.up);\n            float headUpMagnitude = Vector3.Dot(worldRot * headTransform.position, Vector3.up);\n            float mult = Mathf.Lerp(headUpMagnitude, baseUpMagnitude, 0.8f) - headUpMagnitude;\n            vector += upVector * mult;\n\n            // more wtf\n            Vector3 normalized = baseTransform.rotation * (worldRot * (baseTransform.position - vector).normalized);\n\n            Quaternion b = Quaternion.LookRotation(normalized, rotatedObject.up);\n            rotatedObject.rotation = Quaternion.Lerp(a, b, num2 * 2f);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/ObstacleController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using Heck.Animation;\n    using UnityEngine;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n    using static NoodleExtensions.NoodleObjectDataManager;\n    using static NoodleExtensions.Plugin;\n\n    [HeckPatch(typeof(ObstacleController))]\n    [HeckPatch(\"Init\")]\n    internal static class ObstacleControllerInit\n    {\n        internal static readonly List<ObstacleController> _activeObstacles = new List<ObstacleController>();\n\n        private static readonly FieldInfo _worldRotationField = AccessTools.Field(typeof(ObstacleController), \"_worldRotation\");\n        private static readonly FieldInfo _inverseWorldRotationField = AccessTools.Field(typeof(ObstacleController), \"_inverseWorldRotation\");\n        private static readonly MethodInfo _widthGetter = AccessTools.PropertyGetter(typeof(ObstacleData), nameof(ObstacleData.width));\n        private static readonly FieldInfo _lengthField = AccessTools.Field(typeof(ObstacleController), \"_length\");\n\n        private static readonly MethodInfo _getCustomWidth = AccessTools.Method(typeof(ObstacleControllerInit), nameof(GetCustomWidth));\n        private static readonly MethodInfo _getWorldRotation = AccessTools.Method(typeof(ObstacleControllerInit), nameof(GetWorldRotation));\n        private static readonly MethodInfo _getCustomLength = AccessTools.Method(typeof(ObstacleControllerInit), nameof(GetCustomLength));\n        private static readonly MethodInfo _invertQuaternion = AccessTools.Method(typeof(ObstacleControllerInit), nameof(InvertQuaternion));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n\n                // world rotation\n                .MatchForward(false, new CodeMatch(OpCodes.Stfld, _worldRotationField))\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldarg_1),\n                    new CodeInstruction(OpCodes.Ldarg_2),\n                    new CodeInstruction(OpCodes.Call, _getWorldRotation))\n                .RemoveInstructionsWithOffsets(-4, -1)\n\n                // inverse world rotation\n                .MatchForward(false, new CodeMatch(OpCodes.Stfld, _inverseWorldRotationField))\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldarg_0),\n                    new CodeInstruction(OpCodes.Ldfld, _worldRotationField),\n                    new CodeInstruction(OpCodes.Call, _invertQuaternion))\n                .RemoveInstructionsWithOffsets(-5, -1)\n\n                // width\n                .MatchForward(false, new CodeMatch(OpCodes.Callvirt, _widthGetter))\n                .Advance(2)\n                .InsertAndAdvance(\n                    new CodeInstruction(OpCodes.Ldarg_1),\n                    new CodeInstruction(OpCodes.Call, _getCustomWidth))\n\n                // length\n                .MatchForward(false, new CodeMatch(OpCodes.Stfld, _lengthField))\n                .Insert(\n                    new CodeInstruction(OpCodes.Ldarg_1),\n                    new CodeInstruction(OpCodes.Call, _getCustomLength))\n\n                .InstructionEnumeration();\n        }\n\n        private static void Postfix(ObstacleController __instance, Quaternion ____worldRotation, ObstacleData obstacleData, Vector3 ____startPos, Vector3 ____midPos, Vector3 ____endPos, ref Bounds ____bounds)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n            if (noodleData == null)\n            {\n                return;\n            }\n\n            Quaternion? localRotationQuaternion = noodleData.LocalRotationQuaternion;\n\n            Transform transform = __instance.transform;\n\n            Quaternion localRotation = Quaternion.identity;\n            if (localRotationQuaternion.HasValue)\n            {\n                localRotation = localRotationQuaternion.Value;\n                transform.localRotation = ____worldRotation * localRotation;\n            }\n\n            if (transform.localScale != Vector3.one)\n            {\n                transform.localScale = Vector3.one; // This is a fix for animation due to obstacles being recycled\n            }\n\n            IEnumerable<Track>? tracks = noodleData.Track;\n            if (tracks != null)\n            {\n                foreach (Track track in tracks)\n                {\n                    // add to gameobjects\n                    track.AddGameObject(__instance.gameObject);\n                }\n            }\n\n            bool? cuttable = noodleData.Cuttable;\n            if (cuttable.HasValue && !cuttable.Value)\n            {\n                ____bounds.size = Vector3.zero;\n            }\n            else\n            {\n                _activeObstacles.Add(__instance);\n            }\n\n            noodleData.StartPos = ____startPos;\n            noodleData.MidPos = ____midPos;\n            noodleData.EndPos = ____endPos;\n            noodleData.LocalRotation = localRotation;\n            noodleData.BoundsSize = ____bounds.size;\n        }\n\n        private static Quaternion InvertQuaternion(Quaternion quaternion)\n        {\n            return Quaternion.Inverse(quaternion);\n        }\n\n        private static Quaternion GetWorldRotation(ObstacleData obstacleData, float @default)\n        {\n            Quaternion worldRotation = Quaternion.Euler(0, @default, 0);\n\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n            if (noodleData != null)\n            {\n                Quaternion? worldRotationQuaternion = noodleData.WorldRotationQuaternion;\n                if (worldRotationQuaternion.HasValue)\n                {\n                    worldRotation = worldRotationQuaternion.Value;\n                }\n\n                noodleData.WorldRotation = worldRotation;\n            }\n\n            return worldRotation;\n        }\n\n        private static float GetCustomWidth(float @default, ObstacleData obstacleData)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n            if (noodleData != null)\n            {\n                float? width = noodleData.Width;\n                if (width.HasValue)\n                {\n                    return width.Value;\n                }\n            }\n\n            return @default;\n        }\n\n        private static float GetCustomLength(float @default, ObstacleData obstacleData)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n            if (noodleData != null)\n            {\n                float? length = noodleData.Length;\n                if (length.HasValue)\n                {\n                    return length.Value * NoteLinesDistance;\n                }\n            }\n\n            return @default;\n        }\n    }\n\n    [HeckPatch(typeof(ObstacleController))]\n    [HeckPatch(\"ManualUpdate\")]\n    internal static class ObstacleControllerManualUpdate\n    {\n        private static readonly FieldInfo _obstacleDataField = AccessTools.Field(typeof(ObstacleController), \"_obstacleData\");\n        private static readonly FieldInfo _move1DurationField = AccessTools.Field(typeof(ObstacleController), \"_move1Duration\");\n        private static readonly FieldInfo _finishMovementTime = AccessTools.Field(typeof(ObstacleController), \"_finishMovementTime\");\n        private static readonly MethodInfo _obstacleTimeAdjust = AccessTools.Method(typeof(ObstacleControllerManualUpdate), nameof(ObstacleTimeAdjust));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            List<CodeInstruction> instructionList = instructions.ToList();\n            bool foundTime = false;\n            for (int i = 0; i < instructionList.Count; i++)\n            {\n                if (!foundTime &&\n                    instructionList[i].opcode == OpCodes.Stloc_0)\n                {\n                    foundTime = true;\n                    instructionList.Insert(i, new CodeInstruction(OpCodes.Ldarg_0));\n                    instructionList.Insert(i + 1, new CodeInstruction(OpCodes.Ldfld, _obstacleDataField));\n                    instructionList.Insert(i + 2, new CodeInstruction(OpCodes.Ldarg_0));\n                    instructionList.Insert(i + 3, new CodeInstruction(OpCodes.Ldfld, _move1DurationField));\n                    instructionList.Insert(i + 4, new CodeInstruction(OpCodes.Ldarg_0));\n                    instructionList.Insert(i + 5, new CodeInstruction(OpCodes.Ldfld, _finishMovementTime));\n                    instructionList.Insert(i + 6, new CodeInstruction(OpCodes.Call, _obstacleTimeAdjust));\n                }\n            }\n\n            if (!foundTime)\n            {\n                Plugin.Logger.Log(\"Failed to find stloc.0!\", IPA.Logging.Logger.Level.Error);\n            }\n\n            return instructionList.AsEnumerable();\n        }\n\n        private static float ObstacleTimeAdjust(float original, ObstacleData obstacleData, float move1Duration, float finishMovementTime)\n        {\n            if (original > move1Duration)\n            {\n                NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(obstacleData);\n                if (noodleData != null)\n                {\n                    float? time = noodleData.Track?.Select(n => AnimationHelper.TryGetProperty<float?>(n, TIME)).FirstOrDefault(n => n.HasValue);\n                    if (time.HasValue)\n                    {\n                        return (time.Value * (finishMovementTime - move1Duration)) + move1Duration;\n                    }\n                }\n            }\n\n            return original;\n        }\n\n        private static void Prefix(\n            ObstacleController __instance,\n            ObstacleData ____obstacleData,\n            AudioTimeSyncController ____audioTimeSyncController,\n            float ____startTimeOffset,\n            ref Vector3 ____startPos,\n            ref Vector3 ____midPos,\n            ref Vector3 ____endPos,\n            float ____move1Duration,\n            float ____move2Duration,\n            float ____obstacleDuration,\n            ref Quaternion ____worldRotation,\n            ref Quaternion ____inverseWorldRotation,\n            ref Bounds ____bounds)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(____obstacleData);\n            if (noodleData == null)\n            {\n                return;\n            }\n\n            IEnumerable<Track>? tracks = noodleData.Track;\n            NoodleObjectData.AnimationObjectData? animationObject = noodleData.AnimationObject;\n            if (tracks != null || animationObject != null)\n            {\n                // idk i just copied base game time\n                float elapsedTime = ____audioTimeSyncController.songTime - ____startTimeOffset;\n                float normalTime = (elapsedTime - ____move1Duration) / (____move2Duration + ____obstacleDuration);\n\n                Animation.AnimationHelper.GetObjectOffset(animationObject, tracks, normalTime, out Vector3? positionOffset, out Quaternion? rotationOffset, out Vector3? scaleOffset, out Quaternion? localRotationOffset, out float? dissolve, out float? _, out float? cuttable);\n\n                if (positionOffset.HasValue)\n                {\n                    Vector3 startPos = noodleData.StartPos;\n                    Vector3 midPos = noodleData.MidPos;\n                    Vector3 endPos = noodleData.EndPos;\n\n                    Vector3 offset = positionOffset.Value;\n                    ____startPos = startPos + offset;\n                    ____midPos = midPos + offset;\n                    ____endPos = endPos + offset;\n                }\n\n                Transform transform = __instance.transform;\n\n                if (rotationOffset.HasValue || localRotationOffset.HasValue)\n                {\n                    Quaternion worldRotation = noodleData.WorldRotation;\n                    Quaternion localRotation = noodleData.LocalRotation;\n\n                    Quaternion worldRotationQuatnerion = worldRotation;\n                    if (rotationOffset.HasValue)\n                    {\n                        worldRotationQuatnerion *= rotationOffset.Value;\n                        Quaternion inverseWorldRotation = Quaternion.Inverse(worldRotationQuatnerion);\n                        ____worldRotation = worldRotationQuatnerion;\n                        ____inverseWorldRotation = inverseWorldRotation;\n                    }\n\n                    worldRotationQuatnerion *= localRotation;\n\n                    if (localRotationOffset.HasValue)\n                    {\n                        worldRotationQuatnerion *= localRotationOffset.Value;\n                    }\n\n                    transform.localRotation = worldRotationQuatnerion;\n                }\n\n                if (cuttable.HasValue)\n                {\n                    if (cuttable.Value >= 1)\n                    {\n                        if (____bounds.size != Vector3.zero)\n                        {\n                            ____bounds.size = Vector3.zero;\n                        }\n                    }\n                    else\n                    {\n                        Vector3 boundsSize = noodleData.BoundsSize;\n                        if (____bounds.size != boundsSize)\n                        {\n                            ____bounds.size = boundsSize;\n                        }\n                    }\n                }\n\n                if (scaleOffset.HasValue)\n                {\n                    transform.localScale = scaleOffset.Value;\n                }\n\n                if (dissolve.HasValue)\n                {\n                    if (CutoutManager.ObstacleCutoutEffects.TryGetValue(__instance, out CutoutAnimateEffectWrapper cutoutAnimateEffect))\n                    {\n                        cutoutAnimateEffect.SetCutout(dissolve.Value);\n                    }\n                }\n            }\n\n            if (noodleData.DoUnhide)\n            {\n                __instance.hide = false;\n            }\n        }\n    }\n\n    [HeckPatch(typeof(ObstacleController))]\n    [HeckPatch(\"GetPosForTime\")]\n    internal static class ObstacleControllerGetPosForTime\n    {\n        private static bool Prefix(\n            ref Vector3 __result,\n            ObstacleData ____obstacleData,\n            Vector3 ____startPos,\n            Vector3 ____midPos,\n            float ____move1Duration,\n            float ____move2Duration,\n            float ____obstacleDuration,\n            float time)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(____obstacleData);\n            if (noodleData == null)\n            {\n                return true;\n            }\n\n            float jumpTime = Mathf.Clamp((time - ____move1Duration) / (____move2Duration + ____obstacleDuration), 0, 1);\n            Animation.AnimationHelper.GetDefinitePositionOffset(noodleData.AnimationObject, noodleData.Track, jumpTime, out Vector3? position);\n\n            if (position.HasValue)\n            {\n                Vector3 noteOffset = noodleData.NoteOffset;\n                Vector3 definitePosition = position.Value + noteOffset;\n                definitePosition.x += noodleData.XOffset;\n                if (time < ____move1Duration)\n                {\n                    __result = Vector3.LerpUnclamped(____startPos, ____midPos, time / ____move1Duration);\n                    __result += definitePosition - ____midPos;\n                }\n                else\n                {\n                    __result = definitePosition;\n                }\n\n                return false;\n            }\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SceneTransition/MissionLevelScenesTransitionSetupDataSO.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using HarmonyLib;\n\n    [HarmonyPatch(\n        typeof(MissionLevelScenesTransitionSetupDataSO),\n        new Type[] { typeof(string), typeof(IDifficultyBeatmap), typeof(IPreviewBeatmapLevel), typeof(MissionObjective[]), typeof(ColorScheme), typeof(GameplayModifiers), typeof(PlayerSpecificSettings), typeof(string) })]\n    [HarmonyPatch(\"Init\")]\n    internal static class MissionLevelScenesTransitionSetupDataSOInit\n    {\n        private static void Postfix(IDifficultyBeatmap difficultyBeatmap)\n        {\n            SceneTransitionHelper.Patch(difficultyBeatmap);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SceneTransition/MultiplayerLevelScenesTransitionSetupDataSO.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using HarmonyLib;\n\n    [HarmonyPatch(\n        typeof(MultiplayerLevelScenesTransitionSetupDataSO),\n        new Type[] { typeof(string), typeof(IPreviewBeatmapLevel), typeof(BeatmapDifficulty), typeof(BeatmapCharacteristicSO), typeof(IDifficultyBeatmap), typeof(ColorScheme), typeof(GameplayModifiers), typeof(PlayerSpecificSettings), typeof(PracticeSettings), typeof(bool) })]\n    [HarmonyPatch(\"Init\")]\n    internal static class MultiplayerLevelScenesTransitionSetupDataSOInit\n    {\n        private static void Postfix(IDifficultyBeatmap difficultyBeatmap)\n        {\n            SceneTransitionHelper.Patch(difficultyBeatmap);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SceneTransition/SceneTransitionHelper.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using static NoodleExtensions.Plugin;\n\n    internal static class SceneTransitionHelper\n    {\n        internal static void Patch(IDifficultyBeatmap difficultyBeatmap)\n        {\n            if (difficultyBeatmap.beatmapData is CustomBeatmapData customBeatmapData)\n            {\n                IEnumerable<string>? requirements = customBeatmapData.beatmapCustomData.Get<List<object>>(\"_requirements\")?.Cast<string>();\n                bool noodleRequirement = requirements?.Contains(CAPABILITY) ?? false;\n                NoodleController.ToggleNoodlePatches(noodleRequirement);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SceneTransition/StandardLevelScenesTransitionSetupDataSO.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using HarmonyLib;\n\n    [HarmonyPatch(\n        typeof(StandardLevelScenesTransitionSetupDataSO),\n        new Type[] { typeof(string), typeof(IDifficultyBeatmap), typeof(IPreviewBeatmapLevel), typeof(OverrideEnvironmentSettings), typeof(ColorScheme), typeof(GameplayModifiers), typeof(PlayerSpecificSettings), typeof(PracticeSettings), typeof(string), typeof(bool) })]\n    [HarmonyPatch(\"Init\")]\n    internal static class StandardLevelScenesTransitionSetupDataSOInit\n    {\n        private static void Postfix(IDifficultyBeatmap difficultyBeatmap)\n        {\n            SceneTransitionHelper.Patch(difficultyBeatmap);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SceneTransition/TutorialScenesTransitionSetupDataSO.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using HarmonyLib;\n\n    // Force disable Noodle Extensions in tutorial scene\n    [HarmonyPatch(typeof(TutorialScenesTransitionSetupDataSO))]\n    [HarmonyPatch(\"Init\")]\n    internal static class TutorialScenesTransitionSetupDataSOInit\n    {\n        private static void Prefix()\n        {\n            NoodleController.ToggleNoodlePatches(false);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/BasicBeatmapObjectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using System.Collections.Generic;\n    using Heck;\n\n    // Do not add fake obstacles to active obstacles to increase performance\n    [HeckPatch(typeof(BasicBeatmapObjectManager))]\n    [HeckPatch(\"Init\")]\n    internal static class BasicBeatmapObjectManagerInit\n    {\n        private static void Postfix()\n        {\n            ObstacleControllerInit._activeObstacles.Clear();\n        }\n    }\n\n    [HeckPatch(typeof(BasicBeatmapObjectManager))]\n    [HeckPatch(\"get_activeObstacleControllers\")]\n    internal static class BasicBeatmapObjectManagerGetActiveObstacleControllers\n    {\n        private static bool Prefix(ref List<ObstacleController> __result)\n        {\n            __result = ObstacleControllerInit._activeObstacles;\n            return false;\n        }\n    }\n\n    [HeckPatch(typeof(BasicBeatmapObjectManager))]\n    [HeckPatch(\"DespawnInternal\")]\n    [HeckPatch(new Type[] { typeof(ObstacleController) })]\n    internal static class BasicBeatmapObjectManagerDespawnInternal\n    {\n        private static void Postfix(ObstacleController obstacleController)\n        {\n            ObstacleControllerInit._activeObstacles.Remove(obstacleController);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/BeatEffectSpawner.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using UnityEngine;\n\n    // Readjust spawn effect to take global position instead of local\n    [HeckPatch(typeof(BeatEffectSpawner))]\n    [HeckPatch(\"HandleNoteDidStartJump\")]\n    internal static class BeatEffectSpawnerHandleNoteDidStartJump\n    {\n        private static readonly MethodInfo _jumpStartPosGetter = AccessTools.PropertyGetter(typeof(NoteController), nameof(NoteController.jumpStartPos));\n        private static readonly MethodInfo _beatEffectInit = AccessTools.Method(typeof(BeatEffect), nameof(BeatEffect.Init));\n\n        private static readonly MethodInfo _getNoteControllerPosition = AccessTools.Method(typeof(BeatEffectSpawnerHandleNoteDidStartJump), nameof(GetNoteControllerPosition));\n        private static readonly MethodInfo _getNoteControllerRotation = AccessTools.Method(typeof(BeatEffectSpawnerHandleNoteDidStartJump), nameof(GetNoteControllerRotation));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n\n                // position\n                .MatchForward(false, new CodeMatch(OpCodes.Callvirt, _jumpStartPosGetter))\n                .Advance(-2)\n                .InsertAndAdvance(new CodeInstruction(OpCodes.Call, _getNoteControllerPosition))\n                .RemoveInstructions(4)\n\n                // rotation\n                .MatchForward(false, new CodeMatch(OpCodes.Callvirt, _beatEffectInit))\n                .Advance(-1)\n                .Set(OpCodes.Call, _getNoteControllerRotation)\n\n                .InstructionEnumeration();\n        }\n\n        private static Vector3 GetNoteControllerPosition(NoteController noteController)\n        {\n            return noteController.transform.position;\n        }\n\n        private static Quaternion GetNoteControllerRotation(NoteController noteController)\n        {\n            return noteController.transform.rotation;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/BeatmapObjectManager.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using static NoodleExtensions.NoodleObjectDataManager;\n\n    // TODO: find out what actually causes obstacle flickering\n    [HeckPatch(typeof(BeatmapObjectManager))]\n    [HeckPatch(\"SpawnObstacle\")]\n    internal static class BeatmapObjectManagerSpawnObstacle\n    {\n        private static readonly MethodInfo _spawnhiddenGetter = AccessTools.PropertyGetter(typeof(BeatmapObjectManager), nameof(BeatmapObjectManager.spawnHidden));\n\n        private static readonly MethodInfo _getHiddenForType = AccessTools.Method(typeof(BeatmapObjectManagerSpawnObstacle), nameof(GetHiddenForType));\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _spawnhiddenGetter))\n                .SetOperandAndAdvance(_getHiddenForType)\n                .InstructionEnumeration();\n        }\n\n        private static void Postfix(ObstacleController __result)\n        {\n            NoodleObstacleData? noodleData = TryGetObjectData<NoodleObstacleData>(__result.obstacleData);\n            if (noodleData != null)\n            {\n                noodleData.DoUnhide = true;\n            }\n        }\n\n        private static bool GetHiddenForType(BeatmapObjectManager beatmapObjectManager)\n        {\n            if (beatmapObjectManager is BasicBeatmapObjectManager)\n            {\n                return true;\n            }\n\n            return beatmapObjectManager.spawnHidden;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/CutoutEffect.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System;\n    using Heck;\n    using UnityEngine;\n\n    // Do not run SetCutout if the new value is the same as old.\n    [HeckPatch(typeof(CutoutEffect))]\n    [HeckPatch(\"SetCutout\")]\n    [HeckPatch(new Type[] { typeof(float), typeof(Vector3) })]\n    internal static class CutoutEffectSetCutout\n    {\n        private static bool Prefix(float cutout, float ____cutout)\n        {\n            return !(cutout == ____cutout);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/DisappearingArrowController.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using Heck;\n\n    [HeckPatch(typeof(DisappearingArrowControllerBase<GameNoteController>))]\n    [HeckPatch(\"SetArrowTransparency\")]\n    internal static class DisappearingArrowControllerSetArrowTransparency\n    {\n        // This makes _dissolveArrow work and I cannot figure out why\n        private static void Postfix(CutoutEffect ____arrowCutoutEffect, float arrowTransparency)\n        {\n            ____arrowCutoutEffect.SetCutout(1f - arrowTransparency);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/MemoryPoolBase.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using System.Collections.Generic;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;\n    using Heck;\n    using ModestTree;\n    using Zenject;\n\n    [HeckPatch(typeof(MemoryPoolBase<ObstacleController>))]\n    [HeckPatch(\"Despawn\")]\n    internal static class MemoryPoolBaseObstacleControllerDespawn\n    {\n        private static readonly MethodInfo _assertThat = AccessTools.Method(typeof(Assert), nameof(Assert.That), new System.Type[] { typeof(bool), typeof(string), typeof(object) });\n\n        // This stupid assert runs a Contains() which is laggy when spawning an insane amount of walls\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            return new CodeMatcher(instructions)\n                .MatchForward(false, new CodeMatch(OpCodes.Call, _assertThat))\n                .RemoveInstructionsWithOffsets(-9, 0)\n                .InstructionEnumeration();\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SmallFixes/PlayerTransforms.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    /*using System.Collections.Generic;\n    using System.Linq;\n    using System.Reflection;\n    using System.Reflection.Emit;\n    using HarmonyLib;*/\n\n    using Heck;\n    using UnityEngine;\n\n    // Possibly breaks in multiplayer? We don't support that anyways.\n    // edit: we do support it, but it hasnt broken yet so fuck it\n    [HeckPatch(typeof(PlayerTransforms))]\n    [HeckPatch(\"Update\")]\n    internal static class PlayerTransformsUpdate\n    {\n        private static void Postfix(ref Vector3 ____headPseudoLocalPos, Transform ____headTransform)\n        {\n            ____headPseudoLocalPos = ____headTransform.localPosition;\n        }\n    }\n\n    /*[NoodlePatch(typeof(PlayerTransforms))]\n    [NoodlePatch(\"MoveTowardsHead\")]\n    internal static class PlayerTransformsMoveTowardsHead\n    {\n        private static readonly MethodInfo _headOffsetZNotPsuedo = SymbolExtensions.GetMethodInfo(() => HeadOffsetZNotPsuedo(Quaternion.identity, Vector3.zero));\n        private static readonly FieldInfo _headTransformField = AccessTools.Field(typeof(PlayerTransforms), \"_headTransform\");\n        private static readonly MethodInfo _localPositionMethod = typeof(Transform).GetProperty(\"localPosition\").GetGetMethod();\n\n        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)\n        {\n            List<CodeInstruction> instructionList = instructions.ToList();\n            bool foundHeadLocalPos = false;\n            for (int i = 0; i < instructionList.Count; i++)\n            {\n                if (!foundHeadLocalPos &&\n                    instructionList[i].opcode == OpCodes.Call &&\n                  ((MethodInfo)instructionList[i].operand).Name == \"HeadOffsetZ\")\n                {\n                    foundHeadLocalPos = true;\n                    instructionList[i].operand = _headOffsetZNotPsuedo;\n\n                    instructionList.Insert(i, new CodeInstruction(OpCodes.Ldarg_0));\n                    instructionList.Insert(i + 1, new CodeInstruction(OpCodes.Ldfld, _headTransformField));\n                    instructionList.Insert(i + 2, new CodeInstruction(OpCodes.Callvirt, _localPositionMethod));\n\n                    instructionList.RemoveAt(0);\n                }\n            }\n\n            if (!foundHeadLocalPos)\n            {\n                NoodleExtensions.Plugin.Logger.Log(\"Failed to find call to HeadOffsetZ!\", IPA.Logging.Logger.Level.Error);\n            }\n\n            return instructionList.AsEnumerable();\n        }\n\n        private static float HeadOffsetZNotPsuedo(Quaternion noteInverseWorldRotation, Vector3 localPos)\n        {\n            return (noteInverseWorldRotation * localPos).z;\n        }\n    }*/\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SpawnDataHelper.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using IPA.Utilities;\n    using UnityEngine;\n    using static NoodleExtensions.HarmonyPatches.SpawnDataHelper.BeatmapObjectSpawnMovementDataVariables;\n\n    internal static class SpawnDataHelper\n    {\n        internal static Vector3 GetNoteOffset(BeatmapObjectData beatmapObjectData, float? startRow, float? startHeight)\n        {\n            float distance = (-(NoteLinesCount - 1f) * 0.5f) + (startRow.HasValue ? NoteLinesCount / 2f : 0); // Add last part to simulate https://github.com/spookyGh0st/beatwalls/#wall\n            float lineIndex = startRow.GetValueOrDefault(beatmapObjectData.lineIndex);\n            distance = (distance + lineIndex) * NoteLinesDistance;\n\n            return (RightVec * distance)\n                + new Vector3(0, LineYPosForLineLayer(beatmapObjectData, startHeight), 0);\n        }\n\n        internal static float LineYPosForLineLayer(BeatmapObjectData beatmapObjectData, float? height)\n        {\n            float ypos = BaseLinesYPos;\n            if (height.HasValue)\n            {\n                ypos = (height.Value * NoteLinesDistance) + BaseLinesYPos; // offset by 0.25\n            }\n            else if (beatmapObjectData is NoteData noteData)\n            {\n                ypos = BeatmapObjectSpawnMovementData.LineYPosForLineLayer(noteData.noteLineLayer);\n            }\n\n            return ypos;\n        }\n\n        internal static void GetNoteJumpValues(\n            float? inputNoteJumpMovementSpeed,\n            float? inputNoteJumpStartBeatOffset,\n            out float localJumpDuration,\n            out float localJumpDistance,\n            out Vector3 localMoveStartPos,\n            out Vector3 localMoveEndPos,\n            out Vector3 localJumpEndPos)\n        {\n            float localNoteJumpMovementSpeed = inputNoteJumpMovementSpeed ?? NoteJumpMovementSpeed;\n            float localNoteJumpStartBeatOffset = inputNoteJumpStartBeatOffset ?? NoteJumpStartBeatOffset;\n            float num = 60f / StartBPM;\n            float num2 = StartHalfJumpDurationInBeats;\n            while (localNoteJumpMovementSpeed * num * num2 > MaxHalfJumpDistance)\n            {\n                num2 /= 2f;\n            }\n\n            num2 += localNoteJumpStartBeatOffset;\n            if (num2 < 1f)\n            {\n                num2 = 1f;\n            }\n\n            localJumpDuration = num * num2 * 2f;\n            localJumpDistance = localNoteJumpMovementSpeed * localJumpDuration;\n            localMoveStartPos = CenterPos + (ForwardVec * (MoveDistance + (localJumpDistance * 0.5f)));\n            localMoveEndPos = CenterPos + (ForwardVec * localJumpDistance * 0.5f);\n            localJumpEndPos = CenterPos - (ForwardVec * localJumpDistance * 0.5f);\n        }\n\n        internal static void InitBeatmapObjectSpawnController(BeatmapObjectSpawnMovementData beatmapObjectSpawnMovementData)\n        {\n            BeatmapObjectSpawnMovementData = beatmapObjectSpawnMovementData;\n        }\n\n        internal static class BeatmapObjectSpawnMovementDataVariables\n        {\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _startBPMAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_startBpm\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _topObstaclePosYAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_topObstaclePosY\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _jumpOffsetYAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_jumpOffsetY\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _verticalObstaclePosYAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_verticalObstaclePosY\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _moveDistanceAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_moveDistance\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _noteJumpMovementSpeedAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_noteJumpMovementSpeed\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _noteJumpStartBeatOffsetAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_noteJumpStartBeatOffset\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _noteLinesDistanceAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_noteLinesDistance\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _baseLinesYPosAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_baseLinesYPos\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _noteLinesCountAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_noteLinesCount\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.Accessor _centerPosAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.GetAccessor(\"_centerPos\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.Accessor _forwardVecAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.GetAccessor(\"_forwardVec\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.Accessor _rightVecAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, Vector3>.GetAccessor(\"_rightVec\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _startHalfJumpDurationInBeatsAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_startHalfJumpDurationInBeats\");\n            private static readonly FieldAccessor<BeatmapObjectSpawnMovementData, float>.Accessor _maxHalfJumpDistanceAccessor = FieldAccessor<BeatmapObjectSpawnMovementData, float>.GetAccessor(\"_maxHalfJumpDistance\");\n            private static BeatmapObjectSpawnMovementData? _beatmapObjectSpawnMovementData;\n\n            internal static BeatmapObjectSpawnMovementData BeatmapObjectSpawnMovementData { get => _beatmapObjectSpawnMovementData!; set => _beatmapObjectSpawnMovementData = value; }\n\n            internal static float StartBPM => _startBPMAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float TopObstaclePosY => _topObstaclePosYAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float JumpOffsetY => _jumpOffsetYAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float VerticalObstaclePosY => _verticalObstaclePosYAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float MoveDistance => _moveDistanceAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float NoteJumpMovementSpeed => _noteJumpMovementSpeedAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float NoteJumpStartBeatOffset => _noteJumpStartBeatOffsetAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float NoteLinesDistance => _noteLinesDistanceAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float BaseLinesYPos => _baseLinesYPosAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float NoteLinesCount => _noteLinesCountAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static Vector3 CenterPos => _centerPosAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static Vector3 ForwardVec => _forwardVecAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static Vector3 RightVec => _rightVecAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float StartHalfJumpDurationInBeats => _startHalfJumpDurationInBeatsAccessor(ref _beatmapObjectSpawnMovementData!);\n\n            internal static float MaxHalfJumpDistance => _maxHalfJumpDistanceAccessor(ref _beatmapObjectSpawnMovementData!);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/HarmonyPatches/SpawnRotationProcessor.cs",
    "content": "﻿namespace NoodleExtensions.HarmonyPatches\n{\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using Heck;\n    using static NoodleExtensions.Plugin;\n\n    // i still dont even know if this works, but must maintain feature parity with mapping extensions!\n    [HeckPatch(typeof(SpawnRotationProcessor))]\n    [HeckPatch(\"ProcessBeatmapEventData\")]\n    internal static class SpawnRotationProcessorProcessBeatmapEventData\n    {\n        private static bool Prefix(BeatmapEventData beatmapEventData, ref float ____rotation)\n        {\n            if (beatmapEventData.type.IsRotationEvent() && beatmapEventData is CustomBeatmapEventData customData)\n            {\n                float? rotation = customData.customData.Get<float?>(ROTATION);\n\n                if (rotation.HasValue)\n                {\n                    ____rotation += rotation.Value;\n                    return false;\n                }\n            }\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/NoodleController.cs",
    "content": "﻿namespace NoodleExtensions\n{\n    using static NoodleExtensions.Plugin;\n\n    public static class NoodleController\n    {\n        public static bool NoodleExtensionsActive { get; private set; }\n\n        public static void ToggleNoodlePatches(bool value)\n        {\n            if (value != NoodleExtensionsActive)\n            {\n                Heck.HeckData.TogglePatches(_harmonyInstance, value);\n\n                NoodleExtensionsActive = value;\n                if (NoodleExtensionsActive)\n                {\n                    CustomJSONData.CustomEventCallbackController.didInitEvent += Animation.AnimationController.CustomEventCallbackInit;\n                }\n                else\n                {\n                    CustomJSONData.CustomEventCallbackController.didInitEvent -= Animation.AnimationController.CustomEventCallbackInit;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/NoodleExtensions.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net48</TargetFramework>\n    <OutputType>Library</OutputType>\n    <LangVersion>9</LangVersion>\n    <Nullable>enable</Nullable>\n    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>\n    <LocalRefsDir Condition=\"Exists('..\\Refs')\">..\\Refs</LocalRefsDir>\n    <BeatSaberDir>$(LocalRefsDir)</BeatSaberDir>\n    <AppOutputBase>$(MSBuildProjectDirectory)\\</AppOutputBase>\n    <EnableNETAnalyzers>true</EnableNETAnalyzers>\n    <DebugType>portable</DebugType>\n  </PropertyGroup>\n\n  <PropertyGroup Condition=\"$(DefineConstants.Contains('CIBuild')) OR '$(NCrunch)' == '1'\">\n    <DisableCopyToPlugins>True</DisableCopyToPlugins>\n  </PropertyGroup>\n\n  <PropertyGroup Condition=\"'$(NCrunch)' == '1'\">\n    <DisableCopyToPlugins>True</DisableCopyToPlugins>\n    <DisableZipRelease>True</DisableZipRelease>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Reference Include=\"UnityEngine.CoreModule\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\UnityEngine.CoreModule.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"Main\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\Main.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"BeatmapCore\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\BeatmapCore.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"IPA.Loader\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\IPA.Loader.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"0Harmony\">\n      <HintPath>$(BeatSaberDir)\\Libs\\0Harmony.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"Zenject\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\Zenject.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"Heck\">\n      <HintPath>$(BeatSaberDir)\\Plugins\\Heck.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"GameplayCore\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\GameplayCore.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"CustomJSONData\">\n      <HintPath>$(BeatSaberDir)\\Plugins\\CustomJSONData.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"Colors\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\Colors.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"HMLib\">\n      <HintPath>$(BeatSaberDir)\\Beat Saber_Data\\Managed\\HMLib.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"SongCore\">\n      <HintPath>$(BeatSaberDir)\\Plugins\\SongCore.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Include=\"Directory.Build.props\" Condition=\"Exists('Directory.Build.props')\" />\n    <None Include=\"Directory.Build.targets\" Condition=\"Exists('Directory.Build.targets')\" />\n    <None Include=\"NoodleExtensions.csproj.user\" Condition=\"Exists('NoodleExtensions.csproj.user')\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <EmbeddedResource Include=\"manifest.json\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"BeatSaberModdingTools.Tasks\" Version=\"1.4.3\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"StyleCop.Analyzers\">\n      <Version>1.2.0-beta.354</Version>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      <PrivateAssets>all</PrivateAssets>\n    </PackageReference>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "NoodleExtensions/NoodleExtensionsExtensions.cs",
    "content": "﻿namespace NoodleExtensions\n{\n    using System;\n    using System.Collections.Generic;\n    using CustomJSONData.CustomBeatmap;\n\n    internal static class NoodleExtensionsExtensions\n    {\n        internal static Dictionary<string, object?> GetDataForObject(this BeatmapObjectData beatmapObjectData)\n        {\n            Dictionary<string, object?> dynData;\n            switch (beatmapObjectData)\n            {\n                case CustomObstacleData data:\n                    dynData = data.customData;\n                    break;\n\n                case CustomNoteData data:\n                    dynData = data.customData;\n                    break;\n\n                case CustomWaypointData data:\n                    dynData = data.customData;\n                    break;\n\n                default:\n                    throw new InvalidOperationException($\"beatmapObjectdata was not of type CustomObstacleData, CustomNoteData, or CustomWaypointData. Was: {beatmapObjectData.GetType().FullName}\");\n            }\n\n            return dynData;\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/NoodleObjectData.cs",
    "content": "﻿namespace NoodleExtensions\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using CustomJSONData;\n    using CustomJSONData.CustomBeatmap;\n    using Heck;\n    using Heck.Animation;\n    using UnityEngine;\n    using static NoodleExtensions.Plugin;\n\n    internal static class NoodleObjectDataManager\n    {\n        private static Dictionary<BeatmapObjectData, NoodleObjectData> _noodleObjectDatas = new Dictionary<BeatmapObjectData, NoodleObjectData>();\n\n        internal static T? TryGetObjectData<T>(BeatmapObjectData beatmapObjectData)\n        {\n            if (_noodleObjectDatas.TryGetValue(beatmapObjectData, out NoodleObjectData noodleObjectData))\n            {\n                if (noodleObjectData is T t)\n                {\n                    return t;\n                }\n                else\n                {\n                    throw new InvalidOperationException($\"NoodleObjectData was not of correct type. Expected: {typeof(T).Name}, was: {noodleObjectData.GetType().Name}\");\n                }\n            }\n\n            return default;\n        }\n\n        internal static void DeserializeBeatmapData(IReadonlyBeatmapData beatmapData)\n        {\n            _noodleObjectDatas = new Dictionary<BeatmapObjectData, NoodleObjectData>();\n            foreach (BeatmapObjectData beatmapObjectData in beatmapData.beatmapObjectsData)\n            {\n                try\n                {\n                    NoodleObjectData noodleObjectData;\n\n                    Dictionary<string, object?> customData;\n\n                    switch (beatmapObjectData)\n                    {\n                        case CustomObstacleData customObstacleData:\n                            customData = customObstacleData.customData;\n                            noodleObjectData = ProcessCustomObstacle(customData);\n                            break;\n\n                        case CustomNoteData customNoteData:\n                            customData = customNoteData.customData;\n                            noodleObjectData = ProcessCustomNote(customData);\n                            break;\n\n                        case CustomWaypointData customWaypointData:\n                            customData = customWaypointData.customData;\n                            noodleObjectData = new NoodleObjectData();\n                            break;\n\n                        default:\n                            continue;\n                    }\n\n                    if (noodleObjectData != null)\n                    {\n                        FinalizeCustomObject(customData, noodleObjectData, beatmapData);\n                        _noodleObjectDatas.Add(beatmapObjectData, noodleObjectData);\n                    }\n                }\n                catch (Exception e)\n                {\n                    Plugin.Logger.Log($\"Could not create NoodleObjectData for object {beatmapObjectData.GetType().Name} at {beatmapObjectData.time}\", IPA.Logging.Logger.Level.Error);\n                    Plugin.Logger.Log(e, IPA.Logging.Logger.Level.Error);\n                }\n            }\n        }\n\n        private static void FinalizeCustomObject(Dictionary<string, object?> dynData, NoodleObjectData noodleObjectData, IReadonlyBeatmapData beatmapData)\n        {\n            object? rotation = dynData.Get<object>(ROTATION);\n            if (rotation != null)\n            {\n                if (rotation is List<object> list)\n                {\n                    IEnumerable<float> rot = list.Select(n => Convert.ToSingle(n));\n                    noodleObjectData.WorldRotationQuaternion = Quaternion.Euler(rot.ElementAt(0), rot.ElementAt(1), rot.ElementAt(2));\n                }\n                else\n                {\n                    noodleObjectData.WorldRotationQuaternion = Quaternion.Euler(0, Convert.ToSingle(rotation), 0);\n                }\n            }\n\n            IEnumerable<float>? localrot = dynData.Get<List<object>>(LOCALROTATION)?.Select(n => Convert.ToSingle(n));\n            if (localrot != null)\n            {\n                noodleObjectData.LocalRotationQuaternion = Quaternion.Euler(localrot.ElementAt(0), localrot.ElementAt(1), localrot.ElementAt(2));\n            }\n\n            noodleObjectData.Track = AnimationHelper.GetTrackArray(dynData, beatmapData);\n\n            Dictionary<string, object?>? animationObjectDyn = dynData.Get<Dictionary<string, object?>>(\"_animation\");\n            if (animationObjectDyn != null)\n            {\n                Dictionary<string, PointDefinition>? pointDefinitions = ((CustomBeatmapData)beatmapData).customData.Get<Dictionary<string, PointDefinition>>(\"pointDefinitions\")\n                    ?? throw new InvalidOperationException(\"Could not retrieve point definitions.\");\n                Animation.AnimationHelper.GetAllPointData(\n                    animationObjectDyn,\n                    pointDefinitions,\n                    out PointDefinition? localPosition,\n                    out PointDefinition? localRotation,\n                    out PointDefinition? localScale,\n                    out PointDefinition? localLocalRotation,\n                    out PointDefinition? localDissolve,\n                    out PointDefinition? localDissolveArrow,\n                    out PointDefinition? localCuttable,\n                    out PointDefinition? localDefinitePosition);\n                NoodleObjectData.AnimationObjectData animationObjectData = new NoodleObjectData.AnimationObjectData\n                {\n                    LocalPosition = localPosition,\n                    LocalRotation = localRotation,\n                    LocalScale = localScale,\n                    LocalLocalRotation = localLocalRotation,\n                    LocalDissolve = localDissolve,\n                    LocalDissolveArrow = localDissolveArrow,\n                    LocalCuttable = localCuttable,\n                    LocalDefinitePosition = localDefinitePosition,\n                };\n                noodleObjectData.AnimationObject = animationObjectData;\n            }\n\n            noodleObjectData.Cuttable = dynData.Get<bool?>(CUTTABLE);\n            noodleObjectData.Fake = dynData.Get<bool?>(FAKENOTE);\n\n            IEnumerable<float?>? position = dynData.GetNullableFloats(POSITION);\n            noodleObjectData.StartX = position?.ElementAtOrDefault(0);\n            noodleObjectData.StartY = position?.ElementAtOrDefault(1);\n\n            noodleObjectData.NJS = dynData.Get<float?>(NOTEJUMPSPEED);\n            noodleObjectData.SpawnOffset = dynData.Get<float?>(NOTESPAWNOFFSET);\n            noodleObjectData.AheadTimeInternal = dynData.Get<float?>(\"aheadTime\");\n        }\n\n        private static NoodleNoteData ProcessCustomNote(Dictionary<string, object?> dynData)\n        {\n            NoodleNoteData noodleNoteData = new NoodleNoteData();\n\n            float? cutDir = dynData.Get<float?>(CUTDIRECTION);\n            if (cutDir.HasValue)\n            {\n                noodleNoteData.CutQuaternion = Quaternion.Euler(0, 0, cutDir.Value);\n            }\n\n            noodleNoteData.FlipYSideInternal = dynData.Get<float?>(\"flipYSide\");\n            noodleNoteData.FlipLineIndexInternal = dynData.Get<float?>(\"flipLineIndex\");\n\n            noodleNoteData.StartNoteLineLayerInternal = dynData.Get<float?>(\"startNoteLineLayer\");\n\n            noodleNoteData.DisableGravity = dynData.Get<bool?>(NOTEGRAVITYDISABLE) ?? false;\n            noodleNoteData.DisableLook = dynData.Get<bool?>(NOTELOOKDISABLE) ?? false;\n\n            return noodleNoteData;\n        }\n\n        private static NoodleObstacleData ProcessCustomObstacle(Dictionary<string, object?> dynData)\n        {\n            NoodleObstacleData noodleObstacleData = new NoodleObstacleData();\n\n            IEnumerable<float?>? scale = dynData.GetNullableFloats(SCALE);\n            noodleObstacleData.Width = scale?.ElementAtOrDefault(0);\n            noodleObstacleData.Height = scale?.ElementAtOrDefault(1);\n            noodleObstacleData.Length = scale?.ElementAtOrDefault(2);\n\n            return noodleObstacleData;\n        }\n    }\n\n    internal record NoodleNoteData : NoodleObjectData\n    {\n        internal Quaternion? CutQuaternion { get; set; }\n\n        internal Vector3 MoveStartPos { get; set; }\n\n        internal Vector3 MoveEndPos { get; set; }\n\n        internal Vector3 JumpEndPos { get; set; }\n\n        internal float? FlipYSideInternal { get; set; }\n\n        internal float? FlipLineIndexInternal { get; set; }\n\n        internal float? StartNoteLineLayerInternal { get; set; }\n\n        internal bool DisableGravity { get; set; }\n\n        internal bool DisableLook { get; set; }\n\n        internal float EndRotation { get; set; }\n    }\n\n    internal record NoodleObstacleData : NoodleObjectData\n    {\n        internal Vector3 StartPos { get; set; }\n\n        internal Vector3 MidPos { get; set; }\n\n        internal Vector3 EndPos { get; set; }\n\n        internal Vector3 BoundsSize { get; set; }\n\n        internal float? Width { get; set; }\n\n        internal float? Height { get; set; }\n\n        internal float? Length { get; set; }\n\n        internal float XOffset { get; set; }\n\n        internal bool DoUnhide { get; set; }\n    }\n\n    internal record NoodleObjectData\n    {\n        internal Quaternion? WorldRotationQuaternion { get; set; }\n\n        internal Quaternion? LocalRotationQuaternion { get; set; }\n\n        internal IEnumerable<Track>? Track { get; set; }\n\n        internal Quaternion WorldRotation { get; set; }\n\n        internal Quaternion LocalRotation { get; set; }\n\n        internal AnimationObjectData? AnimationObject { get; set; }\n\n        internal Vector3 NoteOffset { get; set; }\n\n        internal bool? Cuttable { get; set; }\n\n        internal bool? Fake { get; set; }\n\n        internal float? StartX { get; set; }\n\n        internal float? StartY { get; set; }\n\n        internal float? NJS { get; set; }\n\n        internal float? SpawnOffset { get; set; }\n\n        internal float? AheadTimeInternal { get; set; }\n\n        internal record AnimationObjectData\n        {\n            internal PointDefinition? LocalPosition { get; set; }\n\n            internal PointDefinition? LocalRotation { get; set; }\n\n            internal PointDefinition? LocalScale { get; set; }\n\n            internal PointDefinition? LocalLocalRotation { get; set; }\n\n            internal PointDefinition? LocalDissolve { get; set; }\n\n            internal PointDefinition? LocalDissolveArrow { get; set; }\n\n            internal PointDefinition? LocalCuttable { get; set; }\n\n            internal PointDefinition? LocalDefinitePosition { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/Plugin.cs",
    "content": "﻿namespace NoodleExtensions\n{\n    using System.Reflection;\n    using HarmonyLib;\n    using Heck;\n    using IPA;\n    using IPALogger = IPA.Logging.Logger;\n\n    [Plugin(RuntimeOptions.DynamicInit)]\n    internal class Plugin\n    {\n        internal const string CAPABILITY = \"Noodle Extensions\";\n        internal const string HARMONYIDCORE = \"com.aeroluna.BeatSaber.NoodleExtensionsCore\";\n        internal const string HARMONYID = \"com.aeroluna.BeatSaber.NoodleExtensions\";\n\n        internal const string CUTDIRECTION = \"_cutDirection\";\n        internal const string CUTTABLE = \"_interactable\";\n        internal const string DEFINITEPOSITION = \"_definitePosition\";\n        internal const string DISSOLVE = \"_dissolve\";\n        internal const string DISSOLVEARROW = \"_dissolveArrow\";\n        internal const string FAKENOTE = \"_fake\";\n        internal const string FLIP = \"_flip\";\n        internal const string LOCALROTATION = \"_localRotation\";\n        internal const string NOTEGRAVITYDISABLE = \"_disableNoteGravity\";\n        internal const string NOTEJUMPSPEED = \"_noteJumpMovementSpeed\";\n        internal const string NOTELOOKDISABLE = \"_disableNoteLook\";\n        internal const string NOTESPAWNOFFSET = \"_noteJumpStartBeatOffset\";\n        internal const string POSITION = \"_position\";\n        internal const string ROTATION = \"_rotation\";\n        internal const string SCALE = \"_scale\";\n        internal const string TIME = \"_time\";\n        internal const string TRACK = \"_track\";\n\n        internal const string ASSIGNPLAYERTOTRACK = \"AssignPlayerToTrack\";\n        internal const string ASSIGNTRACKPARENT = \"AssignTrackParent\";\n\n        internal static readonly Harmony _harmonyInstanceCore = new Harmony(HARMONYIDCORE);\n        internal static readonly Harmony _harmonyInstance = new Harmony(HARMONYID);\n\n#pragma warning disable CS8618\n        internal static HeckLogger Logger { get; private set; }\n#pragma warning restore CS8618\n\n        [Init]\n        public void Init(IPALogger pluginLogger)\n        {\n            Logger = new HeckLogger(pluginLogger);\n            HeckData.InitPatches(_harmonyInstance, Assembly.GetExecutingAssembly());\n\n            Heck.Animation.TrackBuilder.TrackManagerCreated += Animation.AssignPlayerToTrack.OnTrackManagerCreated;\n            Heck.Animation.TrackBuilder.TrackManagerCreated += Animation.AssignTrackParent.OnTrackManagerCreated;\n            Heck.Animation.TrackBuilder.TrackCreated += Animation.AnimationHelper.OnTrackCreated;\n        }\n\n        [OnEnable]\n        public void OnEnable()\n        {\n            SongCore.Collections.RegisterCapability(CAPABILITY);\n            _harmonyInstanceCore.PatchAll(Assembly.GetExecutingAssembly());\n        }\n\n        [OnDisable]\n        public void OnDisable()\n        {\n            SongCore.Collections.DeregisterizeCapability(CAPABILITY);\n            _harmonyInstanceCore.UnpatchAll(HARMONYIDCORE);\n            _harmonyInstanceCore.UnpatchAll(HARMONYID);\n        }\n    }\n}\n"
  },
  {
    "path": "NoodleExtensions/manifest.json",
    "content": "﻿{\n  \"$schema\": \"https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json\",\n  \"author\": \"Aeroluna\",\n  \"description\": \"This mod lets you see cool things that mappers have put in their maps. Report all issues to Reaxt.\",\n  \"gameVersion\": \"1.18.0\",\n  \"id\": \"NoodleExtensions\",\n  \"name\": \"NoodleExtensions\",\n  \"version\": \"1.4.4\",\n  \"dependsOn\": {\n    \"_Heck\": \"^1.3.0\",\n    \"SongCore\": \"^3.8.0\",\n    \"CustomJSONData\": \"^2.0.6\",\n    \"BSIPA\": \"^4.2.0\"\n  }\n}\n"
  },
  {
    "path": "NoodleExtensions.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29025.244\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NoodleExtensions\", \"NoodleExtensions\\NoodleExtensions.csproj\", \"{6F22A5D9-D726-490F-A46C-B89CA4D76B41}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{C716C2DD-39D3-4AC0-8B2D-E129E72F4828}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t.editorconfig = .editorconfig\n\t\tREADME.md = README.md\n\tEndProjectSection\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Documentation\", \"Documentation\", \"{3E8D1134-B4CE-458C-A21F-2016636840FF}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tDocumentation\\AnimationDocs.md = Documentation\\AnimationDocs.md\n\tEndProjectSection\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"examples\", \"examples\", \"{47ABF7CC-E146-49EA-8001-9236C121345F}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"media\", \"media\", \"{78E1F3FF-36CF-4ECF-94D1-4011141FF22C}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tDocumentation\\media\\ColorAnimateTrack.gif = Documentation\\media\\ColorAnimateTrack.gif\n\t\tDocumentation\\media\\ColorAssignPath.gif = Documentation\\media\\ColorAssignPath.gif\n\t\tDocumentation\\media\\DefinitePositionAnimateTrack.gif = Documentation\\media\\DefinitePositionAnimateTrack.gif\n\t\tDocumentation\\media\\DissolveAnimateTrack.gif = Documentation\\media\\DissolveAnimateTrack.gif\n\t\tDocumentation\\media\\DissolveArrowAnimateTrack.gif = Documentation\\media\\DissolveArrowAnimateTrack.gif\n\t\tDocumentation\\media\\DissolveArrowAssignPath.gif = Documentation\\media\\DissolveArrowAssignPath.gif\n\t\tDocumentation\\media\\DissolveAssignPath.gif = Documentation\\media\\DissolveAssignPath.gif\n\t\tDocumentation\\media\\LocalRotationAnimateTrack.gif = Documentation\\media\\LocalRotationAnimateTrack.gif\n\t\tDocumentation\\media\\LocalRotationAssignPath.gif = Documentation\\media\\LocalRotationAssignPath.gif\n\t\tDocumentation\\media\\PositionAnimateTrack1.gif = Documentation\\media\\PositionAnimateTrack1.gif\n\t\tDocumentation\\media\\PositionAssignPath.gif = Documentation\\media\\PositionAssignPath.gif\n\t\tDocumentation\\media\\RotationAnimateTrack.gif = Documentation\\media\\RotationAnimateTrack.gif\n\t\tDocumentation\\media\\RotationAssignPath.gif = Documentation\\media\\RotationAssignPath.gif\n\t\tDocumentation\\media\\TimeIsDumb.gif = Documentation\\media\\TimeIsDumb.gif\n\tEndProjectSection\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"documentationMap\", \"documentationMap\", \"{4DDC5B47-1E50-463F-8C15-67CD182B795D}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tDocumentation\\examples\\documentationMap\\cat.ogg = Documentation\\examples\\documentationMap\\cat.ogg\n\t\tDocumentation\\examples\\documentationMap\\count.txt = Documentation\\examples\\documentationMap\\count.txt\n\t\tDocumentation\\examples\\documentationMap\\demo.js = Documentation\\examples\\documentationMap\\demo.js\n\t\tDocumentation\\examples\\documentationMap\\ExpertPlusStandard.dat = Documentation\\examples\\documentationMap\\ExpertPlusStandard.dat\n\t\tDocumentation\\examples\\documentationMap\\ExpertStandard.dat = Documentation\\examples\\documentationMap\\ExpertStandard.dat\n\t\tDocumentation\\examples\\documentationMap\\Info.dat = Documentation\\examples\\documentationMap\\Info.dat\n\t\tDocumentation\\examples\\documentationMap\\README.md = Documentation\\examples\\documentationMap\\README.md\n\tEndProjectSection\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{6F22A5D9-D726-490F-A46C-B89CA4D76B41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{6F22A5D9-D726-490F-A46C-B89CA4D76B41}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{6F22A5D9-D726-490F-A46C-B89CA4D76B41}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{6F22A5D9-D726-490F-A46C-B89CA4D76B41}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{3E8D1134-B4CE-458C-A21F-2016636840FF} = {C716C2DD-39D3-4AC0-8B2D-E129E72F4828}\n\t\t{47ABF7CC-E146-49EA-8001-9236C121345F} = {3E8D1134-B4CE-458C-A21F-2016636840FF}\n\t\t{78E1F3FF-36CF-4ECF-94D1-4011141FF22C} = {3E8D1134-B4CE-458C-A21F-2016636840FF}\n\t\t{4DDC5B47-1E50-463F-8C15-67CD182B795D} = {47ABF7CC-E146-49EA-8001-9236C121345F}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {C9ABA060-E44A-4177-B81E-E3E3784930C2}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "README.md",
    "content": "**Noodle Extensions is now maintained in the [Aeroluna/Heck](https://github.com/Aeroluna/Heck) repository.**\n\n----\n\n# NoodleExtensions\n\nThis adds a host of new things you can do with your maps.\n\nMAPPERS: EITHER USE MAPPING EXTENSIONS OR NOODLE EXTENSIONS, DO NOT USE BOTH AT THE SAME TIME. Noodle Extensions is meant to completely replace Mapping Extensions, as they both do the same thing. Having both requirements can break some features.\n\nNOODLE EXTENSIONS WILL NOT READ MAPS THAT USE MAPPING EXTENSIONS. YOU HAVE TO INSTALL MAPPING EXTENSIONS FOR THOSE. (You can have both of the mods installed at once)\n\nDocumentation: https://github.com/Aeroluna/Heck/wiki\n"
  }
]