├── .github ├── dependabot.yml └── workflows │ └── dotnet.yml ├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── assets ├── blank.png ├── logo.png ├── logo_white.png ├── preview0.png ├── preview1.png ├── preview2.png └── preview3.png ├── osu.Game.Rulesets.Soyokaze.sln ├── osu.Game.Rulesets.Soyokaze.sln.DotSettings └── osu.Game.Rulesets.Soyokaze ├── .editorconfig ├── Beatmaps └── SoyokazeBeatmapConverter.cs ├── Configuration ├── ColourEnum.cs ├── SoyokazeConfig.cs └── SoyokazeConfigManager.cs ├── Difficulty ├── Preprocessing │ └── SoyokazeDifficultyHitObject.cs ├── Skills │ ├── SkillRead.cs │ └── SkillSpeed.cs ├── SoyokazeDifficultyAttributes.cs └── SoyokazeDifficultyCalculator.cs ├── Extensions ├── ColourExtensions.cs └── PositionExtensions.cs ├── Judgements ├── SoyokazeHoldJudgementResult.cs ├── SoyokazeIgnoreJudgement.cs └── SoyokazeJudgement.cs ├── Mods ├── SoyokazeModAutoplay.cs ├── SoyokazeModCinema.cs ├── SoyokazeModDaycore.cs ├── SoyokazeModDifficultyAdjust.cs ├── SoyokazeModDoubleTime.cs ├── SoyokazeModEasy.cs ├── SoyokazeModHalfTime.cs ├── SoyokazeModHardRock.cs ├── SoyokazeModHidden.cs ├── SoyokazeModHolds.cs ├── SoyokazeModNightcore.cs ├── SoyokazeModNoFail.cs ├── SoyokazeModPerfect.cs ├── SoyokazeModRandom.cs ├── SoyokazeModStaccato.cs ├── SoyokazeModSuddenDeath.cs ├── SoyokazeModWindDown.cs └── SoyokazeModWindUp.cs ├── Objects ├── Drawables │ ├── DrawableHitCircle.cs │ ├── DrawableHold.cs │ ├── DrawableHoldCircle.cs │ ├── DrawableSoyokazeHitObject.cs │ └── DrawableSoyokazeJudgement.cs ├── HitCircle.cs ├── Hold.cs ├── HoldCircle.cs ├── SoyokazeHitObject.cs └── SoyokazeHitObjectLifetimeEntry.cs ├── Replays ├── SoyokazeAutoGenerator.cs ├── SoyokazeFramedReplayInputHandler.cs └── SoyokazeReplayFrame.cs ├── Resources └── Textures │ ├── Gameplay │ └── soyokaze │ │ ├── approachcircle.png │ │ ├── cursor.png │ │ ├── hitcircle.png │ │ ├── hitcircleoverlay.png │ │ ├── holdoverlay.png │ │ ├── holdoverlaybackground.png │ │ ├── inputoverlaybackground.png │ │ ├── inputoverlaykey.png │ │ └── kiaivisualizersquare.png │ ├── icon.png │ └── icon_hi.png ├── Scoring ├── SoyokazeHitWindows.cs └── SoyokazeScoreProcessor.cs ├── Skinning ├── Defaults │ ├── AbstractDefaultComponent.cs │ ├── DefaultApproachCircle.cs │ ├── DefaultCursor.cs │ ├── DefaultHitCircle.cs │ ├── DefaultHitCircleOverlay.cs │ ├── DefaultHitCircleText.cs │ ├── DefaultHoldOverlay.cs │ ├── DefaultHoldOverlayBackground.cs │ ├── DefaultInputOverlayBackground.cs │ ├── DefaultInputOverlayKey.cs │ └── DefaultKiaiVisualizerSquare.cs ├── Legacy │ └── SoyokazeLegacySkinTransformer.cs ├── SkinnableApproachCircle.cs ├── SkinnableCursor.cs ├── SkinnableHitCircle.cs ├── SkinnableHoldProgress.cs ├── SkinnableInputOverlay.cs ├── SkinnableKiaiVisualizer.cs ├── SoyokazeSkinColour.cs ├── SoyokazeSkinComponentLookup.cs ├── SoyokazeSkinComponents.cs └── SoyokazeSkinConfiguration.cs ├── SoyokazeRuleset.cs ├── Statistics ├── AccuracyGraph.cs └── MiniHitEventTimingDistributionGraph.cs ├── UI ├── DrawableSoyokazeRuleset.cs ├── SoyokazeCursorContainer.cs ├── SoyokazeInputManager.cs ├── SoyokazePlayfield.cs ├── SoyokazeReplayRecorder.cs └── SoyokazeSettingsSubsection.cs └── osu.Game.Rulesets.Soyokaze.csproj /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | open-pull-requests-limit: 1000 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Git checkout 17 | uses: actions/checkout@v2 18 | - name: Restore .NET dependencies 19 | run: dotnet restore 20 | - name: Build .NET 21 | run: dotnet build osu.Game.Rulesets.Soyokaze --no-restore --output ./artifacts --configuration Release 22 | - name: Upload artifacts 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: artifacts 26 | path: ./artifacts 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alden Wu 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | soyokaze! 5 | 6 | 7 | # soyokaze! 8 | 9 | [![Version](https://img.shields.io/github/v/release/goodtrailer/soyokaze.svg?color=green&style=flat-square)](https://github.com/goodtrailer/soyokaze/releases/latest) 10 | [![CodeFactor](https://www.codefactor.io/repository/github/goodtrailer/soyokaze/badge/main?style=flat-square)](https://www.codefactor.io/repository/github/goodtrailer/soyokaze/overview/main) 11 | [![License](https://img.shields.io/github/license/goodtrailer/soyokaze.svg?color=blue&style=flat-square)](https://github.com/goodtrailer/soyokaze/blob/master/LICENSE) 12 | [![Downloads](https://img.shields.io/github/downloads/goodtrailer/soyokaze/total.svg?color=orange&style=flat-square)](https://somsubhra.github.io/github-release-stats/?username=goodtrailer&repository=soyokaze&page=1&per_page=0) 13 | 14 | ***Note.*** soyokaze!'s source code is a mess and does not follow osu!'s conventions particularly well. If you want to write your own ruleset, look at osu!standard's or osu!taiko's. You are looking at a bargain-bin ruleset that I've been half-assedly maintaining for a while and for which I may eventually rewrite large portions of. 15 | 16 | An [osu!](https://github.com/ppy/osu) ruleset mimicking [Genshin Impact](https://genshin.mihoyo.com)'s Ballads of Breeze [mini-game](https://youtu.be/ZsacXMduSFY). For more info, you can also check it out over at [rūrusetto](https://rulesets.info/rulesets/soyokaze), a website created by the [Rūrusetto team](https://github.com/Rurusetto) to catelog osu! rulesets. 17 | 18 | ## Video Showcase (YouTube) 19 | [![Preview1](assets/preview1.png)](https://youtu.be/3Sj6tE2t4do) 20 | | [![Preview0](assets/preview0.png)](https://youtu.be/hWjG0W7EiAE) | [![Preview2](assets/preview2.png)](https://youtu.be/uX0HBadqPzs) | [![Preview3](assets/preview3.png)](https://youtu.be/_QKinzhlMes) | 21 | | --- | --- | --- | 22 | 23 | ## Skinning 24 | Default skin PNGs (they're all white): [/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze](/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze). Judgements and Hit Circle text are skinnable too following normal [osu!standard skinning guidelines](https://osu.ppy.sh/wiki/en/Skinning/osu%21). 25 | 26 | ***Note.*** By default, skin PNGs will be taken from the skin's root folder (i.e. where stable looks for skin PNGs). So certain elements (hit circles, approach circles, judgements) will share the same PNGs as osu!std. However, soyokaze! will also look for skin PNGs inside a separate `soyokaze/` folder (i.e. `soyokaze/hitcircle@2x.png` instead of `hitcircle@2x.png`), so it is possible to differentiate between soyokaze! and osu!std. For better organization, *all* soyokaze! PNGs can be placed inside this `soyokaze/` folder. 27 | 28 | `skin.ini` default values: 29 | ``` 30 | [General] 31 | InputOverlayKeyGap: 20 32 | KiaiVisualizerDefaultSpin: 1.5 33 | KiaiVisualizerKiaiSpin: -60 34 | KiaiVisualizerDefaultOpacity: 128 35 | KiaiVisualizerFirstFlashOpacity: 255 36 | KiaiVisualizerFlashOpacity: 152 37 | RotateApproachCircle: false 38 | RotateHitCircle: false 39 | RotateHoldProgress: true 40 | RotateInputOverlayKey: true 41 | 42 | [Colours] 43 | HoldHighlight: 0, 255, 0 44 | KiaiVisualizerDefault: 47, 79, 79 45 | KiaiVisualizerFirstFlash: 255, 255, 255 46 | KiaiVisualizerFlash: 255, 255 255 47 | ``` 48 | 49 | ## Extras 50 | These are features that I think would be really fun, but are non-trivial to implement (unlike +DT, which took maybe 30 seconds). I may or may not implement these in the future depending on if I find the motivation to. 51 | 1. Star rating rework 52 | * osu!'s star ratings suck real bad for soyokaze!. I tried making my own star rating calculator, and it works *slightly* better. It still undervalues reading and rhythm complexity a ton. 53 | 3. Spinners mod (+SP) 54 | * Basically just note spam, taiko/MuseDash style 55 | 4. Multi-notes mod (+MT) 56 | * Pretty hard mod to implement, would have to look at ![LumpBloom7/sentakki](https://github.com/LumpBloom7/sentakki) for some clues, because I'm not sure how to decide when to do singles, doubles, and triples 57 | 5. Editor support 58 | * Currently unusable without a custom build of osu!, because the legacy beatmap decoder gets angry at non-legacy rulesets or something. Also definitely the hardest feature to implement by far 59 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * Screen center distance limit 4 | * Central divider line 5 | * Better hold note visuals 6 | 7 | -------------------------------------------------------------------------------- /assets/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/blank.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/logo.png -------------------------------------------------------------------------------- /assets/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/logo_white.png -------------------------------------------------------------------------------- /assets/preview0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/preview0.png -------------------------------------------------------------------------------- /assets/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/preview1.png -------------------------------------------------------------------------------- /assets/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/preview2.png -------------------------------------------------------------------------------- /assets/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/assets/preview3.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29123.88 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Soyokaze", "osu.Game.Rulesets.Soyokaze\osu.Game.Rulesets.Soyokaze.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | VisualTests|Any CPU = VisualTests|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU 20 | {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU 21 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU 26 | {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | GlobalSection(NestedProjects) = preSolution 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} 35 | EndGlobalSection 36 | GlobalSection(MonoDevelopProperties) = preSolution 37 | Policies = $0 38 | $0.TextStylePolicy = $1 39 | $1.EolMarker = Windows 40 | $1.inheritsSet = VisualStudio 41 | $1.inheritsScope = text/plain 42 | $1.scope = text/x-csharp 43 | $0.CSharpFormattingPolicy = $2 44 | $2.IndentSwitchSection = True 45 | $2.NewLinesForBracesInProperties = True 46 | $2.NewLinesForBracesInAccessors = True 47 | $2.NewLinesForBracesInAnonymousMethods = True 48 | $2.NewLinesForBracesInControlBlocks = True 49 | $2.NewLinesForBracesInAnonymousTypes = True 50 | $2.NewLinesForBracesInObjectCollectionArrayInitializers = True 51 | $2.NewLinesForBracesInLambdaExpressionBody = True 52 | $2.NewLineForElse = True 53 | $2.NewLineForCatch = True 54 | $2.NewLineForFinally = True 55 | $2.NewLineForMembersInObjectInit = True 56 | $2.NewLineForMembersInAnonymousTypes = True 57 | $2.NewLineForClausesInQuery = True 58 | $2.SpacingAfterMethodDeclarationName = False 59 | $2.SpaceAfterMethodCallName = False 60 | $2.SpaceBeforeOpenSquareBracket = False 61 | $2.inheritsSet = Mono 62 | $2.inheritsScope = text/x-csharp 63 | $2.scope = text/x-csharp 64 | EndGlobalSection 65 | GlobalSection(MonoDevelopProperties) = preSolution 66 | Policies = $0 67 | $0.TextStylePolicy = $1 68 | $1.EolMarker = Windows 69 | $1.inheritsSet = VisualStudio 70 | $1.inheritsScope = text/plain 71 | $1.scope = text/x-csharp 72 | $0.CSharpFormattingPolicy = $2 73 | $2.IndentSwitchSection = True 74 | $2.NewLinesForBracesInProperties = True 75 | $2.NewLinesForBracesInAccessors = True 76 | $2.NewLinesForBracesInAnonymousMethods = True 77 | $2.NewLinesForBracesInControlBlocks = True 78 | $2.NewLinesForBracesInAnonymousTypes = True 79 | $2.NewLinesForBracesInObjectCollectionArrayInitializers = True 80 | $2.NewLinesForBracesInLambdaExpressionBody = True 81 | $2.NewLineForElse = True 82 | $2.NewLineForCatch = True 83 | $2.NewLineForFinally = True 84 | $2.NewLineForMembersInObjectInit = True 85 | $2.NewLineForMembersInAnonymousTypes = True 86 | $2.NewLineForClausesInQuery = True 87 | $2.SpacingAfterMethodDeclarationName = False 88 | $2.SpaceAfterMethodCallName = False 89 | $2.SpaceBeforeOpenSquareBracket = False 90 | $2.inheritsSet = Mono 91 | $2.inheritsScope = text/x-csharp 92 | $2.scope = text/x-csharp 93 | EndGlobalSection 94 | EndGlobal 95 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://editorconfig.org 2 | root = true 3 | 4 | [*.cs] 5 | file_header_template = Copyright (c) Alden Wu . Licensed under the MIT Licence.\nSee the LICENSE file in the repository root for full licence text. 6 | end_of_line = crlf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | 12 | #Roslyn naming styles 13 | 14 | #PascalCase for public and protected members 15 | dotnet_naming_style.pascalcase.capitalization = pascal_case 16 | dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 17 | dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event 18 | dotnet_naming_rule.public_members_pascalcase.severity = error 19 | dotnet_naming_rule.public_members_pascalcase.symbols = public_members 20 | dotnet_naming_rule.public_members_pascalcase.style = pascalcase 21 | 22 | #camelCase for private members 23 | dotnet_naming_style.camelcase.capitalization = camel_case 24 | 25 | dotnet_naming_symbols.private_members.applicable_accessibilities = private 26 | dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event 27 | dotnet_naming_rule.private_members_camelcase.severity = warning 28 | dotnet_naming_rule.private_members_camelcase.symbols = private_members 29 | dotnet_naming_rule.private_members_camelcase.style = camelcase 30 | 31 | dotnet_naming_symbols.local_function.applicable_kinds = local_function 32 | dotnet_naming_rule.local_function_camelcase.severity = warning 33 | dotnet_naming_rule.local_function_camelcase.symbols = local_function 34 | dotnet_naming_rule.local_function_camelcase.style = camelcase 35 | 36 | #all_lower for private and local constants/static readonlys 37 | dotnet_naming_style.all_lower.capitalization = all_lower 38 | dotnet_naming_style.all_lower.word_separator = _ 39 | 40 | dotnet_naming_symbols.private_constants.applicable_accessibilities = private 41 | dotnet_naming_symbols.private_constants.required_modifiers = const 42 | dotnet_naming_symbols.private_constants.applicable_kinds = field 43 | dotnet_naming_rule.private_const_all_lower.severity = warning 44 | dotnet_naming_rule.private_const_all_lower.symbols = private_constants 45 | dotnet_naming_rule.private_const_all_lower.style = all_lower 46 | 47 | dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private 48 | dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly 49 | dotnet_naming_symbols.private_static_readonly.applicable_kinds = field 50 | dotnet_naming_rule.private_static_readonly_all_lower.severity = warning 51 | dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly 52 | dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower 53 | 54 | dotnet_naming_symbols.local_constants.applicable_kinds = local 55 | dotnet_naming_symbols.local_constants.required_modifiers = const 56 | dotnet_naming_rule.local_const_all_lower.severity = warning 57 | dotnet_naming_rule.local_const_all_lower.symbols = local_constants 58 | dotnet_naming_rule.local_const_all_lower.style = all_lower 59 | 60 | #ALL_UPPER for non private constants/static readonlys 61 | dotnet_naming_style.all_upper.capitalization = all_upper 62 | dotnet_naming_style.all_upper.word_separator = _ 63 | 64 | dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 65 | dotnet_naming_symbols.public_constants.required_modifiers = const 66 | dotnet_naming_symbols.public_constants.applicable_kinds = field 67 | dotnet_naming_rule.public_const_all_upper.severity = warning 68 | dotnet_naming_rule.public_const_all_upper.symbols = public_constants 69 | dotnet_naming_rule.public_const_all_upper.style = all_upper 70 | 71 | dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 72 | dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly 73 | dotnet_naming_symbols.public_static_readonly.applicable_kinds = field 74 | dotnet_naming_rule.public_static_readonly_all_upper.severity = warning 75 | dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly 76 | dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper 77 | 78 | #Roslyn formating options 79 | 80 | #Formatting - indentation options 81 | csharp_indent_case_contents = true 82 | csharp_indent_case_contents_when_block = false 83 | csharp_indent_labels = one_less_than_current 84 | csharp_indent_switch_labels = true 85 | 86 | #Formatting - new line options 87 | csharp_new_line_before_catch = true 88 | csharp_new_line_before_else = true 89 | csharp_new_line_before_finally = true 90 | csharp_new_line_before_open_brace = all 91 | #csharp_new_line_before_members_in_anonymous_types = true 92 | #csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing 93 | csharp_new_line_between_query_expression_clauses = true 94 | 95 | #Formatting - organize using options 96 | dotnet_sort_system_directives_first = true 97 | 98 | #Formatting - spacing options 99 | csharp_space_after_cast = false 100 | csharp_space_after_colon_in_inheritance_clause = true 101 | csharp_space_after_keywords_in_control_flow_statements = true 102 | csharp_space_before_colon_in_inheritance_clause = true 103 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 104 | csharp_space_between_method_call_name_and_opening_parenthesis = false 105 | csharp_space_between_method_call_parameter_list_parentheses = false 106 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 107 | csharp_space_between_method_declaration_parameter_list_parentheses = false 108 | 109 | #Formatting - wrapping options 110 | csharp_preserve_single_line_blocks = true 111 | csharp_preserve_single_line_statements = true 112 | 113 | #Roslyn language styles 114 | 115 | #Style - this. qualification 116 | dotnet_style_qualification_for_field = false:warning 117 | dotnet_style_qualification_for_property = false:warning 118 | dotnet_style_qualification_for_method = false:warning 119 | dotnet_style_qualification_for_event = false:warning 120 | 121 | #Style - type names 122 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 123 | dotnet_style_predefined_type_for_member_access = true:warning 124 | csharp_style_var_when_type_is_apparent = true:none 125 | csharp_style_var_for_built_in_types = true:none 126 | csharp_style_var_elsewhere = true:silent 127 | 128 | #Style - modifiers 129 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 130 | csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning 131 | 132 | #Style - parentheses 133 | # Skipped because roslyn cannot separate +-*/ with << >> 134 | 135 | #Style - expression bodies 136 | csharp_style_expression_bodied_accessors = true:warning 137 | csharp_style_expression_bodied_constructors = false:none 138 | csharp_style_expression_bodied_indexers = true:warning 139 | csharp_style_expression_bodied_methods = false:silent 140 | csharp_style_expression_bodied_operators = true:warning 141 | csharp_style_expression_bodied_properties = true:warning 142 | csharp_style_expression_bodied_local_functions = true:silent 143 | 144 | #Style - expression preferences 145 | dotnet_style_object_initializer = true:warning 146 | dotnet_style_collection_initializer = true:warning 147 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 148 | dotnet_style_prefer_auto_properties = true:warning 149 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 150 | dotnet_style_prefer_conditional_expression_over_return = true:silent 151 | dotnet_style_prefer_compound_assignment = true:warning 152 | 153 | #Style - null/type checks 154 | dotnet_style_coalesce_expression = true:warning 155 | dotnet_style_null_propagation = true:warning 156 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 157 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 158 | csharp_style_throw_expression = true:silent 159 | csharp_style_conditional_delegate_call = true:warning 160 | 161 | #Style - unused 162 | dotnet_style_readonly_field = true:silent 163 | dotnet_code_quality_unused_parameters = non_public:silent 164 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 165 | csharp_style_unused_value_assignment_preference = discard_variable:warning 166 | 167 | #Style - variable declaration 168 | csharp_style_inlined_variable_declaration = true:warning 169 | csharp_style_deconstructed_variable_declaration = true:warning 170 | 171 | #Style - other C# 7.x features 172 | dotnet_style_prefer_inferred_tuple_names = true:warning 173 | csharp_prefer_simple_default_expression = true:warning 174 | csharp_style_pattern_local_over_anonymous_function = true:warning 175 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 176 | 177 | #Style - C# 8 features 178 | csharp_prefer_static_local_function = true:warning 179 | csharp_prefer_simple_using_statement = true:silent 180 | csharp_style_prefer_index_operator = true:warning 181 | csharp_style_prefer_range_operator = true:warning 182 | csharp_style_prefer_switch_expression = false:none 183 | 184 | #Supressing roslyn built-in analyzers 185 | # Suppress: EC112 186 | 187 | #Private method is unused 188 | dotnet_diagnostic.IDE0051.severity = silent 189 | #Private member is unused 190 | dotnet_diagnostic.IDE0052.severity = silent 191 | 192 | #Rules for disposable 193 | dotnet_diagnostic.IDE0067.severity = none 194 | dotnet_diagnostic.IDE0068.severity = none 195 | dotnet_diagnostic.IDE0069.severity = none 196 | 197 | #Disable operator overloads requiring alternate named methods 198 | dotnet_diagnostic.CA2225.severity = none 199 | 200 | # Banned APIs 201 | dotnet_diagnostic.RS0030.severity = error -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Beatmaps/SoyokazeBeatmapConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using osu.Framework.Bindables; 8 | using osu.Game.Beatmaps; 9 | using osu.Game.Rulesets.Objects; 10 | using osu.Game.Rulesets.Objects.Types; 11 | using osu.Game.Rulesets.Soyokaze.Extensions; 12 | using osu.Game.Rulesets.Soyokaze.Objects; 13 | using osu.Game.Rulesets.Soyokaze.UI; 14 | using osuTK; 15 | 16 | namespace osu.Game.Rulesets.Soyokaze.Beatmaps 17 | { 18 | public class SoyokazeBeatmapConverter : BeatmapConverter 19 | { 20 | public BindableBool CreateHolds { get; } = new BindableBool(true); 21 | 22 | public SoyokazeBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) 23 | : base(beatmap, ruleset) 24 | { 25 | } 26 | 27 | public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition); 28 | 29 | protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) 30 | { 31 | IHasPosition positionData = original as IHasPosition; 32 | IHasCombo comboData = original as IHasCombo; 33 | 34 | Vector2 originalPosition = positionData?.Position ?? Vector2.Zero; 35 | int column = 0, row = 0; 36 | for (int i = 1; i < PositionExtensions.NUM_COLUMNS; i++) 37 | if (originalPosition.X > i * PositionExtensions.BEATMAP_WIDTH / PositionExtensions.NUM_COLUMNS) 38 | column = i; 39 | for (int i = 1; i < PositionExtensions.NUM_ROWS; i++) 40 | if (originalPosition.Y > i * PositionExtensions.BEATMAP_HEIGHT / PositionExtensions.NUM_ROWS) 41 | row = i; 42 | SoyokazeAction button = (SoyokazeAction)PositionExtensions.PositionToButton(column + PositionExtensions.NUM_COLUMNS * row); 43 | 44 | SoyokazeHitObject hitObject; 45 | switch (original) 46 | { 47 | case IHasPathWithRepeats slider: 48 | if (!CreateHolds.Value) 49 | goto default; 50 | hitObject = new Hold 51 | { 52 | Duration = slider.Duration, 53 | StartSamples = slider.NodeSamples[0], 54 | HoldSamples = original.Samples, 55 | EndSamples = slider.NodeSamples[slider.NodeSamples.Count - 1], 56 | }; 57 | break; 58 | 59 | case IHasDuration longNote: 60 | if (!CreateHolds.Value) 61 | goto default; 62 | hitObject = new Hold 63 | { 64 | Duration = longNote.Duration, 65 | EndSamples = original.Samples, 66 | }; 67 | break; 68 | 69 | default: 70 | hitObject = new HitCircle 71 | { 72 | Samples = original.Samples, 73 | }; 74 | break; 75 | } 76 | 77 | hitObject.Button = button; 78 | hitObject.StartTime = original.StartTime; 79 | hitObject.NewCombo = comboData?.NewCombo ?? false; 80 | hitObject.ComboOffset = comboData?.ComboOffset ?? 0; 81 | 82 | yield return hitObject; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Configuration/ColourEnum.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Configuration 5 | { 6 | public enum ColourEnum 7 | { 8 | None, 9 | 10 | BrightRed, // 255, 0, 0 11 | BrightYellow, // 255, 255, 0 12 | BrightGreen, // 0, 255, 0 13 | BrightBlue, // 0, 255, 255 14 | BrightPurple, // 255, 0, 255 15 | 16 | Red, // 208, 49, 45 17 | Orange, // 255, 128, 0 18 | Yellow, // 255, 196, 32 19 | Green, // 42, 196, 42 20 | Blue, // 64, 152, 255 21 | Purple, // 152, 64, 255 22 | Pink, // 255, 128, 196 23 | 24 | White, // 255, 255, 255 25 | Black, // 0, 0, 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Configuration/SoyokazeConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Configuration 5 | { 6 | public enum SoyokazeConfig 7 | { 8 | ScreenCenterGap, 9 | ObjectGap, 10 | ShowInputOverlay, 11 | ShowKiaiVisualizer, 12 | HighlightHolds, 13 | HoldHighlightColour, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Configuration/SoyokazeConfigManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Configuration; 5 | using osu.Game.Rulesets.Configuration; 6 | using osu.Game.Rulesets.Soyokaze.Extensions; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.Configuration 9 | { 10 | public class SoyokazeConfigManager : RulesetConfigManager 11 | { 12 | public SoyokazeConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) 13 | : base(settings, ruleset, variant) 14 | { 15 | } 16 | 17 | protected override void InitialiseDefaults() 18 | { 19 | base.InitialiseDefaults(); 20 | 21 | SetDefault(SoyokazeConfig.ScreenCenterGap, 220, 0, PositionExtensions.SCREEN_WIDTH / 2); 22 | SetDefault(SoyokazeConfig.ObjectGap, 120, 0, PositionExtensions.SCREEN_WIDTH / 2); 23 | 24 | SetDefault(SoyokazeConfig.HighlightHolds, true); 25 | SetDefault(SoyokazeConfig.ShowInputOverlay, true); 26 | SetDefault(SoyokazeConfig.ShowKiaiVisualizer, true); 27 | 28 | SetDefault(SoyokazeConfig.HoldHighlightColour, ColourEnum.None); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Difficulty/Preprocessing/SoyokazeDifficultyHitObject.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using osu.Game.Rulesets.Difficulty.Preprocessing; 7 | using osu.Game.Rulesets.Objects; 8 | using osu.Game.Rulesets.Soyokaze.Objects; 9 | using osu.Game.Rulesets.Soyokaze.UI; 10 | 11 | namespace osu.Game.Rulesets.Soyokaze.Difficulty.Preprocessing 12 | { 13 | public class SoyokazeDifficultyHitObject : DifficultyHitObject 14 | { 15 | public const int COUNT = 8; 16 | 17 | public SoyokazeAction Button = (SoyokazeAction)(-1); 18 | public int Consecutive = 0; 19 | public int ButtonVariety = 0; 20 | public double TotalDeltaTime = 0.0; 21 | public double ConsecutiveDeltaTime = 0.0; 22 | 23 | public SoyokazeDifficultyHitObject(double clockRate, HitObject[] hitObjects, List diffObjects, int index) 24 | : base(hitObjects[COUNT - 1], hitObjects[COUNT - 2], clockRate, diffObjects, index) 25 | { 26 | Button = (hitObjects[COUNT - 1] as SoyokazeHitObject).Button; 27 | 28 | for (int i = hitObjects.Length - 1; i >= 0; i--) 29 | { 30 | if ((hitObjects[i] as SoyokazeHitObject).Button != Button) 31 | break; 32 | Consecutive++; 33 | } 34 | 35 | bool[] counted = new bool[COUNT]; 36 | foreach (SoyokazeHitObject hitObject in hitObjects) 37 | { 38 | if (counted[(int)hitObject.Button]) 39 | continue; 40 | counted[(int)hitObject.Button] = true; 41 | ButtonVariety++; 42 | } 43 | 44 | TotalDeltaTime = Math.Max((hitObjects[COUNT - 1].StartTime - hitObjects[0].StartTime) / clockRate, 1); 45 | ConsecutiveDeltaTime = Math.Max((hitObjects[COUNT - 1].StartTime - hitObjects[COUNT - Consecutive].StartTime) / clockRate, 1); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Difficulty/Skills/SkillRead.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Difficulty.Preprocessing; 5 | using osu.Game.Rulesets.Difficulty.Skills; 6 | using osu.Game.Rulesets.Mods; 7 | using osu.Game.Rulesets.Soyokaze.Difficulty.Preprocessing; 8 | 9 | namespace osu.Game.Rulesets.Soyokaze.Difficulty.Skills 10 | { 11 | public class SkillRead : StrainDecaySkill 12 | { 13 | // slow decay and medium multiplier = buff consistently fast and cluttered maps 14 | protected override double SkillMultiplier => 7.0; 15 | protected override double StrainDecayBase => 0.3; 16 | 17 | public SkillRead(Mod[] mods) 18 | : base(mods) 19 | { 20 | } 21 | 22 | protected override double StrainValueOf(DifficultyHitObject current) 23 | { 24 | SoyokazeDifficultyHitObject soyokazeObject = current as SoyokazeDifficultyHitObject; 25 | 26 | return SkillMultiplier * (soyokazeObject.ButtonVariety - 1) / soyokazeObject.TotalDeltaTime; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Difficulty/Skills/SkillSpeed.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Difficulty.Preprocessing; 5 | using osu.Game.Rulesets.Difficulty.Skills; 6 | using osu.Game.Rulesets.Mods; 7 | using osu.Game.Rulesets.Soyokaze.Difficulty.Preprocessing; 8 | 9 | namespace osu.Game.Rulesets.Soyokaze.Difficulty.Skills 10 | { 11 | public class SkillSpeed : StrainDecaySkill 12 | { 13 | // fast decay and high multiplier = buff short and fast bursts/triples 14 | protected override double SkillMultiplier => 13.5; 15 | protected override double StrainDecayBase => 0.115; 16 | 17 | public SkillSpeed(Mod[] mods) 18 | : base(mods) 19 | { 20 | } 21 | 22 | protected override double StrainValueOf(DifficultyHitObject current) 23 | { 24 | SoyokazeDifficultyHitObject soyokazeObject = current as SoyokazeDifficultyHitObject; 25 | 26 | if (soyokazeObject.Consecutive < 2) 27 | return 0; 28 | 29 | return SkillMultiplier * (soyokazeObject.Consecutive - 1) / soyokazeObject.ConsecutiveDeltaTime; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Difficulty/SoyokazeDifficultyAttributes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Difficulty; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Difficulty 7 | { 8 | public class SoyokazeDifficultyAttributes : DifficultyAttributes 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Difficulty/SoyokazeDifficultyCalculator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using osu.Game.Beatmaps; 8 | using osu.Game.Rulesets.Difficulty; 9 | using osu.Game.Rulesets.Difficulty.Preprocessing; 10 | using osu.Game.Rulesets.Difficulty.Skills; 11 | using osu.Game.Rulesets.Mods; 12 | using osu.Game.Rulesets.Soyokaze.Difficulty.Preprocessing; 13 | using osu.Game.Rulesets.Soyokaze.Difficulty.Skills; 14 | using osu.Game.Rulesets.Soyokaze.Mods; 15 | 16 | namespace osu.Game.Rulesets.Soyokaze.Difficulty 17 | { 18 | public class SoyokazeDifficultyCalculator : DifficultyCalculator 19 | { 20 | private const double difficulty_multiplier = 0.445; 21 | 22 | public SoyokazeDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) 23 | : base(ruleset, beatmap) 24 | { 25 | } 26 | 27 | protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) 28 | { 29 | if (beatmap.HitObjects.Count == 0) 30 | return new DifficultyAttributes 31 | { 32 | Mods = mods, 33 | }; 34 | 35 | double speedRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; 36 | double readRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; 37 | 38 | double starRating = speedRating + readRating + (speedRating - readRating) / 3; 39 | 40 | int maxCombo = beatmap.HitObjects.Count; 41 | 42 | return new SoyokazeDifficultyAttributes 43 | { 44 | StarRating = starRating, 45 | Mods = mods, 46 | MaxCombo = maxCombo, 47 | }; 48 | } 49 | 50 | protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) 51 | { 52 | var diffObjects = new List(); 53 | 54 | for (int i = 0; i < beatmap.HitObjects.Count - SoyokazeDifficultyHitObject.COUNT; i++) 55 | { 56 | var hitObjects = beatmap.HitObjects.Skip(i).Take(SoyokazeDifficultyHitObject.COUNT).ToArray(); 57 | var diffObject = new SoyokazeDifficultyHitObject(clockRate, hitObjects, diffObjects, i); 58 | diffObjects.Add(diffObject); 59 | } 60 | 61 | return diffObjects; 62 | } 63 | 64 | protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockrate) => new Skill[] 65 | { 66 | new SkillSpeed(mods), 67 | new SkillRead(mods), 68 | }; 69 | 70 | protected override Mod[] DifficultyAdjustmentMods => new Mod[] 71 | { 72 | new SoyokazeModDoubleTime(), 73 | new SoyokazeModHalfTime(), 74 | new SoyokazeModEasy(), 75 | new SoyokazeModHardRock(), 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Extensions/ColourExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics; 5 | using osu.Game.Rulesets.Soyokaze.Configuration; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Extensions 8 | { 9 | public static class ColourExtensions 10 | { 11 | public static Colour4 ToColour4(ColourEnum colourEnum) 12 | { 13 | switch (colourEnum) 14 | { 15 | case ColourEnum.BrightRed: 16 | return new Colour4(255, 0, 0, 255); 17 | case ColourEnum.BrightYellow: 18 | return new Colour4(255, 255, 0, 255); 19 | case ColourEnum.BrightGreen: 20 | return new Colour4(0, 255, 0, 255); 21 | case ColourEnum.BrightBlue: 22 | return new Colour4(0, 255, 255, 255); 23 | case ColourEnum.BrightPurple: 24 | return new Colour4(255, 0, 255, 255); 25 | case ColourEnum.Red: 26 | return new Colour4(208, 49, 45, 255); 27 | case ColourEnum.Orange: 28 | return new Colour4(255, 128, 0, 255); 29 | case ColourEnum.Yellow: 30 | return new Colour4(255, 196, 32, 255); 31 | case ColourEnum.Green: 32 | return new Colour4(42, 196, 42, 255); 33 | case ColourEnum.Blue: 34 | return new Colour4(64, 152, 255, 255); 35 | case ColourEnum.Purple: 36 | return new Colour4(152, 64, 255, 255); 37 | case ColourEnum.Pink: 38 | return new Colour4(255, 128, 196, 255); 39 | case ColourEnum.White: 40 | return new Colour4(255, 255, 255, 255); 41 | case ColourEnum.Black: 42 | return new Colour4(0, 0, 0, 255); 43 | default: 44 | return new Colour4(0, 0, 0, 255); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Extensions/PositionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics; 5 | using osu.Game.Rulesets.Soyokaze.UI; 6 | using osuTK; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.Extensions 9 | { 10 | public static class PositionExtensions 11 | { 12 | public const int SCREEN_WIDTH = 1366; 13 | public const int SCREEN_HEIGHT = 768; 14 | public static readonly Vector2 SCREEN_DIMS = new Vector2(SCREEN_WIDTH, SCREEN_HEIGHT); 15 | public static readonly Vector2 SCREEN_CENTER = new Vector2(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); 16 | public const int BEATMAP_WIDTH = 512; 17 | public const int BEATMAP_HEIGHT = 384; 18 | public static readonly Vector2 BEATMAP_DIMS = new Vector2(BEATMAP_WIDTH, BEATMAP_HEIGHT); 19 | public static readonly Vector2 BEATMAP_CENTER = new Vector2(BEATMAP_WIDTH / 2, BEATMAP_HEIGHT / 2); 20 | public const int NUM_COLUMNS = 4; 21 | public const int NUM_ROWS = 2; 22 | 23 | public static Vector2[] GetPositions(int screenCenterGap, int objectGap, bool inButtonOrder, Anchor origin) 24 | { 25 | Vector2 offset = new Vector2(0); 26 | 27 | if (origin.HasFlag(Anchor.x0)) 28 | offset.X = SCREEN_WIDTH / 2; 29 | else if (origin.HasFlag(Anchor.x2)) 30 | offset.X = -SCREEN_WIDTH / 2; 31 | 32 | if (origin.HasFlag(Anchor.y0)) 33 | offset.Y = SCREEN_HEIGHT / 2; 34 | else if (origin.HasFlag(Anchor.y2)) 35 | offset.Y = -SCREEN_HEIGHT / 2; 36 | 37 | Vector2[] positions = new Vector2[] 38 | { 39 | new Vector2(offset.X - screenCenterGap - objectGap, offset.Y ), 40 | new Vector2(offset.X - screenCenterGap, offset.Y - objectGap), 41 | new Vector2(offset.X + screenCenterGap - objectGap, offset.Y ), 42 | new Vector2(offset.X + screenCenterGap, offset.Y - objectGap), 43 | 44 | new Vector2(offset.X - screenCenterGap, offset.Y + objectGap), 45 | new Vector2(offset.X - screenCenterGap + objectGap, offset.Y ), 46 | new Vector2(offset.X + screenCenterGap, offset.Y + objectGap), 47 | new Vector2(offset.X + screenCenterGap + objectGap, offset.Y ) 48 | }; 49 | 50 | if (!inButtonOrder) 51 | return positions; 52 | else 53 | return new Vector2[] 54 | { 55 | positions[1], 56 | positions[0], 57 | positions[4], 58 | positions[5], 59 | 60 | positions[3], 61 | positions[2], 62 | positions[6], 63 | positions[7] 64 | }; 65 | } 66 | 67 | public static float ButtonToRotation(SoyokazeAction button) 68 | { 69 | switch (button) 70 | { 71 | case SoyokazeAction.Button0: 72 | case SoyokazeAction.Button4: 73 | return 0f; 74 | 75 | case SoyokazeAction.Button1: 76 | case SoyokazeAction.Button5: 77 | return 270f; 78 | 79 | case SoyokazeAction.Button2: 80 | case SoyokazeAction.Button6: 81 | return 180f; 82 | 83 | case SoyokazeAction.Button3: 84 | case SoyokazeAction.Button7: 85 | return 90f; 86 | 87 | default: 88 | return float.NaN; 89 | } 90 | } 91 | 92 | public static int PositionToButton(int positionIndex) 93 | { 94 | switch (positionIndex) 95 | { 96 | case 0: 97 | return 1; 98 | case 1: 99 | return 0; 100 | case 2: 101 | return 5; 102 | case 3: 103 | return 4; 104 | case 4: 105 | return 2; 106 | case 5: 107 | return 3; 108 | case 6: 109 | return 6; 110 | case 7: 111 | return 7; 112 | default: 113 | return -1; 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Judgements/SoyokazeHoldJudgementResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Judgements; 5 | using osu.Game.Rulesets.Soyokaze.Objects; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Judgements 8 | { 9 | public class SoyokazeHoldJudgementResult : JudgementResult 10 | { 11 | public double TrueTimeOffset { get; set; } 12 | 13 | public SoyokazeHoldJudgementResult(Hold hitObject, Judgement judgement) 14 | : base(hitObject, judgement) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Judgements/SoyokazeIgnoreJudgement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Scoring; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Judgements 7 | { 8 | public class SoyokazeIgnoreJudgement : SoyokazeJudgement 9 | { 10 | public override HitResult MaxResult => HitResult.IgnoreHit; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Judgements/SoyokazeJudgement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Judgements; 5 | using osu.Game.Rulesets.Scoring; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Judgements 8 | { 9 | public class SoyokazeJudgement : Judgement 10 | { 11 | public override HitResult MaxResult => HitResult.Great; 12 | 13 | protected override double HealthIncreaseFor(HitResult result) 14 | { 15 | switch (result) 16 | { 17 | default: 18 | return 0; 19 | 20 | case HitResult.Miss: 21 | case HitResult.LargeTickMiss: 22 | return -DEFAULT_MAX_HEALTH_INCREASE; 23 | 24 | case HitResult.Meh: 25 | case HitResult.SmallTickMiss: 26 | return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; 27 | 28 | case HitResult.Ok: 29 | case HitResult.SmallBonus: 30 | case HitResult.SmallTickHit: 31 | return DEFAULT_MAX_HEALTH_INCREASE * 0.5; 32 | 33 | case HitResult.Good: 34 | return DEFAULT_MAX_HEALTH_INCREASE * 0.75; 35 | 36 | case HitResult.Great: 37 | case HitResult.LargeBonus: 38 | case HitResult.LargeTickHit: 39 | return DEFAULT_MAX_HEALTH_INCREASE; 40 | 41 | case HitResult.Perfect: 42 | return DEFAULT_MAX_HEALTH_INCREASE * 1.15f; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModAutoplay.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Game.Beatmaps; 6 | using osu.Game.Rulesets.Mods; 7 | using osu.Game.Rulesets.Soyokaze.Replays; 8 | 9 | namespace osu.Game.Rulesets.Soyokaze.Mods 10 | { 11 | public class SoyokazeModAutoplay : ModAutoplay 12 | { 13 | public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) 14 | { 15 | var replay = new SoyokazeAutoGenerator(beatmap, mods).Generate(); 16 | var user = new ModCreatedUser { Username = "goodtrailer's super-duper autoplay bot" }; 17 | return new ModReplayData(replay, user); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModCinema.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Game.Beatmaps; 6 | using osu.Game.Rulesets.Mods; 7 | using osu.Game.Rulesets.Soyokaze.Objects; 8 | using osu.Game.Rulesets.Soyokaze.Replays; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.Mods 11 | { 12 | public class SoyokazeModCinema : ModCinema 13 | { 14 | public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) 15 | { 16 | var replay = new SoyokazeAutoGenerator(beatmap, mods).Generate(); 17 | var user = new ModCreatedUser { Username = "goodtrailer's really cool theater bot" }; 18 | return new ModReplayData(replay, user); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModDaycore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModDaycore : ModDaycore 9 | { 10 | public override double ScoreMultiplier => 0.3; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModDifficultyAdjust.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using osu.Framework.Localisation; 8 | using osu.Game.Beatmaps; 9 | using osu.Game.Configuration; 10 | using osu.Game.Rulesets.Mods; 11 | 12 | namespace osu.Game.Rulesets.Soyokaze.Mods 13 | { 14 | // ripped from https://github.com/ppy/osu/blob/master/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs 15 | public class SoyokazeModDifficultyAdjust : ModDifficultyAdjust 16 | { 17 | [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] 18 | public DifficultyBindable CircleSize { get; } = new DifficultyBindable 19 | { 20 | Precision = 0.1f, 21 | MinValue = 0, 22 | MaxValue = 10, 23 | ExtendedMaxValue = 11, 24 | ReadCurrentFromDifficulty = diff => diff.CircleSize, 25 | }; 26 | 27 | [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] 28 | public DifficultyBindable ApproachRate { get; } = new DifficultyBindable 29 | { 30 | Precision = 0.1f, 31 | MinValue = 0, 32 | MaxValue = 10, 33 | ExtendedMaxValue = 11, 34 | ReadCurrentFromDifficulty = diff => diff.ApproachRate, 35 | }; 36 | 37 | public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription 38 | { 39 | get 40 | { 41 | if (!CircleSize.IsDefault) 42 | yield return ("Circle size", $"{CircleSize.Value:N1}"); 43 | 44 | foreach (var setting in base.SettingDescription) 45 | yield return setting; 46 | 47 | if (!ApproachRate.IsDefault) 48 | yield return ("Approach rate", $"{ApproachRate.Value:N1}"); 49 | } 50 | } 51 | 52 | protected override void ApplySettings(BeatmapDifficulty difficulty) 53 | { 54 | base.ApplySettings(difficulty); 55 | 56 | if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value; 57 | if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModDoubleTime.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModDoubleTime : ModDoubleTime 9 | { 10 | public override double ScoreMultiplier => 1.12; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModEasy.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Localisation; 5 | using osu.Game.Rulesets.Mods; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Mods 8 | { 9 | public class SoyokazeModEasy : ModEasyWithExtraLives 10 | { 11 | public override double ScoreMultiplier => 0.85; 12 | 13 | public override LocalisableString Description => "Larger circles, more forgiving HP drain, less accuracy required, and multiple lives. As a bonus, it makes the map impossible to read!"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModHalfTime.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModHalfTime : ModHalfTime 9 | { 10 | public override double ScoreMultiplier => 0.3; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModHardRock.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModHardRock : ModHardRock 9 | { 10 | public override double ScoreMultiplier => 1.06; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModHidden.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Linq; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Localisation; 8 | using osu.Game.Beatmaps; 9 | using osu.Game.Configuration; 10 | using osu.Game.Rulesets.Mods; 11 | using osu.Game.Rulesets.Objects.Drawables; 12 | using osu.Game.Rulesets.Soyokaze.Objects; 13 | using osu.Game.Rulesets.Soyokaze.Objects.Drawables; 14 | 15 | namespace osu.Game.Rulesets.Soyokaze.Mods 16 | { 17 | public class SoyokazeModHidden : ModHidden 18 | { 19 | [SettingSource("Fading Approach Circle", "Allow approach circles to fade instead of disappearing")] 20 | public Bindable FadingApproachCircle { get; } = new BindableBool(true); 21 | 22 | public override double ScoreMultiplier => 1.06; 23 | public override LocalisableString Description => "Play with fading circles."; 24 | 25 | private const double fade_in_fraction = 0.4; 26 | private const double fade_out_fraction = 0.3; 27 | 28 | public override void ApplyToBeatmap(IBeatmap beatmap) 29 | { 30 | base.ApplyToBeatmap(beatmap); 31 | 32 | foreach (var hitObject in beatmap.HitObjects.OfType()) 33 | { 34 | hitObject.FadeIn = hitObject.Preempt * fade_in_fraction; 35 | foreach (var nested in hitObject.NestedHitObjects.OfType()) 36 | nested.FadeIn = nested.Preempt * fade_in_fraction; 37 | } 38 | } 39 | 40 | protected override void ApplyIncreasedVisibilityState(DrawableHitObject drawableObject, ArmedState state) 41 | { 42 | applyState(drawableObject, true); 43 | } 44 | 45 | protected override void ApplyNormalVisibilityState(DrawableHitObject drawableObject, ArmedState state) 46 | { 47 | applyState(drawableObject, false); 48 | } 49 | 50 | private void applyState(DrawableHitObject drawableObject, bool increaseVisibility) 51 | { 52 | if (!(drawableObject.HitObject is SoyokazeHitObject hitObject)) 53 | return; 54 | 55 | double fadeOutStartTime = hitObject.StartTime - hitObject.Preempt + hitObject.FadeIn; 56 | double fadeOutDuration; 57 | 58 | switch (hitObject) 59 | { 60 | case Hold h: 61 | fadeOutDuration = h.Duration + h.Preempt - h.FadeIn; 62 | break; 63 | default: 64 | fadeOutDuration = hitObject.Preempt * fade_out_fraction; 65 | break; 66 | } 67 | 68 | Drawable fadeTarget = drawableObject; 69 | switch (drawableObject) 70 | { 71 | case DrawableHitCircle circle: 72 | fadeTarget = circle.HitCircle; 73 | if (!increaseVisibility) 74 | if (FadingApproachCircle.Value) 75 | using (circle.BeginAbsoluteSequence(fadeOutStartTime)) 76 | { 77 | circle.ApproachCircle.FadeOut(fadeOutDuration * 0.8); 78 | } 79 | else 80 | using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.Preempt)) 81 | { 82 | circle.ApproachCircle.Hide(); 83 | } 84 | goto default; 85 | default: 86 | using (fadeTarget.BeginAbsoluteSequence(fadeOutStartTime)) 87 | { 88 | fadeTarget.FadeOut(fadeOutDuration); 89 | } 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModHolds.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics.Sprites; 5 | using osu.Framework.Localisation; 6 | using osu.Game.Rulesets.Mods; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.Mods 9 | { 10 | public class SoyokazeModHolds : Mod 11 | { 12 | public override string Name => "Holds [Obsolete]"; 13 | public override string Acronym => "HO"; 14 | public override LocalisableString Description => string.Empty; 15 | public override double ScoreMultiplier => 1.0; 16 | public override IconUsage? Icon => FontAwesome.Solid.Fingerprint; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModNightcore.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | using osu.Game.Rulesets.Soyokaze.Objects; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Mods 8 | { 9 | public class SoyokazeModNightcore : ModNightcore 10 | { 11 | public override double ScoreMultiplier => 1.12; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModNoFail.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModNoFail : ModNoFail 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModPerfect.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModPerfect : ModPerfect 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModRandom.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Localisation; 5 | using osu.Framework.Utils; 6 | using osu.Game.Beatmaps; 7 | using osu.Game.Rulesets.Mods; 8 | using osu.Game.Rulesets.Soyokaze.Objects; 9 | using osu.Game.Rulesets.Soyokaze.UI; 10 | 11 | namespace osu.Game.Rulesets.Soyokaze.Mods 12 | { 13 | public class SoyokazeModRandom : ModRandom, IApplicableToBeatmap 14 | { 15 | public override LocalisableString Description => @"Shuffle around the notes!"; 16 | 17 | public void ApplyToBeatmap(IBeatmap beatmap) 18 | { 19 | foreach (var obj in beatmap.HitObjects) 20 | { 21 | if (obj is SoyokazeHitObject hitObject) 22 | { 23 | if (obj is HoldCircle) continue; 24 | hitObject.Button = (SoyokazeAction)RNG.Next(8); 25 | if (hitObject is Hold hold) 26 | { 27 | foreach (var nestedObj in hold.NestedHitObjects) 28 | { 29 | if (nestedObj is SoyokazeHitObject nestedHitObject) 30 | nestedHitObject.Button = hitObject.Button; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModStaccato.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics.Sprites; 5 | using osu.Framework.Localisation; 6 | using osu.Game.Beatmaps; 7 | using osu.Game.Rulesets.Mods; 8 | using osu.Game.Rulesets.Soyokaze.Beatmaps; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.Mods 11 | { 12 | public class SoyokazeModStaccato : Mod, IApplicableToBeatmapConverter 13 | { 14 | public override string Name => "Staccato"; 15 | public override string Acronym => "ST"; 16 | public override LocalisableString Description => "We hate hold notes!"; 17 | public override double ScoreMultiplier => 0.86; 18 | public override IconUsage? Icon => FontAwesome.Regular.DotCircle; 19 | public override ModType Type => ModType.DifficultyReduction; 20 | 21 | public void ApplyToBeatmapConverter(IBeatmapConverter converter) 22 | { 23 | var soyokazeConverter = converter as SoyokazeBeatmapConverter; 24 | soyokazeConverter.CreateHolds.Value = false; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModSuddenDeath.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModSuddenDeath : ModSuddenDeath 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModWindDown.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModWindDown : ModWindDown 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Mods/SoyokazeModWindUp.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Mods; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Mods 7 | { 8 | public class SoyokazeModWindUp : ModWindUp 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Drawables/DrawableHitCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Game.Rulesets.Objects.Drawables; 7 | using osu.Game.Rulesets.Scoring; 8 | using osu.Game.Rulesets.Soyokaze.Skinning; 9 | using osu.Game.Rulesets.Soyokaze.UI; 10 | using osuTK; 11 | 12 | namespace osu.Game.Rulesets.Soyokaze.Objects.Drawables 13 | { 14 | public partial class DrawableHitCircle : DrawableSoyokazeHitObject 15 | { 16 | public new HitCircle HitObject => base.HitObject as HitCircle; 17 | public SkinnableApproachCircle ApproachCircle { get; private set; } 18 | public SkinnableHitCircle HitCircle { get; private set; } 19 | public Drawable ApproachCircleProxy => ApproachCircle; 20 | 21 | public DrawableHitCircle() 22 | : this(null) 23 | { 24 | } 25 | 26 | public DrawableHitCircle(HitCircle hitCircle = null) 27 | : base(hitCircle) 28 | { 29 | ApproachCircle = new SkinnableApproachCircle 30 | { 31 | Alpha = 0, 32 | Scale = new Vector2(4), 33 | }; 34 | HitCircle = new SkinnableHitCircle 35 | { 36 | Alpha = 0, 37 | Scale = new Vector2(1), 38 | }; 39 | Size = new Vector2(SoyokazeHitObject.OBJECT_RADIUS * 2); 40 | } 41 | 42 | [BackgroundDependencyLoader] 43 | private void load() 44 | { 45 | AddInternal(ApproachCircle); 46 | AddInternal(HitCircle); 47 | } 48 | 49 | protected override void UpdateInitialTransforms() 50 | { 51 | base.UpdateInitialTransforms(); 52 | 53 | HitCircle.FadeIn(HitObject.FadeIn); 54 | 55 | ApproachCircle.FadeInFromZero(System.Math.Min(HitObject.FadeIn * 2, HitObject.Preempt / 2)); 56 | ApproachCircle.ScaleTo(1f, HitObject.Preempt); 57 | ApproachCircle.Expire(true); 58 | } 59 | 60 | protected override void UpdateStartTimeStateTransforms() 61 | { 62 | base.UpdateStartTimeStateTransforms(); 63 | 64 | ApproachCircle.FadeOut(50); 65 | } 66 | 67 | protected override void UpdateHitStateTransforms(ArmedState state) 68 | { 69 | const double hit_duration = 400; 70 | const float hit_dilate = 1.5f; 71 | 72 | const double miss_duration = 200; 73 | const float miss_contract = 0.8f; 74 | const float miss_offset = 10f; 75 | 76 | switch (state) 77 | { 78 | case ArmedState.Hit: 79 | this.ScaleTo(Scale * hit_dilate, hit_duration, Easing.OutQuint); 80 | this.FadeOut(hit_duration, Easing.OutQuint); 81 | Expire(); 82 | break; 83 | 84 | case ArmedState.Miss: 85 | this.ScaleTo(Scale * miss_contract, miss_duration, Easing.OutQuint); 86 | this.MoveToOffset(new Vector2(0, miss_offset), miss_duration, Easing.In); 87 | this.FadeOut(miss_duration, Easing.OutQuint); 88 | Expire(); 89 | break; 90 | } 91 | } 92 | 93 | public override double LifetimeStart 94 | { 95 | get => base.LifetimeStart; 96 | set 97 | { 98 | base.LifetimeStart = value; 99 | ApproachCircle.LifetimeStart = value; 100 | } 101 | } 102 | 103 | public override double LifetimeEnd 104 | { 105 | get => base.LifetimeEnd; 106 | set 107 | { 108 | base.LifetimeEnd = value; 109 | ApproachCircle.LifetimeEnd = value; 110 | } 111 | } 112 | 113 | protected override void CheckForResult(bool userTriggered, double timeOffset) 114 | { 115 | if (!userTriggered) 116 | { 117 | if (!HitObject.HitWindows.CanBeHit(timeOffset)) 118 | ApplyResult(static (r, _) => r.Type = r.Judgement.MinResult, 0); 119 | return; 120 | } 121 | 122 | HitResult result = HitObject.HitWindows.ResultFor(timeOffset); 123 | 124 | if (result == HitResult.None) 125 | { 126 | // shake, but make sure not to exceed into the window where you can actually miss 127 | Shake(-timeOffset - HitObject.HitWindows.WindowFor(HitResult.Miss)); 128 | return; 129 | } 130 | 131 | ApplyResult(result); 132 | } 133 | 134 | public override bool Hit(SoyokazeAction action) 135 | { 136 | if (Judged) 137 | return false; 138 | 139 | SoyokazeAction validAction = ButtonBindable.Value; 140 | if (action != validAction) 141 | return false; 142 | 143 | UpdateResult(true); 144 | return true; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Drawables/DrawableHold.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using osu.Framework.Allocation; 8 | using osu.Framework.Graphics; 9 | using osu.Framework.Graphics.Containers; 10 | using osu.Game.Audio; 11 | using osu.Game.Rulesets.Judgements; 12 | using osu.Game.Rulesets.Objects; 13 | using osu.Game.Rulesets.Objects.Drawables; 14 | using osu.Game.Rulesets.Scoring; 15 | using osu.Game.Rulesets.Soyokaze.Judgements; 16 | using osu.Game.Rulesets.Soyokaze.Scoring; 17 | using osu.Game.Rulesets.Soyokaze.Skinning; 18 | using osu.Game.Rulesets.Soyokaze.UI; 19 | using osu.Game.Skinning; 20 | using osuTK; 21 | 22 | namespace osu.Game.Rulesets.Soyokaze.Objects.Drawables 23 | { 24 | public partial class DrawableHold : DrawableSoyokazeHitObject 25 | { 26 | public new Hold HitObject => base.HitObject as Hold; 27 | public DrawableHoldCircle HoldCircle => holdCircleContainer.Child; 28 | public SkinnableHoldProgress HoldProgress; 29 | 30 | private PausableSkinnableSound holdSamples; 31 | 32 | private Container holdCircleContainer; 33 | 34 | private double holdStartTime = double.MinValue; 35 | private double holdDuration = 0.0; 36 | 37 | public DrawableHold() 38 | : this(null) 39 | { 40 | } 41 | 42 | public DrawableHold(Hold hold = null) 43 | : base(hold) 44 | { 45 | Size = new Vector2(SoyokazeHitObject.OBJECT_RADIUS * 2); 46 | } 47 | 48 | [BackgroundDependencyLoader] 49 | private void load() 50 | { 51 | InternalChildren = new Drawable[] 52 | { 53 | holdCircleContainer = new Container 54 | { 55 | RelativeSizeAxes = Axes.Both, 56 | Origin = Anchor.Centre, 57 | Anchor = Anchor.Centre, 58 | }, 59 | HoldProgress = new SkinnableHoldProgress 60 | { 61 | RelativeSizeAxes = Axes.Both, 62 | Progress = 0f, 63 | }, 64 | holdSamples = new PausableSkinnableSound { Looping = true }, 65 | }; 66 | } 67 | 68 | protected override void OnApply() 69 | { 70 | base.OnApply(); 71 | 72 | holdStartTime = double.MinValue; 73 | holdDuration = 0.0; 74 | } 75 | 76 | protected override void OnFree() 77 | { 78 | base.OnFree(); 79 | 80 | holdSamples?.ClearSamples(); 81 | } 82 | 83 | protected override void LoadSamples() 84 | { 85 | base.LoadSamples(); 86 | 87 | if (HitObject.HoldSamples == null) 88 | return; 89 | 90 | var slidingSamples = new List(); 91 | 92 | var normalSample = HitObject.HoldSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); 93 | if (normalSample != null) 94 | slidingSamples.Add(normalSample.With("sliderslide")); 95 | 96 | var whistleSample = HitObject.HoldSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); 97 | if (whistleSample != null) 98 | slidingSamples.Add(whistleSample.With("sliderwhistle")); 99 | 100 | holdSamples.Samples = slidingSamples.ToArray(); 101 | } 102 | 103 | public override void StopAllSamples() 104 | { 105 | base.StopAllSamples(); 106 | 107 | holdSamples.Stop(); 108 | } 109 | 110 | protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) 111 | { 112 | switch (hitObject) 113 | { 114 | case HoldCircle holdCircle: 115 | return new DrawableHitCircle(holdCircle); 116 | } 117 | 118 | return base.CreateNestedHitObject(hitObject); 119 | } 120 | 121 | protected override void AddNestedHitObject(DrawableHitObject hitObject) 122 | { 123 | base.AddNestedHitObject(hitObject); 124 | 125 | switch (hitObject) 126 | { 127 | case DrawableHoldCircle holdCircle: 128 | holdCircleContainer.Child = holdCircle; 129 | break; 130 | } 131 | } 132 | 133 | protected override void ClearNestedHitObjects() 134 | { 135 | base.ClearNestedHitObjects(); 136 | holdCircleContainer.Clear(false); 137 | } 138 | 139 | protected override void UpdateInitialTransforms() 140 | { 141 | base.UpdateInitialTransforms(); 142 | 143 | HoldProgress.FadeInFromZero(Math.Min(HitObject.FadeIn * 2, HitObject.Preempt / 2)); 144 | using (BeginDelayedSequence(HitObject.Preempt)) 145 | { 146 | HoldProgress.ProgressTo(1f, HitObject.Duration); 147 | } 148 | } 149 | 150 | protected override void UpdateHitStateTransforms(ArmedState state) 151 | { 152 | const double hit_duration = 350; 153 | const float hit_dilate = 1.5f; 154 | 155 | const double miss_duration = 175; 156 | const float miss_contract = 0.9f; 157 | const float miss_offset = 10f; 158 | 159 | switch (state) 160 | { 161 | case ArmedState.Hit: 162 | HoldProgress.ScaleTo(HoldProgress.Scale * hit_dilate, hit_duration, Easing.OutQuint); 163 | HoldProgress.FadeOut(hit_duration, Easing.OutQuint); 164 | this.MoveToOffset(Vector2.Zero, hit_duration).Expire(); 165 | break; 166 | 167 | case ArmedState.Miss: 168 | HoldProgress.ScaleTo(HoldProgress.Scale * miss_contract, miss_duration, Easing.OutQuint); 169 | HoldProgress.MoveToOffset(new Vector2(0, miss_offset), miss_duration, Easing.In); 170 | HoldProgress.FadeOut(miss_duration, Easing.OutQuint); 171 | this.MoveToOffset(Vector2.Zero, miss_duration).Expire(); 172 | break; 173 | } 174 | } 175 | 176 | protected override JudgementResult CreateResult(Judgement judgement) => new SoyokazeHoldJudgementResult(HitObject, judgement); 177 | 178 | protected override void CheckForResult(bool userTriggered, double timeOffset) 179 | { 180 | if (userTriggered || Time.Current < HitObject.EndTime) 181 | return; 182 | 183 | // if the player releases the key after the hold is over, which is 184 | // what usually happens, then Release wouldn't be called if not for 185 | // this call below 186 | Release(ButtonBindable.Value); 187 | 188 | double holdFraction = holdDuration / HitObject.Duration; 189 | double holdCircleFraction = 0.0; 190 | 191 | JudgementResult trueResult = HoldCircle.TrueResult; 192 | if (trueResult != null) 193 | { 194 | SoyokazeScoreProcessor scorer = new SoyokazeScoreProcessor(); 195 | double score = scorer.GetBaseScoreForResult(trueResult.Type); 196 | double max = scorer.GetBaseScoreForResult(trueResult.Judgement.MaxResult); 197 | holdCircleFraction = score / max; 198 | } 199 | 200 | double scoreFraction = (holdCircleFraction + holdFraction) / 2; 201 | 202 | HitResult result; 203 | if (scoreFraction > 0.8) 204 | result = HitResult.Great; 205 | else if (scoreFraction > 0.5) 206 | result = HitResult.Ok; 207 | else if (scoreFraction > 0.3) 208 | result = HitResult.Meh; 209 | else 210 | result = HitResult.Miss; 211 | 212 | ApplyResult(static (r, p) => 213 | { 214 | if (!(r is SoyokazeHoldJudgementResult hr)) 215 | throw new InvalidOperationException($"Expected result of type {nameof(SoyokazeHoldJudgementResult)}"); 216 | 217 | hr.Type = p.result; 218 | hr.TrueTimeOffset = p.TrueTimeOffset; 219 | }, new { result, HoldCircle.TrueTimeOffset }); 220 | } 221 | 222 | public override bool Hit(SoyokazeAction action) 223 | { 224 | if (Judged) 225 | return false; 226 | 227 | SoyokazeAction validAction = ButtonBindable.Value; 228 | if (action != validAction) 229 | return false; 230 | 231 | holdStartTime = Time.Current; 232 | HoldCircle.Hit(action); 233 | 234 | const float hold_scale = 0.8f; 235 | const double hold_duration = 80; 236 | HoldProgress.ScaleTo(new Vector2(hold_scale), hold_duration, Easing.OutQuint); 237 | holdSamples.Play(); 238 | 239 | return true; 240 | } 241 | 242 | public bool Release(SoyokazeAction action) 243 | { 244 | if (Judged) 245 | return false; 246 | 247 | SoyokazeAction validAction = ButtonBindable.Value; 248 | if (action != validAction) 249 | return false; 250 | 251 | if (holdStartTime != double.MinValue) 252 | { 253 | holdDuration += Time.Current - holdStartTime; 254 | holdStartTime = double.MinValue; 255 | } 256 | 257 | const float hold_scale = 1f; 258 | const double hold_duration = 120; 259 | HoldProgress.ScaleTo(new Vector2(hold_scale), hold_duration, Easing.OutQuint); 260 | holdSamples.Stop(); 261 | 262 | return true; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Drawables/DrawableHoldCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Judgements; 5 | using osu.Game.Rulesets.Scoring; 6 | using osu.Game.Rulesets.Soyokaze.Judgements; 7 | using osu.Game.Rulesets.Soyokaze.UI; 8 | using osuTK; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.Objects.Drawables 11 | { 12 | public partial class DrawableHoldCircle : DrawableHitCircle 13 | { 14 | public new HoldCircle HitObject => (HoldCircle)base.HitObject; 15 | public override bool DisplayResult => false; 16 | public JudgementResult TrueResult { get; private set; } 17 | public double TrueTimeOffset { get; private set; } 18 | 19 | protected DrawableHold Hold => (DrawableHold)ParentHitObject; 20 | 21 | public override void Shake(double maximumLength) 22 | { 23 | Hold.Shake(maximumLength); 24 | } 25 | 26 | public override bool Hit(SoyokazeAction action) 27 | { 28 | if (Judged) 29 | return false; 30 | 31 | SoyokazeAction validAction = ButtonBindable.Value; 32 | if (action != validAction) 33 | return false; 34 | 35 | UpdateResult(true); 36 | 37 | return true; 38 | } 39 | 40 | protected override void CheckForResult(bool userTriggered, double timeOffset) 41 | { 42 | if (!userTriggered) 43 | { 44 | if (!HitObject.HitWindows.CanBeHit(timeOffset)) 45 | { 46 | TrueResult = new JudgementResult(HitObject, new SoyokazeJudgement()) { Type = HitResult.Miss }; 47 | TrueTimeOffset = timeOffset; 48 | ApplyResult(HitResult.IgnoreMiss); 49 | } 50 | return; 51 | } 52 | 53 | HitResult result = HitObject.HitWindows.ResultFor(timeOffset); 54 | 55 | if (result == HitResult.None) 56 | { 57 | // shake, but make sure not to exceed into the window where you can actually miss 58 | Shake(-timeOffset - HitObject.HitWindows.WindowFor(HitResult.Miss)); 59 | return; 60 | } 61 | 62 | TrueResult = new JudgementResult(HitObject, new SoyokazeJudgement()) { Type = result }; 63 | TrueTimeOffset = timeOffset; 64 | ApplyResult(HitResult.IgnoreHit); 65 | } 66 | 67 | protected override void UpdatePosition() 68 | { 69 | Position = Vector2.Zero; 70 | } 71 | 72 | protected override void UpdateScale() 73 | { 74 | Scale = Vector2.One; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Drawables/DrawableSoyokazeHitObject.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Game.Graphics.Containers; 8 | using osu.Game.Rulesets.Objects.Drawables; 9 | using osu.Game.Rulesets.Soyokaze.Configuration; 10 | using osu.Game.Rulesets.Soyokaze.Extensions; 11 | using osu.Game.Rulesets.Soyokaze.UI; 12 | using osuTK; 13 | 14 | namespace osu.Game.Rulesets.Soyokaze.Objects.Drawables 15 | { 16 | public abstract partial class DrawableSoyokazeHitObject : DrawableHitObject 17 | { 18 | public readonly Bindable ScaleBindable = new Bindable(); 19 | public readonly Bindable ButtonBindable = new Bindable(); 20 | public readonly Bindable IndexInCurrentComboBindable = new Bindable(); 21 | public readonly Bindable ScreenCenterGapBindable = new Bindable(); 22 | public readonly Bindable ObjectGapBindable = new Bindable(); 23 | 24 | protected override double InitialLifetimeOffset => HitObject.Preempt; 25 | 26 | private ShakeContainer shakeContainer; 27 | 28 | public DrawableSoyokazeHitObject(SoyokazeHitObject hitObject) 29 | : base(hitObject) 30 | { 31 | Alpha = 1; 32 | Origin = Anchor.Centre; 33 | Anchor = Anchor.Centre; 34 | } 35 | 36 | [BackgroundDependencyLoader(true)] 37 | private void load(SoyokazeConfigManager cm) 38 | { 39 | shakeContainer = new ShakeContainer() 40 | { 41 | ShakeDuration = 30, 42 | RelativeSizeAxes = Axes.Both, 43 | Origin = Anchor.Centre, 44 | Anchor = Anchor.Centre, 45 | }; 46 | base.AddInternal(shakeContainer); 47 | 48 | cm?.BindWith(SoyokazeConfig.ScreenCenterGap, ScreenCenterGapBindable); 49 | cm?.BindWith(SoyokazeConfig.ObjectGap, ObjectGapBindable); 50 | 51 | ScaleBindable.BindValueChanged(_ => UpdateScale(), true); 52 | ScreenCenterGapBindable.BindValueChanged(_ => UpdatePosition(), true); 53 | ObjectGapBindable.BindValueChanged(_ => UpdatePosition(), true); 54 | ButtonBindable.BindValueChanged(_ => UpdatePosition(), true); 55 | } 56 | 57 | public abstract bool Hit(SoyokazeAction action); 58 | 59 | public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength); 60 | 61 | protected virtual void UpdatePosition() 62 | { 63 | Vector2[] positions = PositionExtensions.GetPositions(ScreenCenterGapBindable.Value, ObjectGapBindable.Value, true, Anchor.Centre); 64 | Position = positions[(int)ButtonBindable.Value]; 65 | } 66 | 67 | protected virtual void UpdateScale() 68 | { 69 | Scale = new Vector2(ScaleBindable.Value); 70 | } 71 | 72 | protected override void OnApply() 73 | { 74 | base.OnApply(); 75 | 76 | ScaleBindable.BindTo(HitObject.ScaleBindable); 77 | ButtonBindable.BindTo(HitObject.ButtonBindable); 78 | IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable); 79 | } 80 | 81 | protected override void OnFree() 82 | { 83 | base.OnFree(); 84 | 85 | ScaleBindable.UnbindFrom(HitObject.ScaleBindable); 86 | ButtonBindable.UnbindFrom(HitObject.ButtonBindable); 87 | IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable); 88 | } 89 | 90 | protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable); 91 | protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren); 92 | protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Drawables/DrawableSoyokazeJudgement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Game.Rulesets.Judgements; 7 | using osu.Game.Rulesets.Objects.Drawables; 8 | using osu.Game.Rulesets.Scoring; 9 | using osu.Game.Rulesets.Soyokaze.Configuration; 10 | using osu.Game.Rulesets.Soyokaze.Extensions; 11 | using osu.Game.Rulesets.Soyokaze.UI; 12 | using osuTK; 13 | 14 | namespace osu.Game.Rulesets.Soyokaze.Objects.Drawables 15 | { 16 | public partial class DrawableSoyokazeJudgement : DrawableJudgement 17 | { 18 | [Resolved] 19 | private SoyokazeConfigManager configManager { get; set; } = null; 20 | 21 | public DrawableSoyokazeJudgement() 22 | : base() 23 | { 24 | Origin = Anchor.Centre; 25 | Anchor = Anchor.Centre; 26 | } 27 | 28 | protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultJudgementPiece(result); 29 | 30 | public override void Apply(JudgementResult result, DrawableHitObject judgedObject) 31 | { 32 | base.Apply(result, judgedObject); 33 | 34 | SoyokazeAction button = (judgedObject as DrawableSoyokazeHitObject)?.ButtonBindable.Value ?? default; 35 | int screenCenterDistance = configManager?.Get(SoyokazeConfig.ScreenCenterGap) ?? 0; 36 | int gap = configManager?.Get(SoyokazeConfig.ObjectGap) ?? 0; 37 | 38 | Vector2[] positions = PositionExtensions.GetPositions(screenCenterDistance, gap, true, Anchor.Centre); 39 | Position = positions[(int)button]; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/HitCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Objects 5 | { 6 | public class HitCircle : SoyokazeHitObject 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/Hold.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using osu.Game.Audio; 7 | using osu.Game.Rulesets.Objects.Types; 8 | 9 | namespace osu.Game.Rulesets.Soyokaze.Objects 10 | { 11 | public class Hold : SoyokazeHitObject, IHasDuration 12 | { 13 | private IList startSamples = new List(); 14 | public IList StartSamples 15 | { 16 | get => holdCircle?.Samples ?? startSamples; 17 | set 18 | { 19 | startSamples = value; 20 | if (holdCircle != null) 21 | holdCircle.Samples = value; 22 | } 23 | } 24 | 25 | public IList HoldSamples { get; set; } = new List(); 26 | 27 | public IList EndSamples 28 | { 29 | get => Samples; 30 | set => Samples = value; 31 | } 32 | 33 | private HoldCircle holdCircle; 34 | 35 | public double EndTime 36 | { 37 | get => StartTime + Duration; 38 | set => Duration = value - StartTime; 39 | } 40 | 41 | public double Duration { get; set; } 42 | 43 | protected override void CreateNestedHitObjects(CancellationToken cancellationToken) 44 | { 45 | base.CreateNestedHitObjects(cancellationToken); 46 | 47 | AddNested(holdCircle = new HoldCircle 48 | { 49 | Button = Button, 50 | Samples = StartSamples, 51 | StartTime = StartTime, 52 | NewCombo = NewCombo, 53 | ComboOffset = ComboOffset, 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/HoldCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Judgements; 5 | using osu.Game.Rulesets.Soyokaze.Judgements; 6 | 7 | namespace osu.Game.Rulesets.Soyokaze.Objects 8 | { 9 | public class HoldCircle : HitCircle 10 | { 11 | public override Judgement CreateJudgement() => new SoyokazeIgnoreJudgement(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/SoyokazeHitObject.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Bindables; 5 | using osu.Game.Beatmaps; 6 | using osu.Game.Beatmaps.ControlPoints; 7 | using osu.Game.Rulesets.Judgements; 8 | using osu.Game.Rulesets.Objects; 9 | using osu.Game.Rulesets.Objects.Types; 10 | using osu.Game.Rulesets.Scoring; 11 | using osu.Game.Rulesets.Soyokaze.Judgements; 12 | using osu.Game.Rulesets.Soyokaze.Scoring; 13 | using osu.Game.Rulesets.Soyokaze.UI; 14 | 15 | namespace osu.Game.Rulesets.Soyokaze.Objects 16 | { 17 | public class SoyokazeHitObject : HitObject, IHasComboInformation 18 | { 19 | public const float OBJECT_RADIUS = 64; 20 | 21 | public const double BASE_PREEMPT = 450; 22 | 23 | public const double BASE_FADEIN = 400; 24 | 25 | public readonly Bindable ButtonBindable = new Bindable(); 26 | 27 | public SoyokazeAction Button 28 | { 29 | get => ButtonBindable.Value; 30 | set => ButtonBindable.Value = value; 31 | } 32 | 33 | public readonly Bindable ScaleBindable = new Bindable(1f); 34 | 35 | public float Scale 36 | { 37 | get => ScaleBindable.Value; 38 | set => ScaleBindable.Value = value; 39 | } 40 | 41 | public double Preempt = BASE_PREEMPT; 42 | public double FadeIn = BASE_FADEIN; 43 | 44 | // HitObject Impl ---------------------------------------------- 45 | protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) 46 | { 47 | base.ApplyDefaultsToSelf(controlPointInfo, difficulty); 48 | 49 | Preempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, BASE_PREEMPT); 50 | 51 | // i refuse to use Math.Min because min funcs are the hardest thing to read i swear. even though i see them it all the time in math LOL 52 | FadeIn = BASE_FADEIN; 53 | if (Preempt < BASE_PREEMPT) 54 | FadeIn *= Preempt / BASE_PREEMPT; 55 | 56 | Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) * 0.8f; // idk why, but the scaling is off by a factor of 1.6 from std. 57 | } 58 | 59 | public override Judgement CreateJudgement() => new SoyokazeJudgement(); 60 | 61 | protected override HitWindows CreateHitWindows() => new SoyokazeHitWindows(); 62 | 63 | // IHasComboInformation Impl ----------------------------------- 64 | 65 | public readonly Bindable ComboOffsetBindable = new Bindable(); 66 | 67 | public int ComboOffset 68 | { 69 | get => ComboOffsetBindable.Value; 70 | set => ComboOffsetBindable.Value = value; 71 | } 72 | 73 | public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); 74 | 75 | public int IndexInCurrentCombo 76 | { 77 | get => IndexInCurrentComboBindable.Value; 78 | set => IndexInCurrentComboBindable.Value = value; 79 | } 80 | 81 | public Bindable ComboIndexBindable { get; } = new Bindable(); 82 | 83 | public int ComboIndex 84 | { 85 | get => ComboIndexBindable.Value; 86 | set => ComboIndexBindable.Value = value; 87 | } 88 | 89 | public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); 90 | 91 | public int ComboIndexWithOffsets 92 | { 93 | get => ComboIndexWithOffsetsBindable.Value; 94 | set => ComboIndexWithOffsetsBindable.Value = value; 95 | } 96 | 97 | public Bindable LastInComboBindable { get; } = new Bindable(); 98 | 99 | public bool LastInCombo 100 | { 101 | get => LastInComboBindable.Value; 102 | set => LastInComboBindable.Value = value; 103 | } 104 | 105 | public bool NewCombo { get; set; } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Objects/SoyokazeHitObjectLifetimeEntry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Objects; 5 | using osu.Game.Rulesets.Scoring; 6 | using osu.Game.Rulesets.Soyokaze.Objects; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.UI 9 | { 10 | internal class SoyokazeHitObjectLifetimeEntry : HitObjectLifetimeEntry 11 | { 12 | public SoyokazeHitObjectLifetimeEntry(HitObject hitObject) 13 | : base(hitObject) 14 | { 15 | LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss); 16 | } 17 | 18 | protected override double InitialLifetimeOffset => (HitObject as SoyokazeHitObject)?.Preempt ?? SoyokazeHitObject.BASE_PREEMPT; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Replays/SoyokazeAutoGenerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Game.Beatmaps; 6 | using osu.Game.Replays; 7 | using osu.Game.Rulesets.Mods; 8 | using osu.Game.Rulesets.Replays; 9 | using osu.Game.Rulesets.Soyokaze.Objects; 10 | using osu.Game.Rulesets.Soyokaze.UI; 11 | using osuTK; 12 | 13 | namespace osu.Game.Rulesets.Soyokaze.Replays 14 | { 15 | public class SoyokazeAutoGenerator : AutoGenerator 16 | { 17 | private Replay replay; 18 | private LinkedList releaseFrameTimes = new LinkedList(); 19 | private KeyPress[] keyPresses = new KeyPress[8]; 20 | 21 | private const double default_press_duration = 75.0; 22 | private const double default_press_safety_buffer = 5.0; 23 | 24 | public new Beatmap Beatmap => (Beatmap)base.Beatmap; 25 | 26 | public SoyokazeAutoGenerator(IBeatmap beatmap, IReadOnlyList mods) 27 | : base(beatmap) 28 | { 29 | replay = new Replay(); 30 | } 31 | 32 | public override Replay Generate() 33 | { 34 | replay.Frames.Clear(); 35 | releaseFrameTimes.Clear(); 36 | for (int i = 0; i < keyPresses.Length; i++) 37 | keyPresses[i] = new KeyPress 38 | { 39 | Start = -1.0, 40 | End = -1.0, 41 | }; 42 | 43 | addOrderedFrame(new SoyokazeReplayFrame(-1000, Vector2.Zero)); 44 | 45 | for (int i = 0; i < Beatmap.HitObjects.Count; i++) 46 | { 47 | SoyokazeHitObject currentObject = Beatmap.HitObjects[i]; 48 | SoyokazeAction currentButton = currentObject.Button; 49 | 50 | for (var node = releaseFrameTimes.First; node != null; node = node.Next) 51 | { 52 | if (node.Value >= currentObject.StartTime) 53 | continue; 54 | 55 | addOrderedFrame(generateReplayFrame(node.Value)); 56 | releaseFrameTimes.Remove(node); 57 | } 58 | 59 | double pressDuration; 60 | switch (currentObject) 61 | { 62 | case Hold hold: 63 | pressDuration = hold.Duration; 64 | break; 65 | default: 66 | pressDuration = default_press_duration; 67 | break; 68 | } 69 | 70 | // Determine press duration 71 | for (int j = i + 1; j < Beatmap.HitObjects.Count; j++) 72 | { 73 | SoyokazeHitObject nextObject = Beatmap.HitObjects[j]; 74 | if (nextObject.Button == currentButton) 75 | { 76 | double delta = nextObject.StartTime - currentObject.StartTime; 77 | double safeDelta = (delta <= default_press_safety_buffer) ? (delta / 2) : (delta - default_press_safety_buffer); 78 | 79 | if (pressDuration > safeDelta) 80 | pressDuration = safeDelta; 81 | 82 | break; 83 | } 84 | } 85 | 86 | keyPresses[(int)currentButton].Start = currentObject.StartTime; 87 | keyPresses[(int)currentButton].End = currentObject.StartTime + pressDuration; 88 | addOrderedFrame(generateReplayFrame(currentObject.StartTime)); 89 | releaseFrameTimes.AddLast(currentObject.StartTime + pressDuration); 90 | } 91 | 92 | for (var node = releaseFrameTimes.First; node != null; node = node.Next) 93 | addOrderedFrame(generateReplayFrame(node.Value)); 94 | 95 | return replay; 96 | } 97 | 98 | private struct KeyPress 99 | { 100 | public double Start; 101 | public double End; 102 | } 103 | 104 | private SoyokazeReplayFrame generateReplayFrame(double time) 105 | { 106 | List buttons = new List(); 107 | 108 | for (int i = 0; i < keyPresses.Length; i++) 109 | if (keyPresses[i].Start <= time && time < keyPresses[i].End) 110 | buttons.Add((SoyokazeAction)i); 111 | 112 | return new SoyokazeReplayFrame(time, Vector2.Zero, buttons.ToArray()); 113 | } 114 | 115 | private class FrameComparer : IComparer 116 | { 117 | public int Compare(ReplayFrame x, ReplayFrame y) 118 | { 119 | if (x.Time < y.Time) 120 | return -1; 121 | else if (x.Time == y.Time) 122 | return 0; 123 | else 124 | return 1; 125 | } 126 | } 127 | 128 | private void addOrderedFrame(SoyokazeReplayFrame frame) 129 | { 130 | #region Unused binary search algorithm 131 | 132 | /* Below is a binary search algorithm that osu!standard uses, but in 133 | * practice, I'm pretty sure it's actually slower than just working 134 | * backwards, because generally speaking, mismatched frames are gonna 135 | * be at the back and not at the front of the List when working with 136 | * autoplay generation. 137 | int index = Frames.BinarySearch(frame, new FrameComparer()); 138 | if (index < 0) 139 | index = ~index; 140 | else 141 | // Go to the first index which is actually bigger 142 | for (; index < Frames.Count && frame.Time == Frames[index].Time; index++) ; 143 | */ 144 | 145 | #endregion 146 | 147 | int index = replay.Frames.Count; 148 | while (index > 0 && replay.Frames[index - 1].Time > frame.Time) 149 | index--; 150 | 151 | replay.Frames.Insert(index, frame); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Replays/SoyokazeFramedReplayInputHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Framework.Input.StateChanges; 6 | using osu.Framework.Utils; 7 | using osu.Game.Replays; 8 | using osu.Game.Rulesets.Replays; 9 | using osu.Game.Rulesets.Soyokaze.UI; 10 | using osuTK; 11 | 12 | namespace osu.Game.Rulesets.Soyokaze.Replays 13 | { 14 | public class SoyokazeFramedReplayInputHandler : FramedReplayInputHandler 15 | { 16 | public SoyokazeFramedReplayInputHandler(Replay replay) 17 | : base(replay) 18 | { 19 | } 20 | 21 | protected override bool IsImportant(SoyokazeReplayFrame frame) => true; 22 | 23 | protected override void CollectReplayInputs(List inputs) 24 | { 25 | Vector2 position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); 26 | inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) }); 27 | inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Replays/SoyokazeReplayFrame.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Game.Beatmaps; 6 | using osu.Game.Replays.Legacy; 7 | using osu.Game.Rulesets.Replays; 8 | using osu.Game.Rulesets.Replays.Types; 9 | using osu.Game.Rulesets.Soyokaze.UI; 10 | using osuTK; 11 | 12 | namespace osu.Game.Rulesets.Soyokaze.Replays 13 | { 14 | public class SoyokazeReplayFrame : ReplayFrame, IConvertibleReplayFrame 15 | { 16 | public Vector2 Position; // DEPRECATED: see SoyokazeCursorContainer 17 | public List Actions = new List(); 18 | 19 | private enum SoyokazeActionFlag 20 | { 21 | None = 0, 22 | Button0 = 0b_0000_0001, 23 | Button1 = 0b_0000_0010, 24 | Button2 = 0b_0000_0100, 25 | Button3 = 0b_0000_1000, 26 | Button4 = 0b_0001_0000, 27 | Button5 = 0b_0010_0000, 28 | Button6 = 0b_0100_0000, 29 | Button7 = 0b_1000_0000 30 | } 31 | 32 | public SoyokazeReplayFrame() 33 | { 34 | } 35 | 36 | public SoyokazeReplayFrame(double time, Vector2 position, params SoyokazeAction[] buttons) 37 | : base(time) 38 | { 39 | Position = position; 40 | Actions.AddRange(buttons); 41 | } 42 | 43 | public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) 44 | { 45 | Position = currentFrame.Position; 46 | SoyokazeActionFlag soyokazeButtonFlags = (SoyokazeActionFlag)currentFrame.ButtonState; 47 | 48 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button0)) 49 | Actions.Add(SoyokazeAction.Button0); 50 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button1)) 51 | Actions.Add(SoyokazeAction.Button1); 52 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button2)) 53 | Actions.Add(SoyokazeAction.Button2); 54 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button3)) 55 | Actions.Add(SoyokazeAction.Button3); 56 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button4)) 57 | Actions.Add(SoyokazeAction.Button4); 58 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button5)) 59 | Actions.Add(SoyokazeAction.Button5); 60 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button6)) 61 | Actions.Add(SoyokazeAction.Button6); 62 | if (soyokazeButtonFlags.HasFlag(SoyokazeActionFlag.Button7)) 63 | Actions.Add(SoyokazeAction.Button7); 64 | } 65 | 66 | public LegacyReplayFrame ToLegacy(IBeatmap beatmap) 67 | { 68 | SoyokazeActionFlag soyokazeButtonFlags = SoyokazeActionFlag.None; 69 | 70 | if (Actions.Contains(SoyokazeAction.Button0)) 71 | soyokazeButtonFlags |= SoyokazeActionFlag.Button0; 72 | if (Actions.Contains(SoyokazeAction.Button1)) 73 | soyokazeButtonFlags |= SoyokazeActionFlag.Button1; 74 | if (Actions.Contains(SoyokazeAction.Button2)) 75 | soyokazeButtonFlags |= SoyokazeActionFlag.Button2; 76 | if (Actions.Contains(SoyokazeAction.Button3)) 77 | soyokazeButtonFlags |= SoyokazeActionFlag.Button3; 78 | if (Actions.Contains(SoyokazeAction.Button4)) 79 | soyokazeButtonFlags |= SoyokazeActionFlag.Button4; 80 | if (Actions.Contains(SoyokazeAction.Button5)) 81 | soyokazeButtonFlags |= SoyokazeActionFlag.Button5; 82 | if (Actions.Contains(SoyokazeAction.Button6)) 83 | soyokazeButtonFlags |= SoyokazeActionFlag.Button6; 84 | if (Actions.Contains(SoyokazeAction.Button7)) 85 | soyokazeButtonFlags |= SoyokazeActionFlag.Button7; 86 | 87 | return new LegacyReplayFrame(Time, Position.X, Position.Y, (ReplayButtonState)soyokazeButtonFlags); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/approachcircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/approachcircle.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/cursor.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/hitcircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/hitcircle.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/hitcircleoverlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/hitcircleoverlay.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/holdoverlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/holdoverlay.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/holdoverlaybackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/holdoverlaybackground.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/inputoverlaybackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/inputoverlaybackground.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/inputoverlaykey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/inputoverlaykey.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/kiaivisualizersquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/Gameplay/soyokaze/kiaivisualizersquare.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/icon.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Resources/Textures/icon_hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goodtrailer/soyokaze/8923226efb5f98b6d461040ea790bfe9471b6fd8/osu.Game.Rulesets.Soyokaze/Resources/Textures/icon_hi.png -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Scoring/SoyokazeHitWindows.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Scoring; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Scoring 7 | { 8 | public class SoyokazeHitWindows : HitWindows 9 | { 10 | internal static readonly DifficultyRange[] SOYOKAZE_RANGES = 11 | { 12 | new DifficultyRange(HitResult.Great, 80, 50, 25), 13 | new DifficultyRange(HitResult.Ok, 140, 100, 70), 14 | new DifficultyRange(HitResult.Meh, 200, 150, 115), 15 | new DifficultyRange(HitResult.Miss, 400, 400, 400), 16 | }; 17 | 18 | public override bool IsHitResultAllowed(HitResult result) 19 | { 20 | switch (result) 21 | { 22 | case HitResult.Great: 23 | case HitResult.Ok: 24 | case HitResult.Meh: 25 | case HitResult.Miss: 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | protected override DifficultyRange[] GetRanges() => SOYOKAZE_RANGES; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Scoring/SoyokazeScoreProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Rulesets.Judgements; 5 | using osu.Game.Rulesets.Scoring; 6 | using osu.Game.Rulesets.Soyokaze.Judgements; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.Scoring 9 | { 10 | public partial class SoyokazeScoreProcessor : ScoreProcessor 11 | { 12 | public SoyokazeScoreProcessor(SoyokazeRuleset ruleset = null) 13 | : base(ruleset ?? new SoyokazeRuleset()) 14 | { 15 | } 16 | 17 | protected override HitEvent CreateHitEvent(JudgementResult result) 18 | { 19 | var hitEvent = base.CreateHitEvent(result); 20 | 21 | if (result is SoyokazeHoldJudgementResult hr) 22 | hitEvent = new HitEvent(hr.TrueTimeOffset, hitEvent.GameplayRate, hitEvent.Result, hitEvent.HitObject, hitEvent.LastHitObject, hitEvent.Position); 23 | 24 | return hitEvent; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/AbstractDefaultComponent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Framework.Graphics.Containers; 7 | using osu.Framework.Graphics.Sprites; 8 | using osu.Framework.Graphics.Textures; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 11 | { 12 | public abstract partial class AbstractDefaultComponent : CompositeDrawable 13 | { 14 | public virtual Texture Texture { get; init; } 15 | 16 | public AbstractDefaultComponent() 17 | { 18 | Anchor = Anchor.Centre; 19 | Origin = Anchor.Centre; 20 | } 21 | 22 | protected abstract SoyokazeSkinComponents Component { get; } 23 | 24 | [BackgroundDependencyLoader] 25 | private void load(TextureStore textures) 26 | { 27 | string storeName = $"Gameplay/{SoyokazeRuleset.SHORT_NAME}/{Component.ToString().ToLower()}"; 28 | AddInternal(new Sprite 29 | { 30 | Anchor = Anchor.Centre, 31 | Origin = Anchor.Centre, 32 | Texture = Texture ?? textures.Get(storeName), 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultApproachCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultApproachCircle : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.ApproachCircle; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultCursor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultCursor : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.Cursor; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultHitCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultHitCircle : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.HitCircle; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultHitCircleOverlay.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultHitCircleOverlay : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.HitCircleOverlay; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultHitCircleText.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics.Sprites; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 7 | { 8 | public partial class DefaultHitCircleText : SpriteText 9 | { 10 | public DefaultHitCircleText() 11 | { 12 | Shadow = true; 13 | Font = FontUsage.Default.With(size: 40); 14 | UseFullGlyphHeight = true; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultHoldOverlay.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 7 | { 8 | public partial class DefaultHoldOverlay : AbstractDefaultComponent 9 | { 10 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.HoldOverlay; 11 | 12 | public DefaultHoldOverlay() 13 | { 14 | AutoSizeAxes = Axes.Both; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultHoldOverlayBackground.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultHoldOverlayBackground : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.HoldOverlayBackground; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultInputOverlayBackground.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultInputOverlayBackground : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.InputOverlayBackground; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultInputOverlayKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultInputOverlayKey : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.InputOverlayKey; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Defaults/DefaultKiaiVisualizerSquare.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Defaults 5 | { 6 | public partial class DefaultKiaiVisualizerSquare : AbstractDefaultComponent 7 | { 8 | protected override SoyokazeSkinComponents Component => SoyokazeSkinComponents.KiaiVisualizerSquare; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/Legacy/SoyokazeLegacySkinTransformer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Textures; 8 | using osu.Game.Rulesets.Scoring; 9 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 10 | using osu.Game.Skinning; 11 | using osuTK; 12 | 13 | namespace osu.Game.Rulesets.Soyokaze.Skinning.Legacy 14 | { 15 | public class SoyokazeLegacySkinTransformer : LegacySkinTransformer 16 | { 17 | public SoyokazeLegacySkinTransformer(ISkin skin) 18 | : base(skin) 19 | { 20 | } 21 | 22 | public override Drawable GetDrawableComponent(ISkinComponentLookup lookup) 23 | { 24 | switch (lookup) 25 | { 26 | // This was taken from LegacySkin.GetDrawableComponent(ISkinComponentLookup lookup) 27 | case SkinComponentLookup resultLookup: 28 | // kind of wasteful that we throw this away, but should do for now. 29 | if (getJudgementAnimation(resultLookup.Component) != null) 30 | { 31 | // TODO: this should be inside the judgement pieces. 32 | Func createDrawable = () => getJudgementAnimation(resultLookup.Component); 33 | 34 | var particle = getParticleTexture(resultLookup.Component); 35 | 36 | if (particle != null) 37 | return new LegacyJudgementPieceNew(resultLookup.Component, createDrawable, particle); 38 | 39 | return new LegacyJudgementPieceOld(resultLookup.Component, createDrawable); 40 | } 41 | 42 | return null; 43 | 44 | case SoyokazeSkinComponentLookup soyokazeLookup: 45 | string fallbackName = soyokazeLookup.Component.ToString().ToLower(); 46 | string primaryName = $"{SoyokazeRuleset.SHORT_NAME}/{fallbackName}"; 47 | Texture texture = GetTexture(primaryName) ?? GetTexture(fallbackName); 48 | 49 | switch (soyokazeLookup.Component) 50 | { 51 | case SoyokazeSkinComponents.ApproachCircle: 52 | return new DefaultApproachCircle { Texture = texture }; 53 | 54 | case SoyokazeSkinComponents.Cursor: 55 | return new DefaultCursor { Texture = texture }; 56 | 57 | case SoyokazeSkinComponents.HitCircle: 58 | return new DefaultHitCircle { Texture = texture }; 59 | 60 | case SoyokazeSkinComponents.HitCircleOverlay: 61 | return new DefaultHitCircleOverlay { Texture = texture }; 62 | 63 | case SoyokazeSkinComponents.HoldOverlay: 64 | return new DefaultHoldOverlay { Texture = texture }; 65 | 66 | case SoyokazeSkinComponents.HoldOverlayBackground: 67 | return new DefaultHoldOverlayBackground { Texture = texture }; 68 | 69 | case SoyokazeSkinComponents.InputOverlayKey: 70 | return new DefaultInputOverlayKey { Texture = texture }; 71 | 72 | case SoyokazeSkinComponents.InputOverlayBackground: 73 | return new DefaultInputOverlayBackground { Texture = texture }; 74 | 75 | case SoyokazeSkinComponents.KiaiVisualizerSquare: 76 | return new DefaultKiaiVisualizerSquare { Texture = texture }; 77 | 78 | case SoyokazeSkinComponents.HitCircleText: 79 | if (!this.HasFont(LegacyFont.HitCircle)) 80 | return null; 81 | 82 | // stable applies a blanket 0.8x scale to hitcircle fonts; 83 | // see OsuLegacySkinTransformer.GetDrawableComponent(ISkinComponentLookup lookup) 84 | const float hitcircle_text_scale = 0.8f; 85 | return new LegacySpriteText(LegacyFont.HitCircle) 86 | { 87 | Scale = new Vector2(hitcircle_text_scale), 88 | }; 89 | 90 | default: 91 | return base.GetDrawableComponent(lookup); 92 | } 93 | default: 94 | return null; 95 | } 96 | } 97 | 98 | public override IBindable GetConfig(TLookup lookup) 99 | { 100 | switch (lookup) 101 | { 102 | case SoyokazeSkinColour colour: 103 | return base.GetConfig(new SkinCustomColourLookup(colour)); 104 | default: 105 | return base.GetConfig(lookup); 106 | } 107 | } 108 | 109 | // This function was copied from LegacySkin.getJudgementAnimation(HitResult result) 110 | private Drawable getJudgementAnimation(HitResult result) 111 | { 112 | switch (result) 113 | { 114 | case HitResult.Miss: 115 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/hit0", true, false) ?? this.GetAnimation("hit0", true, false); 116 | 117 | case HitResult.LargeTickMiss: 118 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/slidertickmiss", true, false) ?? this.GetAnimation("slidertickmiss", true, false); 119 | 120 | case HitResult.IgnoreMiss: 121 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/sliderendmiss", true, false) ?? this.GetAnimation("sliderendmiss", true, false); 122 | 123 | case HitResult.Meh: 124 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/hit50", true, false) ?? this.GetAnimation("hit50", true, false); 125 | 126 | case HitResult.Ok: 127 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/hit100", true, false) ?? this.GetAnimation("hit100", true, false); 128 | 129 | case HitResult.Great: 130 | return this.GetAnimation($"{SoyokazeRuleset.SHORT_NAME}/hit300", true, false) ?? this.GetAnimation("hit300", true, false); 131 | } 132 | 133 | return null; 134 | } 135 | 136 | // This function was copied from LegacySkin.getParticleTexture(HitResult result) 137 | private Texture getParticleTexture(HitResult result) 138 | { 139 | switch (result) 140 | { 141 | case HitResult.Meh: 142 | return GetTexture($"{SoyokazeRuleset.SHORT_NAME}/particle50") ?? GetTexture("particle50"); 143 | 144 | case HitResult.Ok: 145 | return GetTexture($"{SoyokazeRuleset.SHORT_NAME}/particle100") ?? GetTexture("particle100"); 146 | 147 | case HitResult.Great: 148 | return GetTexture($"{SoyokazeRuleset.SHORT_NAME}/particle300") ?? GetTexture("particle300"); 149 | } 150 | 151 | return null; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableApproachCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Game.Rulesets.Objects.Drawables; 9 | using osu.Game.Rulesets.Soyokaze.Configuration; 10 | using osu.Game.Rulesets.Soyokaze.Extensions; 11 | using osu.Game.Rulesets.Soyokaze.Objects.Drawables; 12 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 13 | using osu.Game.Rulesets.Soyokaze.UI; 14 | using osu.Game.Skinning; 15 | using osuTK.Graphics; 16 | 17 | namespace osu.Game.Rulesets.Soyokaze.Skinning 18 | { 19 | public partial class SkinnableApproachCircle : Container 20 | { 21 | public override bool RemoveWhenNotAlive => false; 22 | 23 | [Resolved] 24 | private DrawableHitObject drawableObject { get; set; } 25 | 26 | private readonly Bindable accentColourBindable = new Bindable(); 27 | private readonly Bindable buttonBindable = new Bindable(); 28 | private readonly Bindable highlightColourEnumBindable = new Bindable(); 29 | private readonly Bindable highlightBindable = new Bindable(); 30 | private Drawable approachCircle; 31 | private Colour4 highlightColour; 32 | 33 | public SkinnableApproachCircle() 34 | { 35 | Anchor = Anchor.Centre; 36 | Origin = Anchor.Centre; 37 | 38 | approachCircle = new SkinnableDrawable( 39 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.ApproachCircle), 40 | _ => new DefaultApproachCircle() 41 | ) 42 | { 43 | Anchor = Anchor.Centre, 44 | Origin = Anchor.Centre, 45 | }; 46 | } 47 | 48 | [BackgroundDependencyLoader] 49 | private void load(ISkinSource skin, SoyokazeConfigManager cm) 50 | { 51 | AddInternal(approachCircle); 52 | 53 | DrawableSoyokazeHitObject drawableSoyokazeObject = (DrawableSoyokazeHitObject)drawableObject; 54 | 55 | accentColourBindable.BindTo(drawableSoyokazeObject.AccentColour); 56 | buttonBindable.BindTo(drawableSoyokazeObject.ButtonBindable); 57 | 58 | accentColourBindable.BindValueChanged(colourChanged => 59 | { 60 | if (!highlightBindable.Value) 61 | approachCircle.Colour = colourChanged.NewValue; 62 | }, true); 63 | buttonBindable.BindValueChanged(buttonChanged => 64 | { 65 | bool doRotation = skin.GetConfig(SoyokazeSkinConfiguration.RotateApproachCircle)?.Value ?? false; 66 | approachCircle.Rotation = doRotation ? PositionExtensions.ButtonToRotation(buttonChanged.NewValue) : 0f; 67 | }, true); 68 | 69 | if (drawableSoyokazeObject is DrawableHoldCircle) 70 | { 71 | cm?.BindWith(SoyokazeConfig.HoldHighlightColour, highlightColourEnumBindable); 72 | highlightColourEnumBindable.BindValueChanged(colourEnumChanged => 73 | { 74 | if (colourEnumChanged.NewValue == ColourEnum.None) 75 | highlightColour = skin.GetConfig(SoyokazeSkinColour.HoldHighlight)?.Value ?? Colour4.Lime; 76 | else 77 | highlightColour = ColourExtensions.ToColour4(colourEnumChanged.NewValue); 78 | 79 | if (highlightBindable.Value) 80 | approachCircle.Colour = highlightColour; 81 | }, true); 82 | 83 | cm?.BindWith(SoyokazeConfig.HighlightHolds, highlightBindable); 84 | highlightBindable.BindValueChanged(valueChanged => 85 | { 86 | if (valueChanged.NewValue) 87 | approachCircle.Colour = highlightColour; 88 | else 89 | approachCircle.Colour = accentColourBindable.Value; 90 | }, true); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableCursor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Framework.Graphics.Containers; 7 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 8 | using osu.Game.Skinning; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.Skinning 11 | { 12 | // DEPRECATED: see SoyokazeCursorContainer 13 | public partial class SkinnableCursor : Container 14 | { 15 | public SkinnableCursor() 16 | { 17 | Origin = Anchor.Centre; 18 | } 19 | 20 | [BackgroundDependencyLoader] 21 | private void load() 22 | { 23 | AddInternal(new SkinnableDrawable(new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.Cursor), _ => new DefaultCursor())); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableHitCircle.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Game.Rulesets.Objects.Drawables; 9 | using osu.Game.Rulesets.Soyokaze.Extensions; 10 | using osu.Game.Rulesets.Soyokaze.Objects.Drawables; 11 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 12 | using osu.Game.Rulesets.Soyokaze.UI; 13 | using osu.Game.Skinning; 14 | using osuTK.Graphics; 15 | 16 | namespace osu.Game.Rulesets.Soyokaze.Skinning 17 | { 18 | public partial class SkinnableHitCircle : Container 19 | { 20 | [Resolved] 21 | private DrawableHitObject drawableObject { get; set; } 22 | 23 | private readonly Bindable accentColourBindable = new Bindable(); 24 | private readonly Bindable indexInCurrentComboBindable = new Bindable(); 25 | private readonly Bindable buttonBindable = new Bindable(); 26 | 27 | private SkinnableDrawable hitCircle; 28 | private SkinnableDrawable hitCircleOverlay; 29 | private SkinnableSpriteText hitCircleText; 30 | 31 | private const bool use_default_text = false; 32 | 33 | public SkinnableHitCircle() 34 | { 35 | Anchor = Anchor.Centre; 36 | Origin = Anchor.Centre; 37 | RelativeSizeAxes = Axes.Both; 38 | Masking = false; 39 | 40 | hitCircle = new SkinnableDrawable( 41 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.HitCircle), 42 | _ => new DefaultHitCircle() 43 | ) 44 | { 45 | Anchor = Anchor.Centre, 46 | Origin = Anchor.Centre, 47 | }; 48 | 49 | hitCircleOverlay = new SkinnableDrawable( 50 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.HitCircleOverlay), 51 | _ => new DefaultHitCircleOverlay() 52 | ) 53 | { 54 | Anchor = Anchor.Centre, 55 | Origin = Anchor.Centre, 56 | }; 57 | 58 | hitCircleText = new SkinnableSpriteText( 59 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.HitCircleText), 60 | _ => new DefaultHitCircleText() { Alpha = use_default_text ? 1 : 0 }, 61 | confineMode: ConfineMode.NoScaling 62 | ) 63 | { 64 | Anchor = Anchor.Centre, 65 | Origin = Anchor.Centre, 66 | }; 67 | } 68 | 69 | [BackgroundDependencyLoader] 70 | private void load(ISkinSource skin) 71 | { 72 | InternalChildren = new Drawable[] 73 | { 74 | hitCircle, 75 | hitCircleOverlay, 76 | hitCircleText, 77 | }; 78 | 79 | DrawableSoyokazeHitObject drawableSoyokazeObject = (DrawableSoyokazeHitObject)drawableObject; 80 | 81 | accentColourBindable.BindTo(drawableObject.AccentColour); 82 | indexInCurrentComboBindable.BindTo(drawableSoyokazeObject.IndexInCurrentComboBindable); 83 | buttonBindable.BindTo(drawableSoyokazeObject.ButtonBindable); 84 | 85 | accentColourBindable.BindValueChanged(colourChanged => hitCircle.Colour = colourChanged.NewValue, true); 86 | indexInCurrentComboBindable.BindValueChanged(indexChanged => hitCircleText.Text = (indexChanged.NewValue + 1).ToString(), true); 87 | buttonBindable.BindValueChanged(buttonChanged => 88 | { 89 | bool doRotation = skin.GetConfig(SoyokazeSkinConfiguration.RotateHitCircle)?.Value ?? false; 90 | float rotation = doRotation ? PositionExtensions.ButtonToRotation(buttonChanged.NewValue) : 0f; 91 | hitCircle.Rotation = rotation; 92 | hitCircleOverlay.Rotation = rotation; 93 | }, true); 94 | 95 | drawableObject.ApplyCustomUpdateState += updateState; 96 | } 97 | 98 | private void updateState(DrawableHitObject drawableObject, ArmedState state) 99 | { 100 | // fade out text even quicker 101 | switch (state) 102 | { 103 | case ArmedState.Hit: 104 | hitCircleText.FadeOut(); 105 | break; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableHoldProgress.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Framework.Graphics.Transforms; 9 | using osu.Game.Rulesets.Objects.Drawables; 10 | using osu.Game.Rulesets.Soyokaze.Configuration; 11 | using osu.Game.Rulesets.Soyokaze.Extensions; 12 | using osu.Game.Rulesets.Soyokaze.Objects.Drawables; 13 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 14 | using osu.Game.Rulesets.Soyokaze.UI; 15 | using osu.Game.Skinning; 16 | using osuTK; 17 | using osuTK.Graphics; 18 | 19 | namespace osu.Game.Rulesets.Soyokaze.Skinning 20 | { 21 | public partial class SkinnableHoldProgress : Container 22 | { 23 | public override bool RemoveWhenNotAlive => false; 24 | public float Progress 25 | { 26 | get => progressMask.Scale.Y; 27 | set => progressMask.Size = new Vector2(1f, value); 28 | } 29 | 30 | [Resolved] 31 | private DrawableHitObject drawableObject { get; set; } 32 | 33 | private readonly Bindable accentColourBindable = new Bindable(); 34 | private readonly Bindable buttonBindable = new Bindable(); 35 | private readonly Bindable highlightBindable = new Bindable(); 36 | private readonly Bindable highlightColourEnumBindable = new Bindable(); 37 | private Container progressContainer; 38 | private Container progressMask; 39 | private SkinnableDrawable progress; 40 | private SkinnableDrawable background; 41 | private Color4 highlightColour; 42 | 43 | public SkinnableHoldProgress() 44 | { 45 | Anchor = Anchor.Centre; 46 | Origin = Anchor.Centre; 47 | 48 | progressContainer = new Container 49 | { 50 | AutoSizeAxes = Axes.Both, 51 | Anchor = Anchor.Centre, 52 | Origin = Anchor.Centre, 53 | Children = new Drawable[] 54 | { 55 | progress = new SkinnableDrawable( 56 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.HoldOverlay), 57 | _ => new DefaultHoldOverlay() 58 | ) 59 | { 60 | RelativeSizeAxes = Axes.None, 61 | AutoSizeAxes = Axes.Both, 62 | Anchor = Anchor.Centre, 63 | Origin = Anchor.Centre, 64 | Alpha = 0f, 65 | }, 66 | progressMask = new Container 67 | { 68 | RelativeSizeAxes = Axes.Both, 69 | Anchor = Anchor.BottomCentre, 70 | Origin = Anchor.BottomCentre, 71 | Size = new Vector2(1f, 0f), 72 | Masking = true, 73 | Child = progress.CreateProxy(), 74 | }, 75 | }, 76 | }; 77 | 78 | background = new SkinnableDrawable( 79 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.HoldOverlayBackground), 80 | _ => new DefaultHoldOverlayBackground() 81 | ) 82 | { 83 | RelativeSizeAxes = Axes.None, 84 | Anchor = Anchor.Centre, 85 | Origin = Anchor.Centre, 86 | }; 87 | } 88 | 89 | [BackgroundDependencyLoader] 90 | private void load(ISkinSource skin, SoyokazeConfigManager cm) 91 | { 92 | InternalChildren = new Drawable[] 93 | { 94 | background, 95 | progressContainer, 96 | }; 97 | 98 | DrawableSoyokazeHitObject drawableSoyokazeObject = (DrawableSoyokazeHitObject)drawableObject; 99 | 100 | accentColourBindable.BindTo(drawableSoyokazeObject.AccentColour); 101 | buttonBindable.BindTo(drawableSoyokazeObject.ButtonBindable); 102 | 103 | accentColourBindable.BindValueChanged(colourChanged => 104 | { 105 | if (!highlightBindable.Value) 106 | progress.Colour = colourChanged.NewValue; 107 | }, true); 108 | buttonBindable.BindValueChanged(buttonChanged => 109 | { 110 | bool doRotation = skin.GetConfig(SoyokazeSkinConfiguration.RotateHoldProgress)?.Value ?? true; 111 | float rotation = doRotation ? PositionExtensions.ButtonToRotation(buttonChanged.NewValue) : 0f; 112 | background.Rotation = rotation; 113 | progressContainer.Rotation = rotation; 114 | }, true); 115 | 116 | cm?.BindWith(SoyokazeConfig.HoldHighlightColour, highlightColourEnumBindable); 117 | highlightColourEnumBindable.BindValueChanged(colourEnumChanged => 118 | { 119 | if (colourEnumChanged.NewValue == ColourEnum.None) 120 | highlightColour = skin.GetConfig(SoyokazeSkinColour.HoldHighlight)?.Value ?? Color4.Lime; 121 | else 122 | highlightColour = ColourExtensions.ToColour4(colourEnumChanged.NewValue); 123 | 124 | if (highlightBindable.Value) 125 | progress.Colour = highlightColour; 126 | }, true); 127 | 128 | cm?.BindWith(SoyokazeConfig.HighlightHolds, highlightBindable); 129 | highlightBindable.BindValueChanged(valueChanged => 130 | { 131 | if (valueChanged.NewValue) 132 | progress.Colour = highlightColour; 133 | else 134 | progress.Colour = accentColourBindable.Value; 135 | }, true); 136 | } 137 | 138 | public TransformSequence ProgressTo(float newValue, double duration = 0, Easing easing = Easing.None) 139 | { 140 | TransformSequence resize = progressMask.ResizeHeightTo(newValue, duration, easing); 141 | 142 | if (newValue > 0f) 143 | { 144 | return resize.Append(_ => progress.FadeTo(1f)); 145 | } 146 | else 147 | { 148 | return resize.Append(_ => 149 | { 150 | using (BeginDelayedSequence(duration)) 151 | progress.FadeTo(0f); 152 | }); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableInputOverlay.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Bindables; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Game.Rulesets.Soyokaze.Configuration; 9 | using osu.Game.Rulesets.Soyokaze.Extensions; 10 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 11 | using osu.Game.Rulesets.Soyokaze.UI; 12 | using osu.Game.Skinning; 13 | using osuTK; 14 | 15 | namespace osu.Game.Rulesets.Soyokaze.Skinning 16 | { 17 | public partial class SkinnableInputOverlay : Container 18 | { 19 | private SkinnableDrawable[] inputOverlayKeys = new SkinnableDrawable[8]; 20 | private SkinnableDrawable[] inputOverlayBackgrounds = new SkinnableDrawable[2]; 21 | 22 | private Bindable screenCenterGapBindable = new Bindable(); 23 | private Bindable showBindable = new Bindable(); 24 | 25 | private int screenCenterGap => screenCenterGapBindable.Value; 26 | private int keyGap; 27 | 28 | private const double press_duration = 60d; 29 | private const float press_scale = 0.6f; 30 | 31 | public SkinnableInputOverlay() 32 | { 33 | for (int i = 0; i < inputOverlayKeys.Length; i++) 34 | { 35 | inputOverlayKeys[i] = new SkinnableDrawable( 36 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.InputOverlayKey), 37 | _ => new DefaultInputOverlayKey() 38 | ) 39 | { 40 | Anchor = Anchor.Centre, 41 | Origin = Anchor.Centre, 42 | }; 43 | } 44 | 45 | for (int i = 0; i < inputOverlayBackgrounds.Length; i++) 46 | { 47 | inputOverlayBackgrounds[i] = new SkinnableDrawable( 48 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.InputOverlayBackground), 49 | _ => new DefaultInputOverlayBackground() 50 | ) 51 | { 52 | Anchor = Anchor.Centre, 53 | Origin = Anchor.Centre, 54 | }; 55 | } 56 | } 57 | 58 | [BackgroundDependencyLoader(true)] 59 | private void load(ISkinSource skin, SoyokazeConfigManager cm) 60 | { 61 | AddRangeInternal(inputOverlayKeys); 62 | AddRangeInternal(inputOverlayBackgrounds); 63 | 64 | cm?.BindWith(SoyokazeConfig.ScreenCenterGap, screenCenterGapBindable); 65 | cm?.BindWith(SoyokazeConfig.ShowInputOverlay, showBindable); 66 | keyGap = skin.GetConfig(SoyokazeSkinConfiguration.InputOverlayKeyGap)?.Value ?? 20; 67 | 68 | screenCenterGapBindable.BindValueChanged(_ => updatePositions(), true); 69 | showBindable.BindValueChanged(valueChanged => Alpha = valueChanged.NewValue ? 1f : 0f, true); 70 | 71 | bool doRotation = skin.GetConfig(SoyokazeSkinConfiguration.RotateInputOverlayKey)?.Value ?? true; 72 | if (doRotation) 73 | { 74 | for (int i = 0; i < inputOverlayKeys.Length; i++) 75 | inputOverlayKeys[i].Rotation = PositionExtensions.ButtonToRotation((SoyokazeAction)i); 76 | } 77 | } 78 | 79 | private void updatePositions() 80 | { 81 | Vector2[] positions = PositionExtensions.GetPositions(screenCenterGap, keyGap, true, Anchor.Centre); 82 | for (int i = 0; i < 8; i++) 83 | inputOverlayKeys[i].Position = positions[i]; 84 | 85 | inputOverlayBackgrounds[0].Position = new Vector2(-screenCenterGap, 0); 86 | inputOverlayBackgrounds[1].Position = new Vector2(screenCenterGap, 0); 87 | } 88 | 89 | public void PressKey(SoyokazeAction button) 90 | { 91 | inputOverlayKeys[(int)button].ScaleTo(press_scale, press_duration); 92 | } 93 | 94 | public void UnpressKey(SoyokazeAction button) 95 | { 96 | inputOverlayKeys[(int)button].ScaleTo(1f, press_duration); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SkinnableKiaiVisualizer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System; 5 | using osu.Framework.Allocation; 6 | using osu.Framework.Audio.Track; 7 | using osu.Framework.Bindables; 8 | using osu.Framework.Extensions.Color4Extensions; 9 | using osu.Framework.Graphics; 10 | using osu.Framework.Graphics.Containers; 11 | using osu.Game.Beatmaps.ControlPoints; 12 | using osu.Game.Graphics.Containers; 13 | using osu.Game.Rulesets.Soyokaze.Configuration; 14 | using osu.Game.Rulesets.Soyokaze.Skinning.Defaults; 15 | using osu.Game.Skinning; 16 | using osuTK; 17 | using osuTK.Graphics; 18 | 19 | namespace osu.Game.Rulesets.Soyokaze.Skinning 20 | { 21 | public partial class SkinnableKiaiVisualizer : BeatSyncedContainer 22 | { 23 | private Bindable showBindable = new Bindable(); 24 | 25 | private Color4 firstFlashColour; 26 | private Color4 flashColour; 27 | 28 | private byte firstFlashOpacity; 29 | private byte flashOpacity; 30 | 31 | private float defaultSpin; 32 | private float kiaiSpin; 33 | 34 | private KiaiSquaresComposite composite = new KiaiSquaresComposite() 35 | { 36 | RelativeSizeAxes = Axes.Both, 37 | Origin = Anchor.Centre, 38 | Anchor = Anchor.Centre, 39 | }; 40 | 41 | private bool isFirstFlash = true; 42 | private int slowKiaiSpinBeatIndex = int.MaxValue; 43 | 44 | [BackgroundDependencyLoader(true)] 45 | private void load(ISkinSource skin, SoyokazeConfigManager cm) 46 | { 47 | AddInternal(composite); 48 | 49 | Color4 defaultColor = skin.GetConfig(SoyokazeSkinColour.KiaiVisualizerDefault)?.Value ?? Color4.DarkSlateGray; 50 | byte defaultOpacity = skin.GetConfig(SoyokazeSkinConfiguration.KiaiVisualizerDefaultOpacity)?.Value ?? 128; 51 | composite.Colour = defaultColor.Opacity(defaultOpacity); 52 | 53 | firstFlashColour = skin.GetConfig(SoyokazeSkinColour.KiaiVisualizerFirstFlash)?.Value ?? Color4.White; 54 | firstFlashOpacity = skin.GetConfig(SoyokazeSkinConfiguration.KiaiVisualizerFirstFlashOpacity)?.Value ?? 255; 55 | 56 | flashColour = skin.GetConfig(SoyokazeSkinColour.KiaiVisualizerFlash)?.Value ?? Color4.White; 57 | flashOpacity = skin.GetConfig(SoyokazeSkinConfiguration.KiaiVisualizerFlashOpacity)?.Value ?? 152; 58 | 59 | defaultSpin = skin.GetConfig(SoyokazeSkinConfiguration.KiaiVisualizerDefaultSpin)?.Value ?? 1.5f; 60 | kiaiSpin = skin.GetConfig(SoyokazeSkinConfiguration.KiaiVisualizerKiaiSpin)?.Value ?? -60f; 61 | 62 | cm?.BindWith(SoyokazeConfig.ShowKiaiVisualizer, showBindable); 63 | showBindable.BindValueChanged(valueChanged => Alpha = valueChanged.NewValue ? 1f : 0f, true); 64 | } 65 | 66 | protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) 67 | { 68 | int beatsPerMeasure = timingPoint.TimeSignature.Numerator; 69 | double beatLength = timingPoint.BeatLength; 70 | 71 | if (effectPoint.KiaiMode) 72 | { 73 | int fastKiaiSpinBeatLength = Math.Min(beatsPerMeasure * 3 / 4, 1); 74 | 75 | if (isFirstFlash) 76 | { 77 | composite.FlashColour(firstFlashColour.Opacity(firstFlashOpacity), beatLength * 2, Easing.In); 78 | isFirstFlash = false; 79 | 80 | composite.Spin(beatsPerMeasure * beatLength, 0.65f * beatsPerMeasure * kiaiSpin, Easing.Out); 81 | slowKiaiSpinBeatIndex = beatIndex + fastKiaiSpinBeatLength; 82 | } 83 | else if (beatIndex % beatsPerMeasure == 0) 84 | { 85 | composite.FlashColour(flashColour.Opacity(flashOpacity), beatLength * 2, Easing.In); 86 | 87 | composite.Spin(beatsPerMeasure * beatLength, 0.65f * beatsPerMeasure * kiaiSpin, Easing.Out); 88 | slowKiaiSpinBeatIndex = beatIndex + fastKiaiSpinBeatLength; 89 | } 90 | else if (beatIndex >= slowKiaiSpinBeatIndex) 91 | { 92 | composite.Spin(beatsPerMeasure * beatLength, beatsPerMeasure * kiaiSpin); 93 | } 94 | } 95 | else 96 | { 97 | composite.Spin(beatLength, defaultSpin); 98 | isFirstFlash = true; 99 | } 100 | } 101 | 102 | private partial class KiaiSquaresComposite : CompositeDrawable 103 | { 104 | private SkinnableDrawable[] kiaiSquares = new SkinnableDrawable[2]; 105 | private Bindable screenCenterDistanceBindable = new Bindable(); 106 | private int screenCenterDistance => screenCenterDistanceBindable.Value; 107 | 108 | public KiaiSquaresComposite() 109 | { 110 | for (int i = 0; i < 2; i++) 111 | { 112 | kiaiSquares[i] = new SkinnableDrawable( 113 | new SoyokazeSkinComponentLookup(SoyokazeSkinComponents.KiaiVisualizerSquare), 114 | _ => new DefaultKiaiVisualizerSquare() 115 | ) 116 | { 117 | Anchor = Anchor.Centre, 118 | Origin = Anchor.Centre, 119 | }; 120 | } 121 | } 122 | 123 | [BackgroundDependencyLoader] 124 | private void load(SoyokazeConfigManager cm) 125 | { 126 | AddRangeInternal(kiaiSquares); 127 | 128 | cm.BindWith(SoyokazeConfig.ScreenCenterGap, screenCenterDistanceBindable); 129 | screenCenterDistanceBindable.BindValueChanged(_ => updatePositions(), true); 130 | } 131 | 132 | private void updatePositions() 133 | { 134 | kiaiSquares[0].Position = new Vector2(-screenCenterDistance, 0); 135 | kiaiSquares[1].Position = new Vector2(screenCenterDistance, 0); 136 | } 137 | 138 | public void Pulse(double duration, float scale) 139 | { 140 | foreach (SkinnableDrawable kiaiSquare in kiaiSquares) 141 | { 142 | kiaiSquare.ScaleTo(scale, duration * 0.15f, Easing.InQuint).Then().ScaleTo(1f, duration * 0.85f, Easing.OutQuint); 143 | } 144 | } 145 | 146 | public void Spin(double duration, float angle, Easing easing = Easing.None) 147 | { 148 | kiaiSquares[0].RotateTo(kiaiSquares[0].Rotation + angle, duration, easing); 149 | kiaiSquares[1].RotateTo(kiaiSquares[1].Rotation - angle, duration, easing); 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SoyokazeSkinColour.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning 5 | { 6 | public enum SoyokazeSkinColour 7 | { 8 | HoldHighlight, 9 | KiaiVisualizerDefault, 10 | KiaiVisualizerFirstFlash, 11 | KiaiVisualizerFlash, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SoyokazeSkinComponentLookup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Game.Skinning; 5 | 6 | namespace osu.Game.Rulesets.Soyokaze.Skinning 7 | { 8 | public class SoyokazeSkinComponentLookup : SkinComponentLookup 9 | { 10 | public SoyokazeSkinComponentLookup(SoyokazeSkinComponents component) 11 | : base(component) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SoyokazeSkinComponents.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning 5 | { 6 | public enum SoyokazeSkinComponents 7 | { 8 | ApproachCircle, 9 | Cursor, 10 | HitCircle, 11 | HitCircleOverlay, 12 | HitCircleText, 13 | HoldOverlay, 14 | HoldOverlayBackground, 15 | InputOverlayKey, 16 | InputOverlayBackground, 17 | KiaiVisualizerSquare 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Skinning/SoyokazeSkinConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | namespace osu.Game.Rulesets.Soyokaze.Skinning 5 | { 6 | public enum SoyokazeSkinConfiguration 7 | { 8 | HitCirclePrefix, 9 | HitCircleOverlap, 10 | InputOverlayKeyGap, 11 | KiaiVisualizerDefaultOpacity, 12 | KiaiVisualizerFirstFlashOpacity, 13 | KiaiVisualizerFlashOpacity, 14 | KiaiVisualizerDefaultSpin, 15 | KiaiVisualizerKiaiSpin, 16 | RotateApproachCircle, 17 | RotateHitCircle, 18 | RotateHoldProgress, 19 | RotateInputOverlayKey, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/SoyokazeRuleset.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Framework.Allocation; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Framework.Graphics.Rendering; 9 | using osu.Framework.Graphics.Sprites; 10 | using osu.Framework.Graphics.Textures; 11 | using osu.Framework.Input.Bindings; 12 | using osu.Game.Beatmaps; 13 | using osu.Game.Configuration; 14 | using osu.Game.Overlays.Settings; 15 | using osu.Game.Rulesets.Configuration; 16 | using osu.Game.Rulesets.Difficulty; 17 | using osu.Game.Rulesets.Mods; 18 | using osu.Game.Rulesets.Replays.Types; 19 | using osu.Game.Rulesets.Scoring; 20 | using osu.Game.Rulesets.Soyokaze.Beatmaps; 21 | using osu.Game.Rulesets.Soyokaze.Configuration; 22 | using osu.Game.Rulesets.Soyokaze.Difficulty; 23 | using osu.Game.Rulesets.Soyokaze.Extensions; 24 | using osu.Game.Rulesets.Soyokaze.Mods; 25 | using osu.Game.Rulesets.Soyokaze.Objects; 26 | using osu.Game.Rulesets.Soyokaze.Replays; 27 | using osu.Game.Rulesets.Soyokaze.Scoring; 28 | using osu.Game.Rulesets.Soyokaze.Skinning.Legacy; 29 | using osu.Game.Rulesets.Soyokaze.Statistics; 30 | using osu.Game.Rulesets.Soyokaze.UI; 31 | using osu.Game.Rulesets.UI; 32 | using osu.Game.Scoring; 33 | using osu.Game.Screens.Ranking.Statistics; 34 | using osu.Game.Skinning; 35 | using osuTK; 36 | 37 | namespace osu.Game.Rulesets.Soyokaze 38 | { 39 | public partial class SoyokazeRuleset : Ruleset 40 | { 41 | public const string SHORT_NAME = "soyokaze"; 42 | 43 | public override string Description => "soyokaze!"; 44 | public override string ShortName => SHORT_NAME; 45 | public override string PlayingVerb => "Feeling the wind"; 46 | 47 | public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => 48 | new SoyokazeBeatmapConverter(beatmap, this); 49 | 50 | public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new BeatmapProcessor(beatmap); 51 | 52 | public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new SoyokazeConfigManager(settings, RulesetInfo); 53 | 54 | public override ScoreProcessor CreateScoreProcessor() => new SoyokazeScoreProcessor(this); 55 | 56 | public override RulesetSettingsSubsection CreateSettings() => new SoyokazeSettingsSubsection(this); 57 | 58 | public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => 59 | new SoyokazeDifficultyCalculator(RulesetInfo, beatmap); 60 | 61 | public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => 62 | new DrawableSoyokazeRuleset(this, beatmap, mods); 63 | 64 | public override ISkin CreateSkinTransformer(ISkin skin, IBeatmap beatmap) 65 | { 66 | switch (skin) 67 | { 68 | case LegacySkin: 69 | return new SoyokazeLegacySkinTransformer(skin); 70 | 71 | default: 72 | return null; 73 | } 74 | } 75 | 76 | public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new SoyokazeReplayFrame(); 77 | 78 | public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) 79 | { 80 | var hitEventLists = new List[8]; 81 | for (int i = 0; i < hitEventLists.Length; i++) 82 | hitEventLists[i] = new List(); 83 | 84 | foreach (var hitEvent in score.HitEvents) 85 | { 86 | if (!(hitEvent.HitObject is SoyokazeHitObject soyokazeObject)) 87 | continue; 88 | 89 | hitEventLists[(int)soyokazeObject.Button].Add(hitEvent); 90 | } 91 | 92 | return new[] 93 | { 94 | new StatisticItem("Button Accuracies", () => 95 | { 96 | Container accuracyGraphsContainer = new Container() 97 | { 98 | Anchor = Anchor.Centre, 99 | Origin = Anchor.Centre, 100 | AutoSizeAxes = Axes.Both, 101 | }; 102 | Vector2[] positions = PositionExtensions.GetPositions(220, 110, true, Anchor.Centre); 103 | for (int i = 0; i < positions.Length; i++) 104 | accuracyGraphsContainer.Add(new AccuracyGraph(hitEventLists[i]) { Position = positions[i] }); 105 | return accuracyGraphsContainer; 106 | }, true), 107 | new StatisticItem("Overall Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents) 108 | { 109 | RelativeSizeAxes = Axes.X, 110 | Size = new Vector2(1f, 100f) 111 | }, true), 112 | new StatisticItem("", () => new UnstableRate(score.HitEvents) 113 | { 114 | AutoSizeAxes = Axes.None, 115 | RelativeSizeAxes = Axes.X, 116 | Size = new Vector2(0.2f, 10f) 117 | }, true), 118 | }; 119 | } 120 | 121 | public override IEnumerable GetModsFor(ModType type) 122 | { 123 | switch (type) 124 | { 125 | case ModType.DifficultyReduction: 126 | return new Mod[] 127 | { 128 | new SoyokazeModEasy(), 129 | new SoyokazeModNoFail(), 130 | new MultiMod(new SoyokazeModHalfTime(), new SoyokazeModDaycore()), 131 | new SoyokazeModStaccato(), 132 | }; 133 | 134 | case ModType.DifficultyIncrease: 135 | return new Mod[] 136 | { 137 | new SoyokazeModHardRock(), 138 | new MultiMod(new SoyokazeModSuddenDeath(), new SoyokazeModPerfect()), 139 | new MultiMod(new SoyokazeModDoubleTime(), new SoyokazeModNightcore()), 140 | new SoyokazeModHidden(), 141 | }; 142 | 143 | case ModType.Automation: 144 | return new Mod[] 145 | { 146 | new MultiMod(new SoyokazeModAutoplay(), new SoyokazeModCinema()), 147 | }; 148 | 149 | case ModType.Conversion: 150 | return new Mod[] 151 | { 152 | new SoyokazeModRandom(), 153 | new SoyokazeModDifficultyAdjust(), 154 | }; 155 | 156 | case ModType.Fun: 157 | return new Mod[] 158 | { 159 | new MultiMod(new ModWindUp(), new ModWindDown()), 160 | }; 161 | 162 | case ModType.System: 163 | return new Mod[] 164 | { 165 | new SoyokazeModHolds(), 166 | }; 167 | 168 | default: 169 | return new Mod[] 170 | { 171 | }; 172 | } 173 | } 174 | 175 | public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] 176 | { 177 | new KeyBinding(InputKey.W, SoyokazeAction.Button0), 178 | new KeyBinding(InputKey.A, SoyokazeAction.Button1), 179 | new KeyBinding(InputKey.S, SoyokazeAction.Button2), 180 | new KeyBinding(InputKey.D, SoyokazeAction.Button3), 181 | new KeyBinding(InputKey.I, SoyokazeAction.Button4), 182 | new KeyBinding(InputKey.J, SoyokazeAction.Button5), 183 | new KeyBinding(InputKey.K, SoyokazeAction.Button6), 184 | new KeyBinding(InputKey.L, SoyokazeAction.Button7), 185 | }; 186 | 187 | public override Drawable CreateIcon() => new SoyokazeIcon(this); 188 | 189 | public partial class SoyokazeIcon : Container 190 | { 191 | private Sprite sprite; 192 | private Ruleset ruleset; 193 | 194 | public SoyokazeIcon(Ruleset ruleset) 195 | { 196 | AutoSizeAxes = Axes.Both; 197 | Children = new Drawable[] 198 | { 199 | new SpriteIcon 200 | { 201 | Anchor = Anchor.Centre, 202 | Origin = Anchor.Centre, 203 | Icon = FontAwesome.Regular.Circle, 204 | }, 205 | sprite = new Sprite 206 | { 207 | RelativeSizeAxes = Axes.Both, 208 | Size = new Vector2(1), 209 | Scale = new Vector2(.65f), 210 | Anchor = Anchor.Centre, 211 | Origin = Anchor.Centre, 212 | }, 213 | }; 214 | 215 | this.ruleset = ruleset; 216 | } 217 | 218 | [BackgroundDependencyLoader] 219 | private void load(IRenderer renderer) 220 | { 221 | sprite.Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/icon"); 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Statistics/AccuracyGraph.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Framework.Extensions.Color4Extensions; 6 | using osu.Framework.Graphics; 7 | using osu.Framework.Graphics.Containers; 8 | using osu.Framework.Graphics.Sprites; 9 | using osu.Game.Graphics; 10 | using osu.Game.Rulesets.Scoring; 11 | using osu.Game.Rulesets.Soyokaze.Scoring; 12 | using osu.Game.Screens.Ranking.Statistics; 13 | using osuTK; 14 | 15 | namespace osu.Game.Rulesets.Soyokaze.Statistics 16 | { 17 | public partial class AccuracyGraph : FillFlowContainer 18 | { 19 | public AccuracyGraph(List hitEvents) 20 | { 21 | Direction = FillDirection.Vertical; 22 | Anchor = Anchor.Centre; 23 | Origin = Anchor.Centre; 24 | Size = new Vector2(200f, 140f); 25 | 26 | AddRange(new Drawable[] 27 | { 28 | new SpriteText 29 | { 30 | Text = (calculateAccuracy(hitEvents) * 100).ToString("0.00") + "%", 31 | Colour = Color4Extensions.FromHex("#66FFCC"), 32 | Font = OsuFont.Torus.With(size: 42), 33 | AllowMultiline = false, 34 | 35 | Anchor = Anchor.Centre, 36 | Origin = Anchor.Centre, 37 | }, 38 | new UnstableRate(hitEvents) 39 | { 40 | AutoSizeAxes = Axes.None, 41 | 42 | Anchor = Anchor.Centre, 43 | Origin = Anchor.Centre, 44 | RelativeSizeAxes = Axes.Both, 45 | Size = new Vector2(0.8f, 0.13f), 46 | }, 47 | new MiniHitEventTimingDistributionGraph(hitEvents) 48 | { 49 | Anchor = Anchor.Centre, 50 | Origin = Anchor.Centre, 51 | RelativeSizeAxes = Axes.Both, 52 | Size = new Vector2(1f, 0.4f), 53 | }, 54 | new SpriteText 55 | { 56 | Text = hitEvents.Count.ToString() + " objects", 57 | Colour = Color4Extensions.FromHex("#66FFCC"), 58 | Font = OsuFont.Torus.With(size: 12), 59 | AllowMultiline = false, 60 | 61 | Anchor = Anchor.Centre, 62 | Origin = Anchor.Centre, 63 | }, 64 | }); 65 | } 66 | 67 | private float calculateAccuracy(List hitEvents) 68 | { 69 | int maxScore = 0; 70 | int score = 0; 71 | 72 | SoyokazeScoreProcessor scorer = new SoyokazeScoreProcessor(); 73 | foreach (HitEvent hitEvent in hitEvents) 74 | { 75 | score += scorer.GetBaseScoreForResult(hitEvent.Result); 76 | maxScore += scorer.GetBaseScoreForResult(hitEvent.HitObject.CreateJudgement().MaxResult); 77 | } 78 | 79 | return (float)score / maxScore; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/Statistics/MiniHitEventTimingDistributionGraph.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using osu.Framework.Allocation; 10 | using osu.Framework.Graphics; 11 | using osu.Framework.Graphics.Containers; 12 | using osu.Framework.Graphics.Shapes; 13 | using osu.Game.Graphics; 14 | using osu.Game.Graphics.Sprites; 15 | using osu.Game.Rulesets.Scoring; 16 | using osu.Game.Screens.Ranking.Statistics; 17 | using osuTK.Graphics; 18 | 19 | namespace osu.Game.Rulesets.Soyokaze.Statistics 20 | { 21 | /// 22 | /// THIS CODE IS COPIED FROM . 23 | /// THE ONLY DIFFERENCE IS THE NUMBER OF BINS. 24 | /// TODO: PR a non-const timing_distribution_bins lmfao but shit man i am lazy 25 | /// 26 | public partial class MiniHitEventTimingDistributionGraph : CompositeDrawable 27 | { 28 | /// 29 | /// The number of bins on each side of the timing distribution. 30 | /// 31 | private const int timing_distribution_bins = 18; 32 | 33 | /// 34 | /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. 35 | /// 36 | private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; 37 | 38 | /// 39 | /// The centre bin, with a timing distribution very close to/at 0. 40 | /// 41 | private const int timing_distribution_centre_bin_index = timing_distribution_bins; 42 | 43 | /// 44 | /// The number of data points shown on each side of the axis below the graph. 45 | /// 46 | private const float axis_points = 5; 47 | 48 | /// 49 | /// The currently displayed hit events. 50 | /// 51 | private readonly IReadOnlyList hitEvents; 52 | 53 | private readonly IDictionary[] bins; 54 | private double binSize; 55 | private double hitOffset; 56 | 57 | private Bar[]? barDrawables; 58 | 59 | /// 60 | /// Creates a new . 61 | /// 62 | /// The s to display the timing distribution of. 63 | public MiniHitEventTimingDistributionGraph(IReadOnlyList hitEvents) 64 | { 65 | this.hitEvents = hitEvents.Where(e => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit()).ToList(); 66 | bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); 67 | } 68 | 69 | [BackgroundDependencyLoader] 70 | private void load() 71 | { 72 | if (hitEvents.Count == 0) 73 | return; 74 | 75 | binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); 76 | 77 | // Prevent div-by-0 by enforcing a minimum bin size 78 | binSize = Math.Max(1, binSize); 79 | 80 | Scheduler.AddOnce(updateDisplay); 81 | } 82 | 83 | public void UpdateOffset(double hitOffset) 84 | { 85 | this.hitOffset = hitOffset; 86 | Scheduler.AddOnce(updateDisplay); 87 | } 88 | 89 | private void updateDisplay() 90 | { 91 | bool roundUp = true; 92 | 93 | foreach (var bin in bins) 94 | bin.Clear(); 95 | 96 | foreach (var e in hitEvents) 97 | { 98 | double time = e.TimeOffset + hitOffset; 99 | 100 | double binOffset = time / binSize; 101 | 102 | // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display 103 | // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket 104 | // so the easiest way is to cycle between downwards and upwards rounding as we process events. 105 | if (Math.Abs(binOffset - (int)binOffset) == 0.5) 106 | { 107 | binOffset = (int)binOffset + Math.Sign(binOffset) * (roundUp ? 1 : 0); 108 | roundUp = !roundUp; 109 | } 110 | 111 | int index = timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero); 112 | 113 | // may be out of range when applying an offset. for such cases we can just drop the results. 114 | if (index >= 0 && index < bins.Length) 115 | { 116 | bins[index].TryGetValue(e.Result, out int value); 117 | bins[index][e.Result] = ++value; 118 | } 119 | } 120 | 121 | if (barDrawables != null) 122 | { 123 | for (int i = 0; i < barDrawables.Length; i++) 124 | { 125 | barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value)); 126 | } 127 | } 128 | else 129 | { 130 | int maxCount = bins.Max(b => b.Values.Sum()); 131 | barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray(); 132 | 133 | Container axisFlow; 134 | 135 | const float axis_font_size = 12; 136 | 137 | InternalChild = new GridContainer 138 | { 139 | Anchor = Anchor.Centre, 140 | Origin = Anchor.Centre, 141 | RelativeSizeAxes = Axes.Both, 142 | Width = 0.8f, 143 | Content = new[] 144 | { 145 | new Drawable[] 146 | { 147 | new GridContainer 148 | { 149 | RelativeSizeAxes = Axes.Both, 150 | Content = new[] { barDrawables } 151 | } 152 | }, 153 | new Drawable[] 154 | { 155 | axisFlow = new Container 156 | { 157 | RelativeSizeAxes = Axes.X, 158 | Height = axis_font_size, 159 | } 160 | }, 161 | }, 162 | RowDimensions = new[] 163 | { 164 | new Dimension(), 165 | new Dimension(GridSizeMode.AutoSize), 166 | } 167 | }; 168 | 169 | // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. 170 | double maxValue = timing_distribution_bins * binSize; 171 | double axisValueStep = maxValue / axis_points; 172 | 173 | axisFlow.Add(new OsuSpriteText 174 | { 175 | Anchor = Anchor.Centre, 176 | Origin = Anchor.Centre, 177 | Text = "0", 178 | Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) 179 | }); 180 | 181 | for (int i = 1; i <= axis_points; i++) 182 | { 183 | double axisValue = i * axisValueStep; 184 | float position = (float)(axisValue / maxValue); 185 | float alpha = 1f - position * 0.8f; 186 | 187 | axisFlow.Add(new OsuSpriteText 188 | { 189 | Anchor = Anchor.Centre, 190 | Origin = Anchor.Centre, 191 | RelativePositionAxes = Axes.X, 192 | X = -position / 2, 193 | Alpha = alpha, 194 | Text = axisValue.ToString("-0"), 195 | Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) 196 | }); 197 | 198 | axisFlow.Add(new OsuSpriteText 199 | { 200 | Anchor = Anchor.Centre, 201 | Origin = Anchor.Centre, 202 | RelativePositionAxes = Axes.X, 203 | X = position / 2, 204 | Alpha = alpha, 205 | Text = axisValue.ToString("+0"), 206 | Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) 207 | }); 208 | } 209 | } 210 | } 211 | 212 | private partial class Bar : CompositeDrawable 213 | { 214 | private readonly IReadOnlyList> values; 215 | private readonly float maxValue; 216 | private readonly bool isCentre; 217 | private readonly float totalValue; 218 | 219 | private float basalHeight; 220 | private float offsetAdjustment; 221 | 222 | private Circle[] boxOriginals = null!; 223 | 224 | private Circle? boxAdjustment; 225 | 226 | [Resolved] 227 | private OsuColour colours { get; set; } = null!; 228 | 229 | private const double duration = 300; 230 | 231 | public Bar(IDictionary values, float maxValue, bool isCentre) 232 | { 233 | this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); 234 | this.maxValue = maxValue; 235 | this.isCentre = isCentre; 236 | totalValue = values.Sum(v => v.Value); 237 | offsetAdjustment = totalValue; 238 | 239 | RelativeSizeAxes = Axes.Both; 240 | Masking = true; 241 | } 242 | 243 | [BackgroundDependencyLoader] 244 | private void load() 245 | { 246 | if (values.Any()) 247 | { 248 | boxOriginals = values.Select((v, i) => new Circle 249 | { 250 | RelativeSizeAxes = Axes.Both, 251 | Anchor = Anchor.BottomCentre, 252 | Origin = Anchor.BottomCentre, 253 | Colour = isCentre && i == 0 ? Color4.White : colours.ForHitResult(v.Key), 254 | Height = 0, 255 | }).ToArray(); 256 | // The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position, 257 | // to the top, and the bottom bar should be drawn more toward the front by design, 258 | // while the drawing order is from the back to the front, so the order passed to `InternalChildren` is the opposite. 259 | InternalChildren = boxOriginals.Reverse().ToArray(); 260 | } 261 | else 262 | { 263 | // A bin with no value draws a grey dot instead. 264 | Circle dot = new Circle 265 | { 266 | RelativeSizeAxes = Axes.Both, 267 | Anchor = Anchor.BottomCentre, 268 | Origin = Anchor.BottomCentre, 269 | Colour = isCentre ? Color4.White : Color4.Gray, 270 | Height = 0, 271 | }; 272 | InternalChildren = boxOriginals = new[] { dot }; 273 | } 274 | } 275 | 276 | protected override void LoadComplete() 277 | { 278 | base.LoadComplete(); 279 | 280 | if (!values.Any()) 281 | return; 282 | 283 | updateBasalHeight(); 284 | 285 | foreach (var boxOriginal in boxOriginals) 286 | { 287 | boxOriginal.Y = 0; 288 | boxOriginal.Height = basalHeight; 289 | } 290 | 291 | float offsetValue = 0; 292 | 293 | for (int i = 0; i < values.Count; i++) 294 | { 295 | boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); 296 | boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); 297 | offsetValue -= values[i].Value; 298 | } 299 | } 300 | 301 | protected override void Update() 302 | { 303 | base.Update(); 304 | updateBasalHeight(); 305 | } 306 | 307 | public void UpdateOffset(float adjustment) 308 | { 309 | bool hasAdjustment = adjustment != totalValue; 310 | 311 | if (boxAdjustment == null) 312 | { 313 | if (!hasAdjustment) 314 | return; 315 | 316 | AddInternal(boxAdjustment = new Circle 317 | { 318 | RelativeSizeAxes = Axes.Both, 319 | Anchor = Anchor.BottomCentre, 320 | Origin = Anchor.BottomCentre, 321 | Colour = Color4.Yellow, 322 | Blending = BlendingParameters.Additive, 323 | Alpha = 0.6f, 324 | Height = 0, 325 | }); 326 | } 327 | 328 | offsetAdjustment = adjustment; 329 | drawAdjustmentBar(); 330 | } 331 | 332 | private void updateBasalHeight() 333 | { 334 | float newBasalHeight = DrawHeight > DrawWidth ? DrawWidth / DrawHeight : 1; 335 | 336 | if (newBasalHeight == basalHeight) 337 | return; 338 | 339 | basalHeight = newBasalHeight; 340 | foreach (var dot in boxOriginals) 341 | dot.Height = basalHeight; 342 | 343 | draw(); 344 | } 345 | 346 | private float offsetForValue(float value) => (1 - basalHeight) * value / maxValue; 347 | 348 | private float heightForValue(float value) => MathF.Max(basalHeight + offsetForValue(value), 0); 349 | 350 | private void draw() 351 | { 352 | resizeBars(); 353 | 354 | if (boxAdjustment != null) 355 | drawAdjustmentBar(); 356 | } 357 | 358 | private void resizeBars() 359 | { 360 | float offsetValue = 0; 361 | 362 | for (int i = 0; i < values.Count; i++) 363 | { 364 | boxOriginals[i].Y = offsetForValue(offsetValue) * DrawHeight; 365 | boxOriginals[i].Height = heightForValue(values[i].Value); 366 | offsetValue -= values[i].Value; 367 | } 368 | } 369 | 370 | private void drawAdjustmentBar() 371 | { 372 | bool hasAdjustment = offsetAdjustment != totalValue; 373 | 374 | boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment), duration, Easing.OutQuint); 375 | boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); 376 | } 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/DrawableSoyokazeRuleset.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Framework.Allocation; 6 | using osu.Framework.Input; 7 | using osu.Game.Beatmaps; 8 | using osu.Game.Input.Handlers; 9 | using osu.Game.Replays; 10 | using osu.Game.Rulesets.Mods; 11 | using osu.Game.Rulesets.Objects.Drawables; 12 | using osu.Game.Rulesets.Soyokaze.Objects; 13 | using osu.Game.Rulesets.Soyokaze.Replays; 14 | using osu.Game.Rulesets.UI; 15 | using osu.Game.Scoring; 16 | 17 | namespace osu.Game.Rulesets.Soyokaze.UI 18 | { 19 | [Cached] 20 | public partial class DrawableSoyokazeRuleset : DrawableRuleset 21 | { 22 | public DrawableSoyokazeRuleset(SoyokazeRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) 23 | : base(ruleset, beatmap, mods) 24 | { 25 | } 26 | 27 | protected override Playfield CreatePlayfield() => new SoyokazePlayfield(); 28 | 29 | public override DrawableHitObject CreateDrawableRepresentation(SoyokazeHitObject h) => null; 30 | 31 | protected override PassThroughInputManager CreateInputManager() => new SoyokazeInputManager(Ruleset?.RulesetInfo); 32 | 33 | protected override ReplayRecorder CreateReplayRecorder(Score score) => new SoyokazeReplayRecorder(score); 34 | 35 | protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new SoyokazeFramedReplayInputHandler(replay); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/SoyokazeCursorContainer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Graphics; 5 | using osu.Game.Rulesets.Soyokaze.Skinning; 6 | using osu.Game.Rulesets.UI; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.UI 9 | { 10 | /* DEPRECATED: soyokaze! no longer has a cursor on screen 11 | * Originally, smoking was planned to be implemented, but after some testing 12 | * I realized that having to reach over and grab your mouse is an absolute 13 | * pain. 14 | */ 15 | public partial class SoyokazeCursorContainer : GameplayCursorContainer 16 | { 17 | protected override Drawable CreateCursor() => new SkinnableCursor(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/SoyokazeInputManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.ComponentModel; 5 | using osu.Framework.Input.Bindings; 6 | using osu.Game.Rulesets.UI; 7 | 8 | namespace osu.Game.Rulesets.Soyokaze.UI 9 | { 10 | public partial class SoyokazeInputManager : RulesetInputManager 11 | { 12 | public SoyokazeInputManager(RulesetInfo ruleset) 13 | : base(ruleset, 0, SimultaneousBindingMode.Unique) 14 | { 15 | } 16 | } 17 | 18 | public enum SoyokazeAction 19 | { 20 | [Description("Button 1 (DPAD-L ↑)")] 21 | Button0, 22 | 23 | [Description("Button 2 (DPAD-L ←)")] 24 | Button1, 25 | 26 | [Description("Button 3 (DPAD-L ↓)")] 27 | Button2, 28 | 29 | [Description("Button 4 (DPAD-L →)")] 30 | Button3, 31 | 32 | [Description("Button 5 (DPAD-R ↑)")] 33 | Button4, 34 | 35 | [Description("Button 6 (DPAD-R ←)")] 36 | Button5, 37 | 38 | [Description("Button 7 (DPAD-R ↓)")] 39 | Button6, 40 | 41 | [Description("Button 8 (DPAD-R →)")] 42 | Button7, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/SoyokazePlayfield.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Framework.Graphics.Containers; 7 | using osu.Framework.Input.Bindings; 8 | using osu.Framework.Input.Events; 9 | using osu.Game.Rulesets.Judgements; 10 | using osu.Game.Rulesets.Objects; 11 | using osu.Game.Rulesets.Objects.Drawables; 12 | using osu.Game.Rulesets.Scoring; 13 | using osu.Game.Rulesets.Soyokaze.Configuration; 14 | using osu.Game.Rulesets.Soyokaze.Objects; 15 | using osu.Game.Rulesets.Soyokaze.Objects.Drawables; 16 | using osu.Game.Rulesets.Soyokaze.Skinning; 17 | using osu.Game.Rulesets.UI; 18 | 19 | namespace osu.Game.Rulesets.Soyokaze.UI 20 | { 21 | [Cached] 22 | public partial class SoyokazePlayfield : Playfield, IKeyBindingHandler 23 | { 24 | private readonly ProxyContainer approachCircleContainer = new ProxyContainer { RelativeSizeAxes = Axes.Both }; 25 | private readonly JudgementContainer judgementContainer = new JudgementContainer { RelativeSizeAxes = Axes.Both }; 26 | private readonly JudgementPooler judgementPooler; 27 | private readonly SkinnableInputOverlay inputOverlay = new SkinnableInputOverlay { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre }; 28 | private readonly SkinnableKiaiVisualizer kiaiVisualizer = new SkinnableKiaiVisualizer { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre }; 29 | private SoyokazeConfigManager configManager; 30 | 31 | // DEPRECATED: see SoyokazeCursorContainer 32 | // protected override GameplayCursorContainer CreateCursor() => new SoyokazeCursorContainer(); 33 | protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new SoyokazeHitObjectLifetimeEntry(hitObject); 34 | 35 | private partial class ProxyContainer : LifetimeManagementContainer 36 | { 37 | public void Add(Drawable proxy) => AddInternal(proxy); 38 | } 39 | 40 | public SoyokazePlayfield() 41 | { 42 | AddRangeInternal(new Drawable[] 43 | { 44 | kiaiVisualizer, 45 | inputOverlay, 46 | HitObjectContainer, 47 | judgementContainer, 48 | approachCircleContainer, 49 | }); 50 | 51 | AddInternal(judgementPooler = new JudgementPooler(new[] 52 | { 53 | HitResult.Perfect, 54 | HitResult.Great, 55 | HitResult.Good, 56 | HitResult.Ok, 57 | HitResult.Meh, 58 | HitResult.Miss, 59 | HitResult.IgnoreHit, 60 | })); 61 | 62 | NewResult += onNewResult; 63 | } 64 | 65 | [BackgroundDependencyLoader(true)] 66 | private void load(SoyokazeConfigManager cm) 67 | { 68 | configManager = cm; 69 | 70 | RegisterPool(15, 30); 71 | 72 | RegisterPool(15, 30); 73 | RegisterPool(15, 30); 74 | } 75 | 76 | protected override void OnNewDrawableHitObject(DrawableHitObject drawableObject) 77 | { 78 | drawableObject.OnLoadComplete += onDrawableHitObjectLoaded; 79 | } 80 | 81 | private void onDrawableHitObjectLoaded(Drawable drawable) 82 | { 83 | switch (drawable) 84 | { 85 | case DrawableHitCircle hitCircle: 86 | approachCircleContainer.Add(hitCircle.ApproachCircleProxy.CreateProxy()); 87 | break; 88 | } 89 | } 90 | 91 | private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) 92 | { 93 | if (!judgedObject.DisplayResult || !DisplayJudgements.Value) 94 | return; 95 | 96 | switch (judgedObject) 97 | { 98 | case DrawableHitCircle _: 99 | case DrawableHold _: 100 | judgementContainer.Add(judgementPooler.Get(result.Type, dsj => dsj.Apply(result, judgedObject))); 101 | break; 102 | } 103 | } 104 | 105 | public bool OnPressed(KeyBindingPressEvent e) 106 | { 107 | inputOverlay.PressKey(e.Action); 108 | 109 | foreach (var hitObject in HitObjectContainer.AliveObjects) 110 | switch (hitObject) 111 | { 112 | case DrawableHitCircle hitCircle: 113 | if (hitCircle.Hit(e.Action)) 114 | return true; 115 | break; 116 | case DrawableHold hold: 117 | if (hold.Hit(e.Action)) 118 | return true; 119 | break; 120 | } 121 | return true; 122 | 123 | #region Unused chronological sort 124 | /* This is what I had written at one point to make sure that the list 125 | * of objects was checked chronologically, because I wasn't sure if 126 | * HitObjectContainer.AliveObjects was chronological or not. I'm still 127 | * not sure if it is, but it seems to be from my small amount of 128 | * playtesting, so I'm commenting it out for now. I'll leave it here 129 | * in case it turns out to be necessary. But even if it is, I'll 130 | * probably go for some alternative method, since this is really slow. 131 | 132 | List hitObjects = new List(); 133 | foreach (var hitObject in HitObjectContainer.AliveObjects) 134 | { 135 | switch (hitObject) 136 | { 137 | case DrawableHitCircle hitCircle: 138 | hitObjects.Add(hitCircle); 139 | break; 140 | } 141 | } 142 | 143 | hitObjects.Sort((x, y) => 144 | { 145 | if (x.HitObject.StartTime > y.HitObject.StartTime) 146 | return 1; 147 | else 148 | return -1; 149 | }); 150 | 151 | foreach (var hitObject in hitObjects) 152 | if (hitObject.Hit(action)) 153 | return true; 154 | */ 155 | #endregion 156 | } 157 | 158 | public void OnReleased(KeyBindingReleaseEvent e) 159 | { 160 | inputOverlay.UnpressKey(e.Action); 161 | foreach (var hitObject in HitObjectContainer.AliveObjects) 162 | switch (hitObject) 163 | { 164 | case DrawableHold hold: 165 | if (hold.Release(e.Action)) 166 | return; 167 | break; 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/SoyokazeReplayRecorder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using osu.Game.Rulesets.Replays; 6 | using osu.Game.Rulesets.Soyokaze.Replays; 7 | using osu.Game.Rulesets.UI; 8 | using osu.Game.Scoring; 9 | using osuTK; 10 | 11 | namespace osu.Game.Rulesets.Soyokaze.UI 12 | { 13 | public partial class SoyokazeReplayRecorder : ReplayRecorder 14 | { 15 | public SoyokazeReplayRecorder(Score score) 16 | : base(score) 17 | { 18 | } 19 | 20 | protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) 21 | { 22 | return new SoyokazeReplayFrame(Time.Current, mousePosition, actions.ToArray()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/UI/SoyokazeSettingsSubsection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Alden Wu . Licensed under the MIT Licence. 2 | // See the LICENSE file in the repository root for full licence text. 3 | 4 | using osu.Framework.Allocation; 5 | using osu.Framework.Graphics; 6 | using osu.Framework.Localisation; 7 | using osu.Game.Overlays.Settings; 8 | using osu.Game.Rulesets.Soyokaze.Configuration; 9 | 10 | namespace osu.Game.Rulesets.Soyokaze.UI 11 | { 12 | public partial class SoyokazeSettingsSubsection : RulesetSettingsSubsection 13 | { 14 | protected override LocalisableString Header => "soyokaze!"; 15 | 16 | public SoyokazeSettingsSubsection(Ruleset ruleset) 17 | : base(ruleset) 18 | { 19 | } 20 | 21 | [BackgroundDependencyLoader] 22 | private void load() 23 | { 24 | if (!(Config is SoyokazeConfigManager configManager)) 25 | return; 26 | 27 | Children = new Drawable[] 28 | { 29 | new SettingsCheckbox 30 | { 31 | LabelText = "Highlight Holds (for +HO)", 32 | Current = configManager.GetBindable(SoyokazeConfig.HighlightHolds), 33 | ShowsDefaultIndicator = true, 34 | }, 35 | new SettingsEnumDropdown 36 | { 37 | LabelText = "Hold highlight colour (None = use skin's config or default)", 38 | Current = configManager.GetBindable(SoyokazeConfig.HoldHighlightColour), 39 | ShowsDefaultIndicator = true, 40 | }, 41 | new SettingsCheckbox 42 | { 43 | LabelText = "Show Input Overlay", 44 | Current = configManager.GetBindable(SoyokazeConfig.ShowInputOverlay), 45 | ShowsDefaultIndicator = true, 46 | }, 47 | new SettingsCheckbox 48 | { 49 | LabelText = "Show Kiai Visualizer", 50 | Current = configManager.GetBindable(SoyokazeConfig.ShowKiaiVisualizer), 51 | ShowsDefaultIndicator = true, 52 | }, 53 | new SettingsSlider 54 | { 55 | LabelText = "Screen center gap", 56 | Current = configManager.GetBindable(SoyokazeConfig.ScreenCenterGap), 57 | ShowsDefaultIndicator = true, 58 | }, 59 | new SettingsSlider 60 | { 61 | LabelText = "Object gap", 62 | Current = configManager.GetBindable(SoyokazeConfig.ObjectGap), 63 | ShowsDefaultIndicator = true, 64 | }, 65 | }; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /osu.Game.Rulesets.Soyokaze/osu.Game.Rulesets.Soyokaze.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | 10.0 5 | osu.Game.Rulesets.Soyokaze 6 | Library 7 | AnyCPU 8 | osu.Game.Rulesets.Soyokaze 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------