├── .gitattributes ├── .gitignore ├── LICENSE ├── MappingExtensions.sln ├── MappingExtensions ├── Directory.Build.props ├── HarmonyPatches │ ├── 360DegreeNoteRotationPatches.cs │ ├── AdditionalWallAdjustementsPatches.cs │ ├── ExtraLanesAndLayersPatches.cs │ ├── MirrorPatches.cs │ └── PrecisePlacementPatches.cs ├── MappingExtensions.csproj ├── Plugin.cs └── manifest.json ├── README-Beatmapv2.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kyle1413 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MappingExtensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2042 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MappingExtensions", "MappingExtensions\MappingExtensions.csproj", "{5B7668CC-078D-4032-A704-EB0991B72B91}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5B7668CC-078D-4032-A704-EB0991B72B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5B7668CC-078D-4032-A704-EB0991B72B91}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5B7668CC-078D-4032-A704-EB0991B72B91}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5B7668CC-078D-4032-A704-EB0991B72B91}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {24087328-83DA-4B8A-B237-49C36E55E6C6} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /MappingExtensions/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | BSIPA 6 | 7 | -------------------------------------------------------------------------------- /MappingExtensions/HarmonyPatches/360DegreeNoteRotationPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection.Emit; 5 | using HarmonyLib; 6 | using SongCore.UI; 7 | using SongCore.Utilities; 8 | using UnityEngine; 9 | 10 | namespace MappingExtensions.HarmonyPatches 11 | { 12 | [HarmonyPatch(typeof(SliderMeshController), nameof(SliderMeshController.CutDirectionToControlPointPosition))] 13 | internal static class SliderMeshControllerCutDirectionToControlPointPositionPatch 14 | { 15 | private static void Postfix(ref Vector3 __result, NoteCutDirection noteCutDirection) 16 | { 17 | if (!Plugin.active) 18 | { 19 | return; 20 | } 21 | 22 | var direction = (int)noteCutDirection; 23 | if (direction is >= 1000 and <= 1360 or >= 2000 and <= 2360) 24 | { 25 | __result = noteCutDirection.Rotation() * Vector3.down; 26 | } 27 | } 28 | } 29 | 30 | [HarmonyPatch(typeof(NoteCutDirectionExtensions), nameof(NoteCutDirectionExtensions.Direction))] 31 | internal static class NoteCutDirectionExtensionsDirectionPatch 32 | { 33 | private static void Postfix(ref Vector2 __result, NoteCutDirection cutDirection) 34 | { 35 | if (!Plugin.active) 36 | { 37 | return; 38 | } 39 | 40 | var direction = (int)cutDirection; 41 | if (direction is >= 1000 and <= 1360 or >= 2000 and <= 2360) 42 | { 43 | var newDirection = cutDirection.Rotation() * Vector3.down; 44 | __result = new Vector2(newDirection.x, newDirection.y); 45 | } 46 | } 47 | } 48 | 49 | [HarmonyPatch(typeof(NoteCutDirectionExtensions), nameof(NoteCutDirectionExtensions.RotationAngle))] 50 | internal static class NoteCutDirectionExtensionsRotationAnglePatch 51 | { 52 | private static void Postfix(ref float __result, NoteCutDirection cutDirection) 53 | { 54 | if (!Plugin.active) 55 | { 56 | return; 57 | } 58 | 59 | var direction = (int)cutDirection; 60 | if (direction is >= 1000 and <= 1360) 61 | { 62 | __result = 1000 - direction; 63 | } 64 | else if (direction is >= 2000 and <= 2360) 65 | { 66 | __result = 2000 - direction; 67 | } 68 | } 69 | } 70 | 71 | [HarmonyPatch(typeof(ColorNoteVisuals), nameof(ColorNoteVisuals.HandleNoteControllerDidInit))] 72 | internal static class ColorNoteVisualsHandleNoteControllerDidInitPatch 73 | { 74 | private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator il) 75 | { 76 | // Converts normal notes to arrowless notes when using a certain rotation range. 77 | return new CodeMatcher(instructions, il) 78 | .MatchEndForward( 79 | new CodeMatch(OpCodes.Bne_Un), 80 | new CodeMatch(OpCodes.Ldarg_0)) 81 | .ThrowIfInvalid() 82 | .CreateLabel(out var destination) 83 | .MatchStartBackwards(new CodeMatch(OpCodes.Bne_Un)) 84 | .InsertAndAdvance( 85 | new CodeInstruction(OpCodes.Beq, destination), 86 | new CodeInstruction(OpCodes.Ldloc_0), 87 | new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(NoteData), nameof(NoteData.cutDirection))), 88 | Transpilers.EmitDelegate>(cutDirection => Plugin.active && (int)cutDirection is >= 2000 and <= 2360)) 89 | .SetOpcodeAndAdvance(OpCodes.Brfalse) 90 | .InstructionEnumeration(); 91 | } 92 | } 93 | 94 | [HarmonyPatch(typeof(BeatmapTypeConverters), nameof(BeatmapTypeConverters.ConvertNoteCutDirection))] 95 | internal static class BeatmapTypeConvertersConvertNoteCutDirectionPatch 96 | { 97 | private static void Postfix(ref NoteCutDirection __result, BeatmapSaveDataCommon.NoteCutDirection noteCutDirection) 98 | { 99 | // This happens in menu, so we can't rely on Plugin.Active. 100 | if (RequirementsUI.instance.diffData == null || !RequirementsUI.instance.diffData.additionalDifficultyData._requirements.Any(r => r.StartsWith("Mapping Extensions", StringComparison.Ordinal))) 101 | { 102 | return; 103 | } 104 | 105 | var direction = (int)noteCutDirection; 106 | if (direction is >= 1000 and <= 1360 or >= 2000 and <= 2360) 107 | { 108 | __result = (NoteCutDirection)direction; 109 | } 110 | } 111 | } 112 | 113 | [HarmonyPatch(typeof(NoteBasicCutInfoHelper), nameof(NoteBasicCutInfoHelper.GetBasicCutInfo))] 114 | internal static class NoteBasicCutInfoHelperGetBasicCutInfoPatch 115 | { 116 | private static void Prefix(ref NoteCutDirection cutDirection) 117 | { 118 | if (!Plugin.active) 119 | { 120 | return; 121 | } 122 | 123 | if ((int)cutDirection is >= 2000 and <= 2360) 124 | { 125 | cutDirection = NoteCutDirection.Any; 126 | } 127 | } 128 | } 129 | 130 | [HarmonyPatch(typeof(RotationTimeProcessor), nameof(RotationTimeProcessor.SpawnRotationForEventValue))] 131 | internal static class RotationTimeProcessorSpawnRotationForEventValuePatch 132 | { 133 | private static void Postfix(ref int __result, int index) 134 | { 135 | if (BS_Utils.Plugin.LevelData.IsSet && !BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData.beatmapKey.beatmapCharacteristic.requires360Movement) 136 | { 137 | return; 138 | } 139 | 140 | if (index is >= 1000 and <= 1720) 141 | { 142 | __result = index - 1360; 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /MappingExtensions/HarmonyPatches/AdditionalWallAdjustementsPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | using HarmonyLib; 6 | using SongCore.Utilities; 7 | 8 | namespace MappingExtensions.HarmonyPatches 9 | { 10 | [HarmonyPatch(typeof(BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter), nameof(BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter.GetHeightForObstacleType))] 11 | internal static class BeatmapDataLoaderObstacleConverterGetHeightForObstacleTypePatch 12 | { 13 | private enum Mode 14 | { 15 | PreciseHeight, 16 | PreciseHeightStart 17 | } 18 | 19 | private static void Postfix(ref int __result, BeatmapSaveDataVersion2_6_0AndEarlier.ObstacleType obstacleType) 20 | { 21 | var type = (int)obstacleType; 22 | 23 | if (type is < 1000 or > 4005000) 24 | { 25 | return; 26 | } 27 | 28 | int obsHeight; 29 | 30 | var mode = type is >= 4001 and <= 4005000 ? Mode.PreciseHeightStart : Mode.PreciseHeight; 31 | if (mode == Mode.PreciseHeightStart) 32 | { 33 | type -= 4001; 34 | obsHeight = type / 1000; 35 | } 36 | else 37 | { 38 | obsHeight = type - 1000; 39 | } 40 | 41 | __result = (int)(obsHeight / 1000f * 5 * 1000 + 1000); 42 | } 43 | } 44 | 45 | [HarmonyPatch(typeof(BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter), nameof(BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter.GetLayerForObstacleType))] 46 | internal static class BeatmapDataLoaderObstacleConverterGetLayerForObstacleTypePatch 47 | { 48 | private enum Mode 49 | { 50 | PreciseHeight, 51 | PreciseHeightStart 52 | } 53 | 54 | private static void Postfix(ref int __result, BeatmapSaveDataVersion2_6_0AndEarlier.ObstacleType obstacleType) 55 | { 56 | var type = (int)obstacleType; 57 | 58 | if (type is < 1000 or > 4005000) 59 | { 60 | return; 61 | } 62 | 63 | var startHeight = 0; 64 | 65 | var mode = type is >= 4001 and <= 4005000 ? Mode.PreciseHeightStart : Mode.PreciseHeight; 66 | if (mode == Mode.PreciseHeightStart) 67 | { 68 | type -= 4001; 69 | startHeight = type % 1000; 70 | } 71 | 72 | // Math that is accurate in shape/proportions but has walls being too high. 73 | __result = (int)(startHeight / 750f * 5 * 1000 + 1334); 74 | } 75 | } 76 | 77 | [HarmonyPatch(typeof(BeatmapObjectSpawnMovementData), nameof(BeatmapObjectSpawnMovementData.GetObstacleSpawnData))] 78 | internal static class BeatmapObjectSpawnMovementDataGetObstacleSpawnDataPatch 79 | { 80 | private static IEnumerable Transpiler(IEnumerable instructions) 81 | { 82 | // Replaces the obstacle width. 83 | return new CodeMatcher(instructions) 84 | .MatchEndForward( 85 | new CodeMatch(i => i.opcode == OpCodes.Callvirt && ((MethodBase)i.operand).Name == $"get_{nameof(ObstacleController.width)}"), 86 | new CodeMatch(OpCodes.Conv_R4), 87 | new CodeMatch()) 88 | .ThrowIfInvalid() 89 | .Insert(Transpilers.EmitDelegate>(obstacleWidth => 90 | { 91 | if (!Plugin.active || obstacleWidth is < 1000 and > -1000) 92 | { 93 | return obstacleWidth; 94 | } 95 | 96 | if (obstacleWidth <= -1000) 97 | { 98 | obstacleWidth += 2000; 99 | } 100 | 101 | return (obstacleWidth - 1000) / 1000; 102 | })) 103 | .InstructionEnumeration(); 104 | } 105 | } 106 | 107 | [HarmonyPatch(typeof(ObstacleController), nameof(ObstacleController.Init))] 108 | internal static class ObstacleControllerInitPatch 109 | { 110 | private static void Prefix(ObstacleData obstacleData, ref ObstacleSpawnData obstacleSpawnData) 111 | { 112 | if (!Plugin.active) 113 | { 114 | return; 115 | } 116 | 117 | var obstacleHeight = obstacleSpawnData.obstacleHeight; 118 | 119 | var height = (float)obstacleData.height; 120 | switch (height) 121 | { 122 | case <= -1000: 123 | obstacleHeight = (height + 2000) / 1000; 124 | break; 125 | case >= 1000: 126 | obstacleHeight = (height - 1000) / 1000; 127 | break; 128 | case > 2: 129 | obstacleHeight = height; 130 | break; 131 | } 132 | 133 | obstacleHeight *= StaticBeatmapObjectSpawnMovementData.layerHeight; 134 | 135 | obstacleSpawnData = new ObstacleSpawnData(obstacleSpawnData.moveOffset, obstacleSpawnData.obstacleWidth, obstacleHeight); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /MappingExtensions/HarmonyPatches/ExtraLanesAndLayersPatches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection.Emit; 5 | using HarmonyLib; 6 | using SongCore.Utilities; 7 | 8 | namespace MappingExtensions.HarmonyPatches 9 | { 10 | [HarmonyPatch(typeof(BeatmapDataLoaderVersion3.BeatmapDataLoader.ObstacleConverter), nameof(BeatmapDataLoaderVersion3.BeatmapDataLoader.ObstacleConverter.GetNoteLineLayer))] 11 | internal static class BeatmapDataLoaderObstacleConverterGetNoteLineLayerPatch 12 | { 13 | private static void Postfix(ref NoteLineLayer __result, int lineLayer) 14 | { 15 | if (lineLayer > 2) 16 | { 17 | __result = (NoteLineLayer)lineLayer; 18 | } 19 | } 20 | } 21 | 22 | [HarmonyPatch(typeof(BeatmapTypeConverters), nameof(BeatmapTypeConverters.ConvertNoteLineLayer), typeof(int))] 23 | internal static class BeatmapTypeConvertersConvertNoteLineLayerPatch 24 | { 25 | private static void Postfix(ref NoteLineLayer __result, int layer) 26 | { 27 | if (layer is > 2 or < 0) 28 | { 29 | __result = (NoteLineLayer)layer; 30 | } 31 | } 32 | } 33 | 34 | [HarmonyPatch(typeof(BeatmapTypeConverters), nameof(BeatmapTypeConverters.ConvertNoteLineLayer), typeof(BeatmapSaveDataCommon.NoteLineLayer))] 35 | internal static class BeatmapTypeConvertersConvertNoteLineLayerPatch2 36 | { 37 | private static void Postfix(ref NoteLineLayer __result, BeatmapSaveDataCommon.NoteLineLayer layer) 38 | { 39 | if ((int)layer is > 2 or < 0) 40 | { 41 | __result = (NoteLineLayer)layer; 42 | } 43 | } 44 | } 45 | 46 | [HarmonyPatch(typeof(BeatmapObjectsInTimeRowProcessor), nameof(BeatmapObjectsInTimeRowProcessor.HandleCurrentTimeSliceAllNotesAndSlidersDidFinishTimeSlice))] 47 | internal static class BeatmapObjectsInTimeRowProcessorHandleCurrentTimeSliceAllNotesAndSlidersDidFinishTimeSlicePatch 48 | { 49 | private static IEnumerable Transpiler(IEnumerable instructions) 50 | { 51 | // Prevents an IndexOutOfRangeException when processing precise line indexes. 52 | return new CodeMatcher(instructions) 53 | .MatchEndForward( 54 | new CodeMatch(OpCodes.Ldloc_S), 55 | new CodeMatch(OpCodes.Callvirt), 56 | new CodeMatch(OpCodes.Ldelem_Ref)) 57 | .ThrowIfInvalid() 58 | .Insert( 59 | new CodeInstruction(OpCodes.Ldc_I4_0), 60 | new CodeInstruction(OpCodes.Ldc_I4_3), 61 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Clamp), new[] { typeof(int), typeof(int), typeof(int) }))) 62 | .InstructionEnumeration(); 63 | } 64 | 65 | // TODO: Make this less compiler-generated garbage. 66 | private static void Postfix(BeatmapObjectsInTimeRowProcessor.TimeSliceContainer allObjectsTimeSlice) 67 | { 68 | IEnumerable enumerable = allObjectsTimeSlice.items.OfType(); 69 | if (!enumerable.Any(x => x.lineIndex is > 3 or < 0)) 70 | { 71 | return; 72 | } 73 | IEnumerable enumerable2 = allObjectsTimeSlice.items.OfType(); 74 | IEnumerable enumerable3 = allObjectsTimeSlice.items.OfType(); 75 | Dictionary> notesInColumnsProcessingDictionaryOfLists = new Dictionary>(); 76 | foreach (NoteData noteData in enumerable) 77 | { 78 | if (!notesInColumnsProcessingDictionaryOfLists.ContainsKey(noteData.lineIndex)) 79 | { 80 | notesInColumnsProcessingDictionaryOfLists[noteData.lineIndex] = new List(3); 81 | } 82 | List list = notesInColumnsProcessingDictionaryOfLists[noteData.lineIndex]; 83 | bool flag = false; 84 | for (int i = 0; i < list.Count; i++) 85 | { 86 | if (list[i].noteLineLayer > noteData.noteLineLayer) 87 | { 88 | list.Insert(i, noteData); 89 | flag = true; 90 | break; 91 | } 92 | } 93 | if (!flag) 94 | { 95 | list.Add(noteData); 96 | } 97 | } 98 | foreach (List list2 in notesInColumnsProcessingDictionaryOfLists.Values) 99 | { 100 | for (int j = 0; j < list2.Count; j++) 101 | { 102 | list2[j].SetBeforeJumpNoteLineLayer((NoteLineLayer)j); 103 | } 104 | } 105 | foreach (SliderData sliderData in enumerable2) 106 | { 107 | foreach (NoteData noteData2 in enumerable) 108 | { 109 | if (BeatmapObjectsInTimeRowProcessor.SliderHeadPositionOverlapsWithNote(sliderData, noteData2)) 110 | { 111 | sliderData.SetHeadBeforeJumpLineLayer(noteData2.beforeJumpNoteLineLayer); 112 | } 113 | } 114 | } 115 | foreach (SliderData sliderData2 in enumerable2) 116 | { 117 | foreach (SliderData sliderData3 in enumerable2) 118 | { 119 | if (sliderData2 != sliderData3 && BeatmapObjectsInTimeRowProcessor.SliderHeadPositionOverlapsWithBurstTail(sliderData2, sliderData3)) 120 | { 121 | sliderData2.SetHeadBeforeJumpLineLayer(sliderData3.tailBeforeJumpLineLayer); 122 | } 123 | } 124 | foreach (BeatmapObjectsInTimeRowProcessor.SliderTailData sliderTailData in enumerable3) 125 | { 126 | if (BeatmapObjectsInTimeRowProcessor.SliderHeadPositionOverlapsWithBurstTail(sliderData2, sliderTailData.slider)) 127 | { 128 | sliderData2.SetHeadBeforeJumpLineLayer(sliderTailData.slider.tailBeforeJumpLineLayer); 129 | } 130 | } 131 | } 132 | foreach (BeatmapObjectsInTimeRowProcessor.SliderTailData sliderTailData2 in enumerable3) 133 | { 134 | SliderData slider = sliderTailData2.slider; 135 | foreach (NoteData noteData3 in enumerable) 136 | { 137 | if (BeatmapObjectsInTimeRowProcessor.SliderTailPositionOverlapsWithNote(slider, noteData3)) 138 | { 139 | slider.SetTailBeforeJumpLineLayer(noteData3.beforeJumpNoteLineLayer); 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | [HarmonyPatch(typeof(NoteJump), nameof(NoteJump.Init))] 147 | internal static class NoteJumpInitPatch 148 | { 149 | private static IEnumerable Transpiler(IEnumerable instructions) 150 | { 151 | // Prevents an IndexOutOfRangeException when using negative line indexes/layers. 152 | return new CodeMatcher(instructions) 153 | .MatchStartForward( 154 | new CodeMatch(OpCodes.Ldelem), 155 | new CodeMatch(OpCodes.Ldc_R4), 156 | new CodeMatch(OpCodes.Call)) 157 | .ThrowIfInvalid() 158 | .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Abs), new[] { typeof(int) }))) 159 | .InstructionEnumeration(); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /MappingExtensions/HarmonyPatches/MirrorPatches.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | 3 | namespace MappingExtensions.HarmonyPatches 4 | { 5 | [HarmonyPatch(typeof(NoteData), nameof(NoteData.Mirror))] 6 | internal static class NoteDataMirrorPatch 7 | { 8 | private static void Prefix(NoteData __instance, out (int, int) __state) 9 | { 10 | __state = (__instance.lineIndex, __instance.flipLineIndex); 11 | } 12 | 13 | private static void Postfix(NoteData __instance, (int, int) __state) 14 | { 15 | var lineIndex = __state.Item1; 16 | var flipLineIndex = __state.Item2; 17 | 18 | if (lineIndex is > 3 or < 0) 19 | { 20 | switch (lineIndex) 21 | { 22 | case >= 1000 or <= -1000: 23 | { 24 | var leftSide = false; 25 | 26 | if (lineIndex <= -1000) 27 | { 28 | lineIndex += 2000; 29 | } 30 | 31 | if (lineIndex >= 4000) 32 | { 33 | leftSide = true; 34 | } 35 | 36 | lineIndex = 5000 - lineIndex; 37 | 38 | if (leftSide) 39 | { 40 | lineIndex -= 2000; 41 | } 42 | 43 | __instance.lineIndex = lineIndex; 44 | break; 45 | } 46 | case > 3: 47 | { 48 | // TODO: Find better naming for that variable. 49 | var diff = (lineIndex - 3) * 2; 50 | var newLaneCount = 4 + diff; 51 | __instance.lineIndex = newLaneCount - diff - 1 - lineIndex; 52 | break; 53 | } 54 | case < 0: 55 | { 56 | // TODO: Find better naming for that variable. 57 | var diff = (0 - lineIndex) * 2; 58 | var newLaneCount = 4 + diff; 59 | __instance.lineIndex = newLaneCount - diff - 1 - lineIndex; 60 | break; 61 | } 62 | } 63 | } 64 | 65 | if (flipLineIndex is > 3 or < 0) 66 | { 67 | switch (flipLineIndex) 68 | { 69 | case >= 1000 or <= -1000: 70 | { 71 | var leftSide = false; 72 | 73 | if (flipLineIndex <= -1000) 74 | { 75 | flipLineIndex += 2000; 76 | } 77 | 78 | if (flipLineIndex >= 4000) 79 | { 80 | leftSide = true; 81 | } 82 | 83 | flipLineIndex = 5000 - flipLineIndex; 84 | 85 | if (leftSide) 86 | { 87 | flipLineIndex -= 2000; 88 | } 89 | 90 | __instance.flipLineIndex = flipLineIndex; 91 | break; 92 | } 93 | case > 3: 94 | { 95 | // TODO: Find better naming for that variable. 96 | var diff = (flipLineIndex - 3) * 2; 97 | var newLaneCount = 4 + diff; 98 | __instance.flipLineIndex = newLaneCount - diff - 1 - flipLineIndex; 99 | break; 100 | } 101 | case < 0: 102 | { 103 | // TODO: Find better naming for that variable. 104 | var diff = (0 - flipLineIndex) * 2; 105 | var newLaneCount = 4 + diff; 106 | __instance.flipLineIndex = newLaneCount - diff - 1 - flipLineIndex; 107 | break; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | [HarmonyPatch(typeof(ObstacleData), nameof(ObstacleData.Mirror))] 115 | internal static class ObstacleDataMirrorPatch 116 | { 117 | private static void Prefix(ObstacleData __instance, out int __state) 118 | { 119 | __state = __instance.lineIndex; 120 | } 121 | 122 | private static void Postfix(ObstacleData __instance, int __state) 123 | { 124 | var lineIndex = __state; 125 | var obstacleWidth = __instance.width; 126 | var precisionWidth = obstacleWidth is >= 1000 or <= -1000; 127 | 128 | if (lineIndex is <= 3 and >= 0 && !precisionWidth) 129 | { 130 | return; 131 | } 132 | 133 | if (lineIndex is >= 1000 or <= -1000 || precisionWidth) // precision lineIndex 134 | { 135 | switch (lineIndex) 136 | { 137 | case <= -1000: // normalize index values, we'll fix them later 138 | lineIndex += 1000; 139 | break; 140 | case >= 1000: 141 | lineIndex += -1000; 142 | break; 143 | default: 144 | lineIndex *= 1000; // convert lineIndex to precision if not already 145 | break; 146 | } 147 | 148 | lineIndex = (lineIndex - 2000) * -1 + 2000; //flip lineIndex 149 | 150 | if (obstacleWidth is < 1000 and > -1000) // normalize wall width 151 | { 152 | obstacleWidth *= 1000; 153 | } 154 | else 155 | { 156 | if (obstacleWidth >= 1000) 157 | { 158 | obstacleWidth -= 1000; 159 | } 160 | 161 | if (obstacleWidth <= -1000) 162 | { 163 | obstacleWidth += 1000; 164 | } 165 | } 166 | 167 | lineIndex -= obstacleWidth; 168 | 169 | if (lineIndex < 0) // this is where we fix them 170 | { 171 | lineIndex -= 1000; 172 | } 173 | else 174 | { 175 | lineIndex += 1000; 176 | } 177 | 178 | __instance.lineIndex = lineIndex; 179 | } 180 | else // lineIndex > -1000 || lineIndex < 1000 assumes no precision width 181 | { 182 | var mirrorLane = (lineIndex - 2) * -1 + 2; // flip lineIndex 183 | __instance.lineIndex = mirrorLane - obstacleWidth; // adjust for wall width 184 | } 185 | } 186 | } 187 | 188 | [HarmonyPatch(typeof(SliderData), nameof(SliderData.Mirror))] 189 | internal static class SliderDataMirrorPatch 190 | { 191 | private static void Prefix(SliderData __instance, out (int, int) __state) 192 | { 193 | // Directional mirroring of the head and tail are handled properly by NoteCutDirection.Mirrored. 194 | __state = (__instance.headLineIndex, __instance.tailLineIndex); 195 | } 196 | 197 | private static void Postfix(SliderData __instance, (int, int) __state) 198 | { 199 | var headLineIndex = __state.Item1; 200 | var tailLineIndex = __state.Item2; 201 | 202 | if (headLineIndex is > 3 or < 0) 203 | { 204 | switch (headLineIndex) 205 | { 206 | case >= 1000 or <= -1000: 207 | { 208 | var leftSide = false; 209 | 210 | if (headLineIndex <= -1000) 211 | { 212 | headLineIndex += 2000; 213 | } 214 | 215 | if (headLineIndex >= 4000) 216 | { 217 | leftSide = true; 218 | } 219 | 220 | headLineIndex = 5000 - headLineIndex; 221 | 222 | if (leftSide) 223 | { 224 | headLineIndex -= 2000; 225 | } 226 | 227 | __instance.headLineIndex = headLineIndex; 228 | break; 229 | } 230 | case > 3: 231 | { 232 | // TODO: Find better naming for that variable. 233 | var diff = (headLineIndex - 3) * 2; 234 | var newLaneCount = 4 + diff; 235 | __instance.headLineIndex = newLaneCount - diff - 1 - headLineIndex; 236 | break; 237 | } 238 | case < 0: 239 | { 240 | // TODO: Find better naming for that variable. 241 | var diff = (0 - headLineIndex) * 2; 242 | var newLaneCount = 4 + diff; 243 | __instance.headLineIndex = newLaneCount - diff - 1 - headLineIndex; 244 | break; 245 | } 246 | } 247 | } 248 | 249 | if (tailLineIndex is > 3 or < 0) 250 | { 251 | switch (tailLineIndex) 252 | { 253 | case >= 1000 or <= -1000: 254 | { 255 | var leftSide = false; 256 | 257 | if (tailLineIndex <= -1000) 258 | { 259 | tailLineIndex += 2000; 260 | } 261 | 262 | if (tailLineIndex >= 4000) 263 | { 264 | leftSide = true; 265 | } 266 | 267 | tailLineIndex = 5000 - tailLineIndex; 268 | 269 | if (leftSide) 270 | { 271 | tailLineIndex -= 2000; 272 | } 273 | 274 | __instance.tailLineIndex = tailLineIndex; 275 | break; 276 | } 277 | case > 3: 278 | { 279 | // TODO: Find better naming for that variable. 280 | var diff = (tailLineIndex - 3) * 2; 281 | var newLaneCount = 4 + diff; 282 | __instance.tailLineIndex = newLaneCount - diff - 1 - tailLineIndex; 283 | break; 284 | } 285 | case < 0: 286 | { 287 | // TODO: Find better naming for that variable. 288 | var diff = (0 - tailLineIndex) * 2; 289 | var newLaneCount = 4 + diff; 290 | __instance.tailLineIndex = newLaneCount - diff - 1 - tailLineIndex; 291 | break; 292 | } 293 | } 294 | } 295 | } 296 | } 297 | 298 | [HarmonyPatch(typeof(NoteCutDirectionExtensions), nameof(NoteCutDirectionExtensions.Mirrored))] 299 | internal static class NoteCutDirectionExtensionsMirroredPatch 300 | { 301 | private static void Prefix(out NoteCutDirection __state, NoteCutDirection cutDirection) 302 | { 303 | __state = cutDirection; 304 | } 305 | 306 | private static void Postfix(ref NoteCutDirection __result, NoteCutDirection __state) 307 | { 308 | var direction = (int)__state; 309 | if (direction is >= 1000 and <= 1360) 310 | { 311 | var newDirection = 2360 - direction; 312 | __result = (NoteCutDirection)newDirection; 313 | } 314 | else if (direction is >= 2000 and <= 2360) 315 | { 316 | var newDirection = 4360 - direction; 317 | __result = (NoteCutDirection)newDirection; 318 | } 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /MappingExtensions/HarmonyPatches/PrecisePlacementPatches.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UnityEngine; 3 | 4 | namespace MappingExtensions.HarmonyPatches 5 | { 6 | [HarmonyPatch(typeof(BeatmapObjectSpawnMovementData), nameof(BeatmapObjectSpawnMovementData.GetNoteOffset))] 7 | internal static class BeatmapObjectSpawnMovementDataGetNoteOffsetPatch 8 | { 9 | private static void Postfix(BeatmapObjectSpawnMovementData __instance, ref Vector3 __result, int noteLineIndex, NoteLineLayer noteLineLayer) 10 | { 11 | if (!Plugin.active) 12 | { 13 | return; 14 | } 15 | 16 | if (noteLineIndex is >= 1000 or <= -1000) 17 | { 18 | if (noteLineIndex <= -1000) 19 | { 20 | noteLineIndex += 2000; 21 | } 22 | 23 | // TODO: Find a better name for this variable. 24 | var num = -(__instance._noteLinesCount - 1f) * 0.5f; 25 | num += noteLineIndex * StaticBeatmapObjectSpawnMovementData.kNoteLinesDistance / 1000; 26 | __result = __instance._rightVec * num + new Vector3(0f, StaticBeatmapObjectSpawnMovementData.LineYPosForLineLayer(noteLineLayer), 0f); 27 | } 28 | } 29 | } 30 | 31 | [HarmonyPatch(typeof(BeatmapObjectSpawnMovementData), nameof(BeatmapObjectSpawnMovementData.GetObstacleOffset))] 32 | internal static class BeatmapObjectSpawnMovementDataGetObstacleOffsetPatch 33 | { 34 | private static void Postfix(BeatmapObjectSpawnMovementData __instance, ref Vector3 __result, int noteLineIndex, NoteLineLayer noteLineLayer) 35 | { 36 | if (!Plugin.active) 37 | { 38 | return; 39 | } 40 | 41 | if (noteLineIndex is >= 1000 or <= -1000) 42 | { 43 | if (noteLineIndex <= -1000) 44 | { 45 | noteLineIndex += 2000; 46 | } 47 | 48 | // TODO: Find a better name for this variable. 49 | var num = -(__instance._noteLinesCount - 1f) * 0.5f; 50 | num += noteLineIndex * StaticBeatmapObjectSpawnMovementData.kNoteLinesDistance / 1000; 51 | __result = __instance._rightVec * num + new Vector3(0f, StaticBeatmapObjectSpawnMovementData.LineYPosForLineLayer(noteLineLayer) + StaticBeatmapObjectSpawnMovementData.kObstacleVerticalOffset, 0f); 52 | } 53 | } 54 | } 55 | 56 | [HarmonyPatch(typeof(BeatmapObjectSpawnMovementData), nameof(BeatmapObjectSpawnMovementData.HighestJumpPosYForLineLayer))] 57 | internal static class BeatmapObjectSpawnMovementDataHighestJumpPosYForLineLayerPatch 58 | { 59 | private static void Postfix(BeatmapObjectSpawnMovementData __instance, ref float __result, NoteLineLayer lineLayer) 60 | { 61 | if (!Plugin.active) 62 | { 63 | return; 64 | } 65 | 66 | var delta = __instance._topLinesHighestJumpPosY - __instance._upperLinesHighestJumpPosY; 67 | var layer = (int)lineLayer; 68 | if (layer is >= 1000 or <= -1000) 69 | { 70 | __result = __instance._upperLinesHighestJumpPosY - delta - delta + __instance._jumpOffsetYProvider.jumpOffsetY + layer * delta / 1000; 71 | } 72 | else if (layer is > 2 or < 0) 73 | { 74 | __result = __instance._upperLinesHighestJumpPosY - delta + __instance._jumpOffsetYProvider.jumpOffsetY + layer * delta; 75 | } 76 | } 77 | } 78 | 79 | [HarmonyPatch(typeof(StaticBeatmapObjectSpawnMovementData), nameof(StaticBeatmapObjectSpawnMovementData.LineYPosForLineLayer))] 80 | internal static class StaticBeatmapObjectSpawnMovementDataLineYPosForLineLayerPatch 81 | { 82 | private static void Postfix(ref float __result, NoteLineLayer lineLayer) 83 | { 84 | if (!Plugin.active) 85 | { 86 | return; 87 | } 88 | 89 | const float delta = StaticBeatmapObjectSpawnMovementData.kTopLinesYPos - StaticBeatmapObjectSpawnMovementData.kUpperLinesYPos; 90 | var layer = (int)lineLayer; 91 | if (layer is >= 1000 or <= -1000) 92 | { 93 | __result = StaticBeatmapObjectSpawnMovementData.kUpperLinesYPos - delta - delta + layer * delta / 1000; 94 | } 95 | else if (layer is > 2 or < 0) 96 | { 97 | __result = StaticBeatmapObjectSpawnMovementData.kUpperLinesYPos - delta + layer * delta; 98 | } 99 | } 100 | } 101 | 102 | [HarmonyPatch(typeof(StaticBeatmapObjectSpawnMovementData), nameof(StaticBeatmapObjectSpawnMovementData.Get2DNoteOffset))] 103 | internal static class StaticBeatmapObjectSpawnMovementDataGet2DNoteOffsetPatch 104 | { 105 | private static void Postfix(ref Vector2 __result, int noteLineIndex, int noteLinesCount, NoteLineLayer noteLineLayer) 106 | { 107 | if (!Plugin.active) 108 | { 109 | return; 110 | } 111 | 112 | if (noteLineIndex is >= 1000 or <= -1000) 113 | { 114 | if (noteLineIndex <= -1000) 115 | { 116 | noteLineIndex += 2000; 117 | } 118 | 119 | // TODO: Find a better name for this variable. 120 | var num = -(noteLinesCount - 1f) * 0.5f; 121 | var x = num + noteLineIndex * StaticBeatmapObjectSpawnMovementData.kNoteLinesDistance / 1000; 122 | var y = StaticBeatmapObjectSpawnMovementData.LineYPosForLineLayer(noteLineLayer); 123 | __result = new Vector2(x, y); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /MappingExtensions/MappingExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | net472 7 | latest 8 | enable 9 | true 10 | true 11 | $(BeatSaberDir)\Beat Saber_Data\Managed 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | $(BeatSaberDir)\Libs\0Harmony.dll 28 | false 29 | 30 | 31 | $(BeatSaberDir)\Beat Saber_Data\Managed\BeatmapCore.dll 32 | false 33 | true 34 | 35 | 36 | $(BeatSaberDir)\Plugins\BS_Utils.dll 37 | false 38 | 39 | 40 | $(BeatSaberDir)\Beat Saber_Data\Managed\DataModels.dll 41 | false 42 | true 43 | 44 | 45 | $(BeatSaberDir)\Beat Saber_Data\Managed\GameplayCore.dll 46 | false 47 | 48 | 49 | $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll 50 | false 51 | 52 | 53 | $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll 54 | false 55 | true 56 | 57 | 58 | $(BeatSaberDir)\Beat Saber_Data\Managed\netstandard.dll 59 | false 60 | 61 | 62 | $(BeatSaberDir)\Plugins\SongCore.dll 63 | false 64 | 65 | 66 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll 67 | false 68 | 69 | 70 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll 71 | false 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /MappingExtensions/Plugin.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using UnityEngine.SceneManagement; 3 | using HarmonyLib; 4 | using IPA; 5 | using IPA.Loader; 6 | using IPA.Logging; 7 | 8 | namespace MappingExtensions 9 | { 10 | [Plugin(RuntimeOptions.DynamicInit)] 11 | public class Plugin 12 | { 13 | private readonly PluginMetadata _metadata; 14 | private readonly Harmony _harmony; 15 | 16 | internal static Logger Log { get; private set; } = null!; 17 | internal static bool active; 18 | 19 | [Init] 20 | public Plugin(Logger logger, PluginMetadata metadata) 21 | { 22 | Log = logger; 23 | _metadata = metadata; 24 | _harmony = new Harmony("com.kyle1413.BeatSaber.MappingExtensions"); 25 | } 26 | 27 | [OnEnable] 28 | public void OnEnable() 29 | { 30 | SongCore.Collections.RegisterCapability("Mapping Extensions"); 31 | SongCore.Collections.RegisterCapability("Mapping Extensions-Precision Placement"); 32 | SongCore.Collections.RegisterCapability("Mapping Extensions-Extra Note Angles"); 33 | SongCore.Collections.RegisterCapability("Mapping Extensions-More Lanes"); 34 | _harmony.PatchAll(_metadata.Assembly); 35 | SceneManager.activeSceneChanged += OnActiveSceneChanged; 36 | } 37 | 38 | [OnDisable] 39 | public void OnDisable() 40 | { 41 | SongCore.Collections.DeregisterCapability("Mapping Extensions"); 42 | SongCore.Collections.DeregisterCapability("Mapping Extensions-Precision Placement"); 43 | SongCore.Collections.DeregisterCapability("Mapping Extensions-Extra Note Angles"); 44 | SongCore.Collections.DeregisterCapability("Mapping Extensions-More Lanes"); 45 | _harmony.UnpatchSelf(); 46 | SceneManager.activeSceneChanged -= OnActiveSceneChanged; 47 | } 48 | 49 | private static void OnActiveSceneChanged(Scene previousScene, Scene newScene) 50 | { 51 | var newSceneName = newScene.name; 52 | if (newSceneName == BS_Utils.SceneNames.Menu) 53 | { 54 | active = false; 55 | } 56 | else if (newSceneName == BS_Utils.SceneNames.Game) 57 | { 58 | CheckActivation(); 59 | } 60 | } 61 | 62 | private static void CheckActivation() 63 | { 64 | if (!BS_Utils.Plugin.LevelData.IsSet) 65 | { 66 | active = false; 67 | return; 68 | } 69 | 70 | var gameplayCoreSceneSetupData = BS_Utils.Plugin.LevelData.GameplayCoreSceneSetupData; 71 | var difficultyData = SongCore.Collections.GetCustomLevelSongDifficultyData(gameplayCoreSceneSetupData.beatmapKey); 72 | if (difficultyData != null && difficultyData.additionalDifficultyData._requirements.Contains("Mapping Extensions")) 73 | { 74 | active = true; 75 | } 76 | } 77 | 78 | public static void ForceActivateForSong() 79 | { 80 | active = true; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /MappingExtensions/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/nike4613/ModSaber-MetadataFileSchema/master/Schema.json", 3 | "author": "Kyle1413", 4 | "description": "This adds a host of new things you can do with your maps as a mapper, and allows you to play said maps as a player. View More Info for details.", 5 | "gameVersion": "1.40.0", 6 | "id": "MappingExtensions", 7 | "name": "MappingExtensions", 8 | "version": "1.7.6", 9 | "dependsOn": { 10 | "BSIPA": "^4.3.0", 11 | "SongCore": "^3.15.1", 12 | "BS Utils": "^1.12.3" 13 | }, 14 | "links": { 15 | "project-source": "https://github.com/Kylemc1413/MappingExtensions", 16 | "donate": "https://ko-fi.com/kyle1413k" 17 | } 18 | } -------------------------------------------------------------------------------- /README-Beatmapv2.md: -------------------------------------------------------------------------------- 1 | ##### [GitHub](https://github.com/Kylemc1413/MappingExtensions) 2 | This adds a host of new things you can do with your maps, currently only possible through JSON editing until an editor adds support for the features. If you map with these, they will be auto detected as requirements by Song Loader 6.0.0 or later, and players will not be able to play them without mapping extensions installed. HOWEVER: 3 | 4 | #### Having a map with the new features in your library without Song Loader 6.0.0 or later will softlock your game 5 | 6 | #### If you use any of these features, you MUST add "Mapping Extensions" as a requirement for your map for them to function, you can go [Here](https://github.com/Kylemc1413/SongCore/blob/master/README.md) to see how adding requirements to the info.dat works 7 | ## Capabilities 8 | ### _Extra Lanes/Layers_ 9 | - You can simply change the `_lineIndex` and `_lineLayer` of a note/bomb/(walls can only change Index) to values outside the normal range and they will show up there accordingly, letting you extend your map further outward/vertically, or simply placing things like bombs or walls further out for aesthetic 10 | 11 | ### _360 Degree note Rotation_ 12 | - You can change the cutDirection of notes to a value between 1000 and 1360 to have the note be rotated from 0-360 degrees clockwise instead of the normal 45 degree deviations the game is limited to, 0 degrees is a down slice , 90 degrees is left slice, 180 up slice, etc etc. 13 | 14 | ### _Precision note Placement_ 15 | - You can also choose to place your notes in lanes/layers *between* normal layers, or simply redefine the space your map takes place in. 16 | 17 | - If you change the line index to be 1000 or higher or -1000 and lower, you'll be placing the note on a "precise" lane, in that instead of every increase being a single lane, every increase will be 1/1000 of a lane. 18 | 19 | - Do not use values between -1000 and 1000, using a value like 500 will simply move the note nearly 500 regular lanes outside of the normal space 20 | 21 | - 1000/-1000 are both equivalent to the normal "leftmost" lane, and going higher than 1000 or lower than -1000 will extend in their respective directions , so 2000 would be equivalent to a line index of 1, -2000 would be the equivalent of a line index of -1, etc etc, with the same logic applying to line layers as well. 22 | 23 | ### _Precise Wall Adjustments_ 24 | - You can change the width of walls to be a value greater than or equal to 1000 to precisely control the width of the wall, with 1000 being the equivalent of 0 width, 2000 being the equivalent of 1 width, etc etc 25 | - You can change the type of walls to be a value between 1000 and 4000 to control the height of the walls, with 1000 being a wall that has no height and is just flat on the ground, 2000 being a normal full height wall, and going higher than that (Up till 4000) being taller than normal 26 | - You can also use precision line index placement like described for notes above 27 | #### _More Precise Wall Adjustments_ 28 | - You can change the type of walls to be a value between 40001 and 4005000 to control how high up the wall starts and how high the wall itself is, the formula for which is below, with wall height ranging from 0 to 4000 and start height ranging from 0 to 999 29 | - Whereas for Regular precision height a height of 1000 would be "0" for 4000+ treat the wall height as the literal value rather than adding 1000 to it when calculating 30 | - Start height corresponds approximately to a wall height 4x as high, so a start height of 250 would equate to just above normal wall height 1000, so the wall would start just above the top of a normal wall 31 | ```cs 32 | type = wall Height * 1000 + start height + 4001 33 | //Example for a wall height of 2300 and a start height of 300 34 | type = 2300 * 1000 + 300 + 4001 = 2304301 35 | ``` 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### [GitHub](https://github.com/Kylemc1413/MappingExtensions) 2 | ##### See Old Readme for legacy maps [HERE](README-Beatmapv2.md) 3 | 4 | This adds a host of new things you can do with your maps, currently only possible through JSON editing until an editor adds support for the features. Keep in mind the following: 5 | 6 | #### If you use any of these features, you MUST add "Mapping Extensions" as a requirement for your map for them to function, you can go [Here](https://github.com/Kylemc1413/SongCore/blob/master/README.md) to see how adding requirements to the info.dat works 7 | ## Capabilities 8 | ### _Extra Lanes/Layers_ 9 | - You can simply change the `Line Index` and `Layer` to be an integer value outside of the normal range (-999 to 999) and they will show up in the expected position, letting you extend your map further outward/vertically, or simply placing things like bombs or walls further out for aesthetic 10 | ##### Supported By 11 | Notes 12 | Bombs 13 | Walls (Does not support negative layers) 14 | Chains 15 | Arcs 16 | ### _360-Degree Note Rotation_ 17 | - You can change the `Cut Direction` of an object to a value between 1000 and 1360 to have the note be rotated from 0-360 degrees clockwise instead of the normal 45 degree deviations the game is limited to, 0 degrees is a down slice , 90 degrees is left slice, 180 up slice, etc etc. 18 | ##### Supported By 19 | Notes 20 | Any Direction Notes (Use range of 2000-2360 instead) 21 | Bombs 22 | Chains 23 | Arcs 24 | ### _Precise Placement_ 25 | - You can also choose to place your objects in lanes/layers *between* normal layers, or simply redefine the space your map takes place in. 26 | 27 | - If you change the line index to be 1000 or higher or -1000 and lower, you'll be placing the object on a "precise" lane, in that instead of every increase being a single lane, every increase will be 1/1000 of a lane. 28 | 29 | - Do not attempt to use values between -1000 and 1000 for precision placement, using a value like 500 will simply move the object nearly 500 regular lanes outside of the normal space 30 | 31 | - 1000/-1000 are both equivalent to the normal "leftmost" lane (Index 0), and going higher than 1000 or lower than -1000 will extend in their respective directions , so 2000 would be equivalent to a line index of 1, -2000 would be the equivalent of a line index of -1, etc etc, with the same logic applying to line layers as well. 32 | ##### Supported By 33 | Notes 34 | Bombs 35 | Walls (Does not support negative layers) 36 | Chains 37 | Arcs 38 | ### _Additional Wall Adjustments_ 39 | - You can use precision placement values for the width of walls. 40 | - You can change the height of walls to be values higher than the normal base game cap 41 | - You can use precision placement values for the height of walls. 42 | ### Important Note 43 | Walls from older maps using an overloaded Type field will have height and start height automatically converted to layer and height values. This may not be entirely accurate at the moment, and walls may look slightly off on maps not made using the new format, improvements to this are welcomed if anybody that knows the format well and is good at math would like to tackle it. 44 | 45 | --------------------------------------------------------------------------------