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