├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── pack.yml ├── .gitignore ├── Directory.Build.props ├── LICENCE ├── README.md ├── UseLocalOsu.ps1 ├── UseLocalOsu.sh ├── global.json ├── osu-difficulty-calculator.licenseheader ├── osu.Server.DifficultyCalculator.sln ├── osu.Server.DifficultyCalculator.sln.DotSettings ├── osu.Server.DifficultyCalculator ├── AppSettings.cs ├── BeatmapLoader.cs ├── Commands │ ├── AllCommand.cs │ ├── BeatmapsCommand.cs │ ├── BeatmapsStringCommand.cs │ ├── CalculatorCommand.cs │ ├── CommandBase.cs │ ├── FilesCommand.cs │ └── ProcessingMode.cs ├── Dockerfile ├── Program.cs ├── Reporter.cs ├── ServerDifficultyCalculator.cs └── osu.Server.DifficultyCalculator.csproj └── osu.Server.Queues.BeatmapProcessor ├── .dockerignore ├── BeatmapItem.cs ├── BeatmapProcessor.cs ├── Dockerfile ├── Program.cs └── osu.Server.Queues.BeatmapProcessor.csproj /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "CodeFileSanity": { 6 | "version": "0.0.37", 7 | "commands": [ 8 | "CodeFileSanity" 9 | ] 10 | }, 11 | "jetbrains.resharper.globaltools": { 12 | "version": "2023.3.3", 13 | "commands": [ 14 | "jb" 15 | ] 16 | }, 17 | "nvika": { 18 | "version": "4.0.0", 19 | "commands": [ 20 | "nvika" 21 | ] 22 | } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://editorconfig.org 2 | root = true 3 | 4 | [*.cs] 5 | end_of_line = crlf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | #Roslyn naming styles 12 | 13 | #PascalCase for public and protected members 14 | dotnet_naming_style.pascalcase.capitalization = pascal_case 15 | dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 16 | dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event 17 | dotnet_naming_rule.public_members_pascalcase.severity = error 18 | dotnet_naming_rule.public_members_pascalcase.symbols = public_members 19 | dotnet_naming_rule.public_members_pascalcase.style = pascalcase 20 | 21 | #camelCase for private members 22 | dotnet_naming_style.camelcase.capitalization = camel_case 23 | 24 | dotnet_naming_symbols.private_members.applicable_accessibilities = private 25 | dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event 26 | dotnet_naming_rule.private_members_camelcase.severity = warning 27 | dotnet_naming_rule.private_members_camelcase.symbols = private_members 28 | dotnet_naming_rule.private_members_camelcase.style = camelcase 29 | 30 | dotnet_naming_symbols.local_function.applicable_kinds = local_function 31 | dotnet_naming_rule.local_function_camelcase.severity = warning 32 | dotnet_naming_rule.local_function_camelcase.symbols = local_function 33 | dotnet_naming_rule.local_function_camelcase.style = camelcase 34 | 35 | #all_lower for private and local constants/static readonlys 36 | dotnet_naming_style.all_lower.capitalization = all_lower 37 | dotnet_naming_style.all_lower.word_separator = _ 38 | 39 | dotnet_naming_symbols.private_constants.applicable_accessibilities = private 40 | dotnet_naming_symbols.private_constants.required_modifiers = const 41 | dotnet_naming_symbols.private_constants.applicable_kinds = field 42 | dotnet_naming_rule.private_const_all_lower.severity = warning 43 | dotnet_naming_rule.private_const_all_lower.symbols = private_constants 44 | dotnet_naming_rule.private_const_all_lower.style = all_lower 45 | 46 | dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private 47 | dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly 48 | dotnet_naming_symbols.private_static_readonly.applicable_kinds = field 49 | dotnet_naming_rule.private_static_readonly_all_lower.severity = warning 50 | dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly 51 | dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower 52 | 53 | dotnet_naming_symbols.local_constants.applicable_kinds = local 54 | dotnet_naming_symbols.local_constants.required_modifiers = const 55 | dotnet_naming_rule.local_const_all_lower.severity = warning 56 | dotnet_naming_rule.local_const_all_lower.symbols = local_constants 57 | dotnet_naming_rule.local_const_all_lower.style = all_lower 58 | 59 | #ALL_UPPER for non private constants/static readonlys 60 | dotnet_naming_style.all_upper.capitalization = all_upper 61 | dotnet_naming_style.all_upper.word_separator = _ 62 | 63 | dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 64 | dotnet_naming_symbols.public_constants.required_modifiers = const 65 | dotnet_naming_symbols.public_constants.applicable_kinds = field 66 | dotnet_naming_rule.public_const_all_upper.severity = warning 67 | dotnet_naming_rule.public_const_all_upper.symbols = public_constants 68 | dotnet_naming_rule.public_const_all_upper.style = all_upper 69 | 70 | dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected 71 | dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly 72 | dotnet_naming_symbols.public_static_readonly.applicable_kinds = field 73 | dotnet_naming_rule.public_static_readonly_all_upper.severity = warning 74 | dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly 75 | dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper 76 | 77 | #Roslyn formating options 78 | 79 | #Formatting - indentation options 80 | csharp_indent_case_contents = true 81 | csharp_indent_case_contents_when_block = false 82 | csharp_indent_labels = one_less_than_current 83 | csharp_indent_switch_labels = true 84 | 85 | #Formatting - new line options 86 | csharp_new_line_before_catch = true 87 | csharp_new_line_before_else = true 88 | csharp_new_line_before_finally = true 89 | csharp_new_line_before_open_brace = all 90 | #csharp_new_line_before_members_in_anonymous_types = true 91 | #csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing 92 | csharp_new_line_between_query_expression_clauses = true 93 | 94 | #Formatting - organize using options 95 | dotnet_sort_system_directives_first = true 96 | 97 | #Formatting - spacing options 98 | csharp_space_after_cast = false 99 | csharp_space_after_colon_in_inheritance_clause = true 100 | csharp_space_after_keywords_in_control_flow_statements = true 101 | csharp_space_before_colon_in_inheritance_clause = true 102 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 103 | csharp_space_between_method_call_name_and_opening_parenthesis = false 104 | csharp_space_between_method_call_parameter_list_parentheses = false 105 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 106 | csharp_space_between_method_declaration_parameter_list_parentheses = false 107 | 108 | #Formatting - wrapping options 109 | csharp_preserve_single_line_blocks = true 110 | csharp_preserve_single_line_statements = true 111 | 112 | #Roslyn language styles 113 | 114 | #Style - this. qualification 115 | dotnet_style_qualification_for_field = false:warning 116 | dotnet_style_qualification_for_property = false:warning 117 | dotnet_style_qualification_for_method = false:warning 118 | dotnet_style_qualification_for_event = false:warning 119 | 120 | #Style - type names 121 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 122 | dotnet_style_predefined_type_for_member_access = true:warning 123 | csharp_style_var_when_type_is_apparent = true:none 124 | csharp_style_var_for_built_in_types = true:none 125 | csharp_style_var_elsewhere = true:silent 126 | 127 | #Style - modifiers 128 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 129 | csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning 130 | 131 | #Style - parentheses 132 | # Skipped because roslyn cannot separate +-*/ with << >> 133 | 134 | #Style - expression bodies 135 | csharp_style_expression_bodied_accessors = true:warning 136 | csharp_style_expression_bodied_constructors = false:none 137 | csharp_style_expression_bodied_indexers = true:warning 138 | csharp_style_expression_bodied_methods = false:silent 139 | csharp_style_expression_bodied_operators = true:warning 140 | csharp_style_expression_bodied_properties = true:warning 141 | csharp_style_expression_bodied_local_functions = true:silent 142 | 143 | #Style - expression preferences 144 | dotnet_style_object_initializer = true:warning 145 | dotnet_style_collection_initializer = true:warning 146 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning 147 | dotnet_style_prefer_auto_properties = true:warning 148 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 149 | dotnet_style_prefer_conditional_expression_over_return = true:silent 150 | dotnet_style_prefer_compound_assignment = true:warning 151 | 152 | #Style - null/type checks 153 | dotnet_style_coalesce_expression = true:warning 154 | dotnet_style_null_propagation = true:warning 155 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 156 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 157 | csharp_style_throw_expression = true:silent 158 | csharp_style_conditional_delegate_call = true:warning 159 | 160 | #Style - unused 161 | dotnet_style_readonly_field = true:silent 162 | dotnet_code_quality_unused_parameters = non_public:silent 163 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 164 | csharp_style_unused_value_assignment_preference = discard_variable:warning 165 | 166 | #Style - variable declaration 167 | csharp_style_inlined_variable_declaration = true:warning 168 | csharp_style_deconstructed_variable_declaration = true:warning 169 | 170 | #Style - other C# 7.x features 171 | dotnet_style_prefer_inferred_tuple_names = true:warning 172 | csharp_prefer_simple_default_expression = true:warning 173 | csharp_style_pattern_local_over_anonymous_function = true:warning 174 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 175 | 176 | #Style - C# 8 features 177 | csharp_prefer_static_local_function = true:warning 178 | csharp_prefer_simple_using_statement = true:silent 179 | csharp_style_prefer_index_operator = true:warning 180 | csharp_style_prefer_range_operator = true:warning 181 | csharp_style_prefer_switch_expression = false:none 182 | 183 | #Supressing roslyn built-in analyzers 184 | # Suppress: EC112 185 | 186 | #Private method is unused 187 | dotnet_diagnostic.IDE0051.severity = silent 188 | #Private member is unused 189 | dotnet_diagnostic.IDE0052.severity = silent 190 | 191 | #Rules for disposable 192 | dotnet_diagnostic.IDE0067.severity = none 193 | dotnet_diagnostic.IDE0068.severity = none 194 | dotnet_diagnostic.IDE0069.severity = none 195 | 196 | #Disable operator overloads requiring alternate named methods 197 | dotnet_diagnostic.CA2225.severity = none -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Autodetect text files and ensure that we normalise their 2 | # line endings to lf internally. When checked out they may 3 | # use different line endings. 4 | * text=auto 5 | 6 | # Check out with crlf (Windows) line endings 7 | *.sln text eol=crlf 8 | *.csproj text eol=crlf 9 | *.cs text diff=csharp eol=crlf 10 | *.resx text eol=crlf 11 | *.vsixmanifest text eol=crlf 12 | packages.config text eol=crlf 13 | App.config text eol=crlf 14 | *.bat text eol=crlf 15 | *.cmd text eol=crlf 16 | *.snippet text eol=crlf 17 | *.manifest text eol=crlf 18 | 19 | # Check out with lf (UNIX) line endings 20 | *.sh text eol=lf 21 | .gitignore text eol=lf 22 | .gitattributes text eol=lf 23 | *.md text eol=lf 24 | .travis.yml text eol=lf -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "17:00" 8 | open-pull-requests-limit: 0 # disabled until https://github.com/dependabot/dependabot-core/issues/369 is resolved. 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Continuous Integration 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | jobs: 8 | inspect-code: 9 | name: Code Quality 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Install .NET 8.0.x 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: "8.0.x" 19 | 20 | - name: Restore Tools 21 | run: dotnet tool restore 22 | 23 | - name: Restore Packages 24 | run: dotnet restore 25 | 26 | - name: CodeFileSanity 27 | run: | 28 | # TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround. 29 | # FIXME: Suppress warnings from templates project 30 | exit_code=0 31 | while read -r line; do 32 | if [[ ! -z "$line" ]]; then 33 | echo "::error::$line" 34 | exit_code=1 35 | fi 36 | done <<< $(dotnet codefilesanity) 37 | exit $exit_code 38 | 39 | - name: InspectCode 40 | run: dotnet jb inspectcode $(pwd)/osu.Server.DifficultyCalculator.sln --build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN 41 | 42 | - name: NVika 43 | run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors 44 | -------------------------------------------------------------------------------- /.github/workflows/pack.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-diffcalc 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | push_to_registry: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | include: 17 | - image: pppy/osu-difficulty-calculator 18 | context: ./ 19 | file: ./osu.Server.DifficultyCalculator/Dockerfile 20 | - image: pppy/osu-queue-beatmap-processor 21 | context: ./ 22 | file: ./osu.Server.Queues.BeatmapProcessor/Dockerfile 23 | steps: 24 | - 25 | name: Checkout 26 | uses: actions/checkout@v2 27 | - 28 | name: Docker meta 29 | id: meta 30 | uses: docker/metadata-action@v3 31 | with: 32 | # list of Docker images to use as base name for tags 33 | images: | 34 | ${{ matrix.image }} 35 | # generate Docker tags based on the following events/attributes 36 | # on tag event: tag using git tag, and as latest if the tag doesn't contain hyphens (pre-releases) 37 | # on push event: tag using git sha, branch name and as latest-dev 38 | tags: | 39 | type=raw,value=latest,enable=${{ github.ref_type == 'tag' && !contains(github.ref_name, '-') }} 40 | type=raw,value=latest-dev,enable=${{ github.ref_type == 'branch' && github.ref_name == 'master' }} 41 | type=raw,value=${{ github.ref_name }} 42 | type=raw,value=${{ github.sha }},enable=${{ github.ref_type == 'branch' }} 43 | flavor: | 44 | latest=false 45 | - 46 | name: Set up Docker Buildx 47 | uses: docker/setup-buildx-action@v1 48 | - 49 | name: Login to DockerHub 50 | uses: docker/login-action@v1 51 | with: 52 | username: ${{ secrets.DOCKER_USERNAME }} 53 | password: ${{ secrets.DOCKER_PASSWORD }} 54 | - 55 | name: Build and push 56 | uses: docker/build-push-action@v2 57 | with: 58 | context: ${{ matrix.context }} 59 | file: ${{ matrix.file }} 60 | platforms: linux/amd64 61 | push: true 62 | tags: ${{ steps.meta.outputs.tags }} 63 | labels: ${{ steps.meta.outputs.labels }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | ### Cake ### 14 | tools/** 15 | build/tools/** 16 | 17 | # Build results 18 | bin/[Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | inspectcode 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | *.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # Since there are multiple workflows, uncomment next line to ignore bower_components 201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 202 | #bower_components/ 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | paket-files/ 250 | 251 | # FAKE - F# Make 252 | .fake/ 253 | 254 | # JetBrains Rider 255 | .idea/ 256 | *.sln.iml 257 | 258 | # CodeRush 259 | .cr/ 260 | 261 | # Python Tools for Visual Studio (PTVS) 262 | __pycache__/ 263 | *.pyc 264 | Staging/ 265 | 266 | inspectcodereport.xml 267 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 4 | 5 | 6 | 7 | osu-difficulty-calculator.licenseheader 8 | 9 | 10 | 11 | ppy Pty Ltd 12 | Copyright (c) 2022 ppy Pty Ltd 13 | 14 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 ppy Pty Ltd . 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osu-difficulty-calculator [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) 2 | 3 | Difficulty calculation server for [osu!](https://osu.ppy.sh). 4 | 5 | For more detailed information see per-component READMEs. 6 | 7 | # Current Versions 8 | 9 | This is part of a group of projects which are used in live deployments where the deployed version is critical to producing correct results. The `master` branch tracks ongoing developments. If looking to use the correct version for matching live values, please [consult this wiki page](https://github.com/ppy/osu-infrastructure/wiki/Star-Rating-and-Performance-Points) for the latest information. 10 | 11 | # Requirements 12 | 13 | - A desktop platform that can compile .NET 6.0. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. 14 | 15 | # Getting Started 16 | - Clone the repository (`git clone https://github.com/ppy/osu-difficulty-calculator`) 17 | - Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`) 18 | 19 | # Contributing 20 | 21 | Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://www.bountysource.com/teams/ppy). If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu-osu-difficulty-calculator/issues). 22 | 23 | Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible. 24 | 25 | # Licence 26 | 27 | The osu! client code, framework, and server-side components are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. 28 | 29 | Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law. 30 | 31 | Please also note that game resources are covered by a separate licence. Please see the [ppy/osu-resources](https://github.com/ppy/osu-resources) repository for clarifications. 32 | 33 | -------------------------------------------------------------------------------- /UseLocalOsu.ps1: -------------------------------------------------------------------------------- 1 | # Run this script to use a local copy of osu rather than fetching it from nuget. 2 | # It expects the osu directory to be at the same level as the osu-tools directory 3 | 4 | 5 | $CSPROJ="osu.Server.DifficultyCalculator/osu.Server.DifficultyCalculator.csproj" 6 | $SLN="osu.Server.DifficultyCalculator.sln" 7 | 8 | $DEPENDENCIES=@( 9 | "..\osu\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" 10 | "..\osu\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" 11 | "..\osu\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" 12 | "..\osu\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" 13 | "..\osu\osu.Game\osu.Game.csproj" 14 | ) 15 | 16 | 17 | dotnet remove $CSPROJ package ppy.osu.Game 18 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Osu 19 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Taiko 20 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Catch 21 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Mania 22 | 23 | dotnet sln $SLN add $DEPENDENCIES 24 | dotnet add $CSPROJ reference $DEPENDENCIES 25 | -------------------------------------------------------------------------------- /UseLocalOsu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run this script to use a local copy of osu rather than fetching it from nuget. 4 | # It expects the osu directory to be at the same level as the osu-tools directory 5 | 6 | 7 | CSPROJ="osu.Server.DifficultyCalculator/osu.Server.DifficultyCalculator.csproj" 8 | SLN="osu.Server.DifficultyCalculator.sln" 9 | 10 | DEPENDENCIES="../osu/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj 11 | ../osu/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj 12 | ../osu/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj 13 | ../osu/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj 14 | ../osu/osu.Game/osu.Game.csproj" 15 | 16 | 17 | dotnet remove $CSPROJ package ppy.osu.Game 18 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Osu 19 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Taiko 20 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Catch 21 | dotnet remove $CSPROJ package ppy.osu.Game.Rulesets.Mania 22 | 23 | dotnet sln $SLN add $DEPENDENCIES 24 | dotnet add $CSPROJ reference $DEPENDENCIES 25 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestFeature", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /osu-difficulty-calculator.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: .cs 2 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 3 | // See the LICENCE file in the repository root for full licence text. 4 | 5 | extensions: .xml .config .xsd 6 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Server.DifficultyCalculator", "osu.Server.DifficultyCalculator\osu.Server.DifficultyCalculator.csproj", "{B86E5C04-C9B1-4276-AB15-4F6C7F4A1343}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Server.Queues.BeatmapProcessor", "osu.Server.Queues.BeatmapProcessor\osu.Server.Queues.BeatmapProcessor.csproj", "{EEB73785-2BD4-4AD9-87B4-B7835842C453}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {B86E5C04-C9B1-4276-AB15-4F6C7F4A1343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {B86E5C04-C9B1-4276-AB15-4F6C7F4A1343}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {B86E5C04-C9B1-4276-AB15-4F6C7F4A1343}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {B86E5C04-C9B1-4276-AB15-4F6C7F4A1343}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {EEB73785-2BD4-4AD9-87B4-B7835842C453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {EEB73785-2BD4-4AD9-87B4-B7835842C453}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {EEB73785-2BD4-4AD9-87B4-B7835842C453}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {EEB73785-2BD4-4AD9-87B4-B7835842C453}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | 7 | SOLUTION 8 | WARNING 9 | WARNING 10 | WARNING 11 | HINT 12 | HINT 13 | WARNING 14 | WARNING 15 | True 16 | WARNING 17 | WARNING 18 | HINT 19 | DO_NOT_SHOW 20 | HINT 21 | DO_NOT_SHOW 22 | DO_NOT_SHOW 23 | WARNING 24 | WARNING 25 | WARNING 26 | WARNING 27 | WARNING 28 | WARNING 29 | WARNING 30 | WARNING 31 | WARNING 32 | WARNING 33 | WARNING 34 | WARNING 35 | WARNING 36 | WARNING 37 | WARNING 38 | WARNING 39 | WARNING 40 | WARNING 41 | WARNING 42 | WARNING 43 | WARNING 44 | WARNING 45 | WARNING 46 | WARNING 47 | WARNING 48 | WARNING 49 | WARNING 50 | WARNING 51 | WARNING 52 | HINT 53 | WARNING 54 | HINT 55 | SUGGESTION 56 | HINT 57 | HINT 58 | HINT 59 | WARNING 60 | WARNING 61 | WARNING 62 | WARNING 63 | WARNING 64 | WARNING 65 | WARNING 66 | WARNING 67 | WARNING 68 | WARNING 69 | WARNING 70 | WARNING 71 | WARNING 72 | HINT 73 | WARNING 74 | HINT 75 | WARNING 76 | DO_NOT_SHOW 77 | WARNING 78 | WARNING 79 | WARNING 80 | WARNING 81 | WARNING 82 | WARNING 83 | WARNING 84 | WARNING 85 | HINT 86 | WARNING 87 | DO_NOT_SHOW 88 | WARNING 89 | HINT 90 | DO_NOT_SHOW 91 | HINT 92 | HINT 93 | ERROR 94 | WARNING 95 | HINT 96 | HINT 97 | HINT 98 | WARNING 99 | WARNING 100 | HINT 101 | DO_NOT_SHOW 102 | HINT 103 | HINT 104 | HINT 105 | HINT 106 | WARNING 107 | WARNING 108 | DO_NOT_SHOW 109 | WARNING 110 | WARNING 111 | WARNING 112 | WARNING 113 | WARNING 114 | WARNING 115 | WARNING 116 | WARNING 117 | WARNING 118 | WARNING 119 | WARNING 120 | WARNING 121 | HINT 122 | WARNING 123 | HINT 124 | HINT 125 | HINT 126 | DO_NOT_SHOW 127 | HINT 128 | HINT 129 | WARNING 130 | WARNING 131 | HINT 132 | HINT 133 | WARNING 134 | WARNING 135 | WARNING 136 | WARNING 137 | HINT 138 | DO_NOT_SHOW 139 | DO_NOT_SHOW 140 | DO_NOT_SHOW 141 | WARNING 142 | WARNING 143 | WARNING 144 | WARNING 145 | WARNING 146 | SUGGESTION 147 | WARNING 148 | WARNING 149 | WARNING 150 | ERROR 151 | WARNING 152 | WARNING 153 | HINT 154 | WARNING 155 | WARNING 156 | WARNING 157 | WARNING 158 | WARNING 159 | WARNING 160 | WARNING 161 | WARNING 162 | WARNING 163 | WARNING 164 | WARNING 165 | WARNING 166 | WARNING 167 | WARNING 168 | WARNING 169 | WARNING 170 | WARNING 171 | WARNING 172 | WARNING 173 | WARNING 174 | WARNING 175 | WARNING 176 | WARNING 177 | WARNING 178 | WARNING 179 | WARNING 180 | WARNING 181 | WARNING 182 | WARNING 183 | WARNING 184 | WARNING 185 | WARNING 186 | WARNING 187 | WARNING 188 | WARNING 189 | WARNING 190 | WARNING 191 | WARNING 192 | WARNING 193 | WARNING 194 | WARNING 195 | WARNING 196 | WARNING 197 | WARNING 198 | WARNING 199 | WARNING 200 | WARNING 201 | WARNING 202 | WARNING 203 | WARNING 204 | WARNING 205 | HINT 206 | WARNING 207 | WARNING 208 | DO_NOT_SHOW 209 | DO_NOT_SHOW 210 | DO_NOT_SHOW 211 | WARNING 212 | WARNING 213 | WARNING 214 | WARNING 215 | WARNING 216 | HINT 217 | WARNING 218 | HINT 219 | HINT 220 | HINT 221 | HINT 222 | HINT 223 | HINT 224 | HINT 225 | HINT 226 | HINT 227 | HINT 228 | DO_NOT_SHOW 229 | WARNING 230 | WARNING 231 | WARNING 232 | WARNING 233 | WARNING 234 | WARNING 235 | 236 | True 237 | WARNING 238 | WARNING 239 | WARNING 240 | WARNING 241 | WARNING 242 | HINT 243 | HINT 244 | WARNING 245 | WARNING 246 | <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> 247 | Code Cleanup (peppy) 248 | RequiredForMultiline 249 | RequiredForMultiline 250 | RequiredForMultiline 251 | RequiredForMultiline 252 | RequiredForMultiline 253 | RequiredForMultiline 254 | RequiredForMultiline 255 | RequiredForMultiline 256 | Explicit 257 | ExpressionBody 258 | BlockBody 259 | True 260 | NEXT_LINE 261 | True 262 | True 263 | True 264 | True 265 | True 266 | True 267 | True 268 | True 269 | NEXT_LINE 270 | 1 271 | 1 272 | NEXT_LINE 273 | MULTILINE 274 | True 275 | True 276 | True 277 | True 278 | NEXT_LINE 279 | 1 280 | 1 281 | True 282 | NEXT_LINE 283 | NEVER 284 | NEVER 285 | True 286 | False 287 | True 288 | NEVER 289 | False 290 | False 291 | True 292 | False 293 | False 294 | True 295 | True 296 | False 297 | False 298 | CHOP_IF_LONG 299 | True 300 | 200 301 | CHOP_IF_LONG 302 | False 303 | False 304 | AABB 305 | API 306 | BPM 307 | GC 308 | GL 309 | GLSL 310 | HID 311 | HTML 312 | HUD 313 | ID 314 | IL 315 | IOS 316 | IP 317 | IPC 318 | JIT 319 | LTRB 320 | MD5 321 | NS 322 | OS 323 | PM 324 | RGB 325 | RNG 326 | SHA 327 | SRGB 328 | TK 329 | SS 330 | PP 331 | GMT 332 | QAT 333 | BNG 334 | UI 335 | False 336 | HINT 337 | <?xml version="1.0" encoding="utf-16"?> 338 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 339 | <TypePattern DisplayName="COM interfaces or structs"> 340 | <TypePattern.Match> 341 | <Or> 342 | <And> 343 | <Kind Is="Interface" /> 344 | <Or> 345 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 346 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 347 | </Or> 348 | </And> 349 | <Kind Is="Struct" /> 350 | </Or> 351 | </TypePattern.Match> 352 | </TypePattern> 353 | <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> 354 | <TypePattern.Match> 355 | <And> 356 | <Kind Is="Class" /> 357 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 358 | </And> 359 | </TypePattern.Match> 360 | <Entry DisplayName="Setup/Teardown Methods"> 361 | <Entry.Match> 362 | <And> 363 | <Kind Is="Method" /> 364 | <Or> 365 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 366 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 367 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 368 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 369 | </Or> 370 | </And> 371 | </Entry.Match> 372 | </Entry> 373 | <Entry DisplayName="All other members" /> 374 | <Entry Priority="100" DisplayName="Test Methods"> 375 | <Entry.Match> 376 | <And> 377 | <Kind Is="Method" /> 378 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 379 | </And> 380 | </Entry.Match> 381 | <Entry.SortBy> 382 | <Name /> 383 | </Entry.SortBy> 384 | </Entry> 385 | </TypePattern> 386 | <TypePattern DisplayName="Default Pattern"> 387 | <Group DisplayName="Fields/Properties"> 388 | <Group DisplayName="Public Fields"> 389 | <Entry DisplayName="Constant Fields"> 390 | <Entry.Match> 391 | <And> 392 | <Access Is="Public" /> 393 | <Or> 394 | <Kind Is="Constant" /> 395 | <Readonly /> 396 | <And> 397 | <Static /> 398 | <Readonly /> 399 | </And> 400 | </Or> 401 | </And> 402 | </Entry.Match> 403 | </Entry> 404 | <Entry DisplayName="Static Fields"> 405 | <Entry.Match> 406 | <And> 407 | <Access Is="Public" /> 408 | <Static /> 409 | <Not> 410 | <Readonly /> 411 | </Not> 412 | <Kind Is="Field" /> 413 | </And> 414 | </Entry.Match> 415 | </Entry> 416 | <Entry DisplayName="Normal Fields"> 417 | <Entry.Match> 418 | <And> 419 | <Access Is="Public" /> 420 | <Not> 421 | <Or> 422 | <Static /> 423 | <Readonly /> 424 | </Or> 425 | </Not> 426 | <Kind Is="Field" /> 427 | </And> 428 | </Entry.Match> 429 | </Entry> 430 | </Group> 431 | <Entry DisplayName="Public Properties"> 432 | <Entry.Match> 433 | <And> 434 | <Access Is="Public" /> 435 | <Kind Is="Property" /> 436 | </And> 437 | </Entry.Match> 438 | </Entry> 439 | <Group DisplayName="Internal Fields"> 440 | <Entry DisplayName="Constant Fields"> 441 | <Entry.Match> 442 | <And> 443 | <Access Is="Internal" /> 444 | <Or> 445 | <Kind Is="Constant" /> 446 | <Readonly /> 447 | <And> 448 | <Static /> 449 | <Readonly /> 450 | </And> 451 | </Or> 452 | </And> 453 | </Entry.Match> 454 | </Entry> 455 | <Entry DisplayName="Static Fields"> 456 | <Entry.Match> 457 | <And> 458 | <Access Is="Internal" /> 459 | <Static /> 460 | <Not> 461 | <Readonly /> 462 | </Not> 463 | <Kind Is="Field" /> 464 | </And> 465 | </Entry.Match> 466 | </Entry> 467 | <Entry DisplayName="Normal Fields"> 468 | <Entry.Match> 469 | <And> 470 | <Access Is="Internal" /> 471 | <Not> 472 | <Or> 473 | <Static /> 474 | <Readonly /> 475 | </Or> 476 | </Not> 477 | <Kind Is="Field" /> 478 | </And> 479 | </Entry.Match> 480 | </Entry> 481 | </Group> 482 | <Entry DisplayName="Internal Properties"> 483 | <Entry.Match> 484 | <And> 485 | <Access Is="Internal" /> 486 | <Kind Is="Property" /> 487 | </And> 488 | </Entry.Match> 489 | </Entry> 490 | <Group DisplayName="Protected Fields"> 491 | <Entry DisplayName="Constant Fields"> 492 | <Entry.Match> 493 | <And> 494 | <Access Is="Protected" /> 495 | <Or> 496 | <Kind Is="Constant" /> 497 | <Readonly /> 498 | <And> 499 | <Static /> 500 | <Readonly /> 501 | </And> 502 | </Or> 503 | </And> 504 | </Entry.Match> 505 | </Entry> 506 | <Entry DisplayName="Static Fields"> 507 | <Entry.Match> 508 | <And> 509 | <Access Is="Protected" /> 510 | <Static /> 511 | <Not> 512 | <Readonly /> 513 | </Not> 514 | <Kind Is="Field" /> 515 | </And> 516 | </Entry.Match> 517 | </Entry> 518 | <Entry DisplayName="Normal Fields"> 519 | <Entry.Match> 520 | <And> 521 | <Access Is="Protected" /> 522 | <Not> 523 | <Or> 524 | <Static /> 525 | <Readonly /> 526 | </Or> 527 | </Not> 528 | <Kind Is="Field" /> 529 | </And> 530 | </Entry.Match> 531 | </Entry> 532 | </Group> 533 | <Entry DisplayName="Protected Properties"> 534 | <Entry.Match> 535 | <And> 536 | <Access Is="Protected" /> 537 | <Kind Is="Property" /> 538 | </And> 539 | </Entry.Match> 540 | </Entry> 541 | <Group DisplayName="Private Fields"> 542 | <Entry DisplayName="Constant Fields"> 543 | <Entry.Match> 544 | <And> 545 | <Access Is="Private" /> 546 | <Or> 547 | <Kind Is="Constant" /> 548 | <Readonly /> 549 | <And> 550 | <Static /> 551 | <Readonly /> 552 | </And> 553 | </Or> 554 | </And> 555 | </Entry.Match> 556 | </Entry> 557 | <Entry DisplayName="Static Fields"> 558 | <Entry.Match> 559 | <And> 560 | <Access Is="Private" /> 561 | <Static /> 562 | <Not> 563 | <Readonly /> 564 | </Not> 565 | <Kind Is="Field" /> 566 | </And> 567 | </Entry.Match> 568 | </Entry> 569 | <Entry DisplayName="Normal Fields"> 570 | <Entry.Match> 571 | <And> 572 | <Access Is="Private" /> 573 | <Not> 574 | <Or> 575 | <Static /> 576 | <Readonly /> 577 | </Or> 578 | </Not> 579 | <Kind Is="Field" /> 580 | </And> 581 | </Entry.Match> 582 | </Entry> 583 | </Group> 584 | <Entry DisplayName="Private Properties"> 585 | <Entry.Match> 586 | <And> 587 | <Access Is="Private" /> 588 | <Kind Is="Property" /> 589 | </And> 590 | </Entry.Match> 591 | </Entry> 592 | </Group> 593 | <Group DisplayName="Constructor/Destructor"> 594 | <Entry DisplayName="Ctor"> 595 | <Entry.Match> 596 | <Kind Is="Constructor" /> 597 | </Entry.Match> 598 | </Entry> 599 | <Region Name="Disposal"> 600 | <Entry DisplayName="Dtor"> 601 | <Entry.Match> 602 | <Kind Is="Destructor" /> 603 | </Entry.Match> 604 | </Entry> 605 | <Entry DisplayName="Dispose()"> 606 | <Entry.Match> 607 | <And> 608 | <Access Is="Public" /> 609 | <Kind Is="Method" /> 610 | <Name Is="Dispose" /> 611 | </And> 612 | </Entry.Match> 613 | </Entry> 614 | <Entry DisplayName="Dispose(true)"> 615 | <Entry.Match> 616 | <And> 617 | <Access Is="Protected" /> 618 | <Or> 619 | <Virtual /> 620 | <Override /> 621 | </Or> 622 | <Kind Is="Method" /> 623 | <Name Is="Dispose" /> 624 | </And> 625 | </Entry.Match> 626 | </Entry> 627 | </Region> 628 | </Group> 629 | <Group DisplayName="Methods"> 630 | <Group DisplayName="Public"> 631 | <Entry DisplayName="Static Methods"> 632 | <Entry.Match> 633 | <And> 634 | <Access Is="Public" /> 635 | <Static /> 636 | <Kind Is="Method" /> 637 | </And> 638 | </Entry.Match> 639 | </Entry> 640 | <Entry DisplayName="Methods"> 641 | <Entry.Match> 642 | <And> 643 | <Access Is="Public" /> 644 | <Not> 645 | <Static /> 646 | </Not> 647 | <Kind Is="Method" /> 648 | </And> 649 | </Entry.Match> 650 | </Entry> 651 | </Group> 652 | <Group DisplayName="Internal"> 653 | <Entry DisplayName="Static Methods"> 654 | <Entry.Match> 655 | <And> 656 | <Access Is="Internal" /> 657 | <Static /> 658 | <Kind Is="Method" /> 659 | </And> 660 | </Entry.Match> 661 | </Entry> 662 | <Entry DisplayName="Methods"> 663 | <Entry.Match> 664 | <And> 665 | <Access Is="Internal" /> 666 | <Not> 667 | <Static /> 668 | </Not> 669 | <Kind Is="Method" /> 670 | </And> 671 | </Entry.Match> 672 | </Entry> 673 | </Group> 674 | <Group DisplayName="Protected"> 675 | <Entry DisplayName="Static Methods"> 676 | <Entry.Match> 677 | <And> 678 | <Access Is="Protected" /> 679 | <Static /> 680 | <Kind Is="Method" /> 681 | </And> 682 | </Entry.Match> 683 | </Entry> 684 | <Entry DisplayName="Methods"> 685 | <Entry.Match> 686 | <And> 687 | <Access Is="Protected" /> 688 | <Not> 689 | <Static /> 690 | </Not> 691 | <Kind Is="Method" /> 692 | </And> 693 | </Entry.Match> 694 | </Entry> 695 | </Group> 696 | <Group DisplayName="Private"> 697 | <Entry DisplayName="Static Methods"> 698 | <Entry.Match> 699 | <And> 700 | <Access Is="Private" /> 701 | <Static /> 702 | <Kind Is="Method" /> 703 | </And> 704 | </Entry.Match> 705 | </Entry> 706 | <Entry DisplayName="Methods"> 707 | <Entry.Match> 708 | <And> 709 | <Access Is="Private" /> 710 | <Not> 711 | <Static /> 712 | </Not> 713 | <Kind Is="Method" /> 714 | </And> 715 | </Entry.Match> 716 | </Entry> 717 | </Group> 718 | </Group> 719 | </TypePattern> 720 | </Patterns> 721 | Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 722 | See the LICENCE file in the repository root for full licence text. 723 | 724 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 725 | <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> 726 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 727 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 728 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 729 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> 730 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 731 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 732 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 733 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 734 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> 735 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> 736 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"><ElementKinds><Kind Name="TYPE_PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 737 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></Policy> 738 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> 739 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local functions"><ElementKinds><Kind Name="LOCAL_FUNCTION" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 740 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></Policy> 741 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 742 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 743 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 744 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy> 745 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy> 746 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> 747 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> 748 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 749 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 750 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 751 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 752 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 753 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 754 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 755 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 756 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 757 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 758 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 759 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 760 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 761 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 762 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 763 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 764 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 765 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 766 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 767 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 768 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 769 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 770 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 771 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 772 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 773 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 774 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 775 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 776 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 777 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 778 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 779 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 780 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 781 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 782 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 783 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 784 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 785 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 786 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 787 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 788 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 789 | 790 | True 791 | True 792 | True 793 | True 794 | True 795 | True 796 | True 797 | True 798 | True 799 | True 800 | True 801 | True 802 | True 803 | True 804 | TestFolder 805 | True 806 | True 807 | o!f – Object Initializer: Anchor&Origin 808 | True 809 | constant("Centre") 810 | 0 811 | True 812 | True 813 | 2.0 814 | InCSharpFile 815 | ofao 816 | True 817 | Anchor = Anchor.$anchor$, 818 | Origin = Anchor.$anchor$, 819 | True 820 | True 821 | o!f – InternalChildren = [] 822 | True 823 | True 824 | 2.0 825 | InCSharpFile 826 | ofic 827 | True 828 | InternalChildren = new Drawable[] 829 | { 830 | $END$ 831 | }; 832 | True 833 | True 834 | o!f – new GridContainer { .. } 835 | True 836 | True 837 | 2.0 838 | InCSharpFile 839 | ofgc 840 | True 841 | new GridContainer 842 | { 843 | RelativeSizeAxes = Axes.Both, 844 | Content = new[] 845 | { 846 | new Drawable[] { $END$ }, 847 | new Drawable[] { } 848 | } 849 | }; 850 | True 851 | True 852 | o!f – new FillFlowContainer { .. } 853 | True 854 | True 855 | 2.0 856 | InCSharpFile 857 | offf 858 | True 859 | new FillFlowContainer 860 | { 861 | RelativeSizeAxes = Axes.Both, 862 | Direction = FillDirection.Vertical, 863 | Children = new Drawable[] 864 | { 865 | $END$ 866 | } 867 | }, 868 | True 869 | True 870 | o!f – new Container { .. } 871 | True 872 | True 873 | 2.0 874 | InCSharpFile 875 | ofcont 876 | True 877 | new Container 878 | { 879 | RelativeSizeAxes = Axes.Both, 880 | Children = new Drawable[] 881 | { 882 | $END$ 883 | } 884 | }, 885 | True 886 | True 887 | o!f – BackgroundDependencyLoader load() 888 | True 889 | True 890 | 2.0 891 | InCSharpFile 892 | ofbdl 893 | True 894 | [BackgroundDependencyLoader] 895 | private void load() 896 | { 897 | $END$ 898 | } 899 | True 900 | True 901 | o!f – new Box { .. } 902 | True 903 | True 904 | 2.0 905 | InCSharpFile 906 | ofbox 907 | True 908 | new Box 909 | { 910 | Colour = Color4.Black, 911 | RelativeSizeAxes = Axes.Both, 912 | }, 913 | True 914 | True 915 | o!f – Children = [] 916 | True 917 | True 918 | 2.0 919 | InCSharpFile 920 | ofc 921 | True 922 | Children = new Drawable[] 923 | { 924 | $END$ 925 | }; 926 | True 927 | True 928 | True 929 | True 930 | True 931 | True 932 | True 933 | True 934 | True 935 | True 936 | True 937 | True 938 | True 939 | True 940 | True 941 | True 942 | True 943 | True 944 | True 945 | True 946 | True 947 | True 948 | True 949 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/AppSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | 6 | namespace osu.Server.DifficultyCalculator 7 | { 8 | public static class AppSettings 9 | { 10 | /// 11 | /// Whether to insert entries into the beatmaps table should they not exist. Should be false for production (beatmaps should already exist). 12 | /// 13 | public static readonly bool INSERT_BEATMAPS; 14 | 15 | /// 16 | /// Whether to insert entries to `osu_difficulty_attributes`. This is quite an intensive operation, and may be skipped when not required (ie. for sandbox runs). 17 | /// 18 | public static readonly bool SKIP_INSERT_ATTRIBUTES; 19 | 20 | /// 21 | /// A full or relative path used to store beatmaps. 22 | /// 23 | public static readonly string BEATMAPS_PATH; 24 | 25 | /// 26 | /// Whether beatmaps should be downloaded if they don't exist in . 27 | /// 28 | public static readonly bool ALLOW_DOWNLOAD; 29 | 30 | /// 31 | /// A URL used to download beatmaps with {0} being replaced with the beatmap_id. 32 | /// ie. "https://osu.ppy.sh/osu/{0}" 33 | /// 34 | public static readonly string DOWNLOAD_PATH; 35 | 36 | /// 37 | /// Whether downloaded files should be cached to . 38 | /// 39 | public static readonly bool SAVE_DOWNLOADED; 40 | 41 | static AppSettings() 42 | { 43 | INSERT_BEATMAPS = Environment.GetEnvironmentVariable("INSERT_BEATMAPS") == "1"; 44 | SKIP_INSERT_ATTRIBUTES = Environment.GetEnvironmentVariable("SKIP_INSERT_ATTRIBUTES") == "1"; 45 | ALLOW_DOWNLOAD = Environment.GetEnvironmentVariable("ALLOW_DOWNLOAD") == "1"; 46 | SAVE_DOWNLOADED = Environment.GetEnvironmentVariable("SAVE_DOWNLOADED") == "1"; 47 | 48 | BEATMAPS_PATH = Environment.GetEnvironmentVariable("BEATMAPS_PATH") ?? "osu"; 49 | DOWNLOAD_PATH = Environment.GetEnvironmentVariable("BEATMAP_DOWNLOAD_PATH") ?? "https://osu.ppy.sh/osu/{0}"; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/BeatmapLoader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.IO; 6 | using McMaster.Extensions.CommandLineUtils; 7 | using osu.Framework.Audio.Track; 8 | using osu.Framework.Graphics.Textures; 9 | using osu.Framework.IO.Network; 10 | using osu.Game.Beatmaps; 11 | using osu.Game.Beatmaps.Formats; 12 | using osu.Game.IO; 13 | using osu.Game.Rulesets.Catch; 14 | using osu.Game.Rulesets.Mania; 15 | using osu.Game.Rulesets.Osu; 16 | using osu.Game.Rulesets.Taiko; 17 | using osu.Game.Skinning; 18 | 19 | namespace osu.Server.DifficultyCalculator 20 | { 21 | public static class BeatmapLoader 22 | { 23 | public static WorkingBeatmap GetBeatmap(int beatmapId, bool verbose = false, bool forceDownload = true, IReporter? reporter = null) 24 | { 25 | string fileLocation = Path.Combine(AppSettings.BEATMAPS_PATH, beatmapId.ToString()) + ".osu"; 26 | 27 | if ((forceDownload || !File.Exists(fileLocation)) && AppSettings.ALLOW_DOWNLOAD) 28 | { 29 | if (verbose) 30 | reporter?.Verbose($"Downloading {beatmapId}."); 31 | 32 | var req = new WebRequest(string.Format(AppSettings.DOWNLOAD_PATH, beatmapId)) 33 | { 34 | AllowInsecureRequests = true 35 | }; 36 | 37 | req.Failed += _ => 38 | { 39 | if (verbose) 40 | reporter?.Error($"Failed to download {beatmapId}."); 41 | }; 42 | 43 | req.Finished += () => 44 | { 45 | if (verbose) 46 | reporter?.Verbose($"{beatmapId} successfully downloaded."); 47 | }; 48 | 49 | req.Perform(); 50 | 51 | var stream = req.ResponseStream; 52 | 53 | if (stream.Length == 0) 54 | throw new Exception("Beatmap download failed."); 55 | 56 | if (AppSettings.SAVE_DOWNLOADED) 57 | { 58 | using (var fileStream = File.Create(fileLocation)) 59 | { 60 | stream.CopyTo(fileStream); 61 | stream.Seek(0, SeekOrigin.Begin); 62 | } 63 | } 64 | 65 | return new LoaderWorkingBeatmap(stream); 66 | } 67 | 68 | if (!File.Exists(fileLocation)) 69 | throw new Exception("Beatmap file does not exist and was not downloaded."); 70 | 71 | return new LoaderWorkingBeatmap(fileLocation); 72 | } 73 | 74 | private class LoaderWorkingBeatmap : WorkingBeatmap 75 | { 76 | private readonly Beatmap beatmap; 77 | 78 | /// 79 | /// Constructs a new from a .osu file. 80 | /// 81 | /// The .osu file. 82 | public LoaderWorkingBeatmap(string file) 83 | : this(File.OpenRead(file)) 84 | { 85 | } 86 | 87 | public LoaderWorkingBeatmap(Stream stream) 88 | : this(new LineBufferedReader(stream)) 89 | { 90 | stream.Dispose(); 91 | } 92 | 93 | private LoaderWorkingBeatmap(LineBufferedReader reader) 94 | : this(Decoder.GetDecoder(reader).Decode(reader)) 95 | { 96 | } 97 | 98 | private LoaderWorkingBeatmap(Beatmap beatmap) 99 | : base(beatmap.BeatmapInfo, null) 100 | { 101 | this.beatmap = beatmap; 102 | 103 | switch (beatmap.BeatmapInfo.Ruleset.OnlineID) 104 | { 105 | case 0: 106 | beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; 107 | break; 108 | 109 | case 1: 110 | beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo; 111 | break; 112 | 113 | case 2: 114 | beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo; 115 | break; 116 | 117 | case 3: 118 | beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo; 119 | break; 120 | } 121 | } 122 | 123 | protected override IBeatmap GetBeatmap() => beatmap; 124 | public override Texture? GetBackground() => null; 125 | protected override Track? GetBeatmapTrack() => null; 126 | protected override ISkin? GetSkin() => null; 127 | public override Stream? GetStream(string storagePath) => null; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/AllCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using Dapper; 6 | using McMaster.Extensions.CommandLineUtils; 7 | using osu.Server.QueueProcessor; 8 | 9 | namespace osu.Server.DifficultyCalculator.Commands 10 | { 11 | [Command(Name = "all", Description = "Calculates the difficulty of all beatmaps in the database.")] 12 | public class AllCommand : CalculatorCommand 13 | { 14 | [Option(CommandOptionType.NoValue, Template = "-r|--ranked", Description = "Only calculate difficulty for ranked/approved/qualified/loved maps.")] 15 | public bool RankedOnly { get; set; } 16 | 17 | [Option("--sql", Description = "Specify a custom query to limit the scope of beatmaps")] 18 | public string? CustomQuery { get; set; } 19 | 20 | [Option("--from", Description = "The minimum beatmap id to calculate the difficulty for.")] 21 | public int StartId { get; set; } 22 | 23 | protected override IEnumerable GetBeatmaps() 24 | { 25 | using (var conn = DatabaseAccess.GetConnection()) 26 | { 27 | var condition = CombineSqlConditions( 28 | RankedOnly ? "`approved` >= 1" : null, 29 | $"`beatmap_id` >= {StartId}", 30 | "`deleted_at` IS NULL", 31 | CustomQuery 32 | ); 33 | 34 | return conn.Query($"SELECT `beatmap_id` FROM `osu_beatmaps` {condition}"); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/BeatmapsCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using McMaster.Extensions.CommandLineUtils; 7 | 8 | namespace osu.Server.DifficultyCalculator.Commands 9 | { 10 | [Command(Name = "beatmaps", Description = "Calculates the difficulty of specific beatmaps.")] 11 | public class BeatmapsCommand : CalculatorCommand 12 | { 13 | [Argument(0, "beatmap", Description = "One or more beatmap ids to calculate the difficulty for.")] 14 | public int[] BeatmapIds { get; set; } = Array.Empty(); 15 | 16 | protected override IEnumerable GetBeatmaps() => BeatmapIds; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/BeatmapsStringCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using McMaster.Extensions.CommandLineUtils; 7 | 8 | namespace osu.Server.DifficultyCalculator.Commands 9 | { 10 | [Command("beatmapsstring", Description = "A compatibility mode which accepts a comma-separated list of beatmap ids.")] 11 | public class BeatmapsStringCommand : CalculatorCommand 12 | { 13 | [Argument(0, "beatmaps", Description = "A comma-separated list of beatmap ids.")] 14 | public string Beatmaps { get; set; } = string.Empty; 15 | 16 | protected override IEnumerable GetBeatmaps() => Beatmaps.Split(',').Select(int.Parse); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/CalculatorCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using McMaster.Extensions.CommandLineUtils; 11 | 12 | namespace osu.Server.DifficultyCalculator.Commands 13 | { 14 | public abstract class CalculatorCommand : CommandBase 15 | { 16 | [Option(CommandOptionType.MultipleValue, Template = "-m|--mode ", Description = "Ruleset(s) to compute difficulty for.\n" 17 | + "0 - osu!\n" 18 | + "1 - osu!taiko\n" 19 | + "2 - osu!catch\n" 20 | + "3 - osu!mania")] 21 | public int[]? Rulesets { get; set; } 22 | 23 | [Option(CommandOptionType.NoValue, Template = "-ac|--allow-converts", Description = "Attempt to convert beatmaps to other rulesets to calculate difficulty.")] 24 | public bool Converts { get; set; } = false; 25 | 26 | [Option(CommandOptionType.SingleValue, Template = "-c|--concurrency", Description = "Number of threads to use. Default 1.")] 27 | public int Concurrency { get; set; } = 1; 28 | 29 | [Option(CommandOptionType.NoValue, Template = "-d|--force-download", Description = "Force download of all beatmaps.")] 30 | public bool ForceDownload { get; set; } 31 | 32 | [Option(CommandOptionType.NoValue, Template = "-v|--verbose", Description = "Provide verbose console output.")] 33 | public bool Verbose { get; set; } 34 | 35 | [Option(CommandOptionType.NoValue, Template = "-q|--quiet", Description = "Disable all console output.")] 36 | public bool Quiet { get; set; } 37 | 38 | [Option(CommandOptionType.SingleValue, Template = "-l|--log-file", Description = "The file to log output to.")] 39 | public string? LogFile { get; set; } 40 | 41 | [Option(CommandOptionType.NoValue, Template = "-dry|--dry-run", Description = "Whether to run the process without writing to the database.")] 42 | public bool DryRun { get; set; } 43 | 44 | [Option(CommandOptionType.SingleValue, Template = "--processing-mode", Description = "The mode in which to process beatmaps.")] 45 | public ProcessingMode ProcessingMode { get; set; } = ProcessingMode.All; 46 | 47 | private int[] threadBeatmapIds = null!; 48 | private IReporter reporter = null!; 49 | 50 | private int totalBeatmaps; 51 | private int processedBeatmaps; 52 | 53 | public void OnExecute(CommandLineApplication app, IConsole console) 54 | { 55 | reporter = new Reporter(console, LogFile) 56 | { 57 | IsQuiet = Quiet, 58 | IsVerbose = Verbose 59 | }; 60 | 61 | if (Concurrency < 1) 62 | { 63 | reporter.Error("Concurrency level must be above 1."); 64 | return; 65 | } 66 | 67 | threadBeatmapIds = new int[Concurrency]; 68 | 69 | var beatmaps = new ConcurrentQueue(GetBeatmaps()); 70 | 71 | totalBeatmaps = beatmaps.Count; 72 | 73 | var tasks = new Task[Concurrency]; 74 | 75 | for (int i = 0; i < Concurrency; i++) 76 | { 77 | int tmp = i; 78 | 79 | tasks[i] = Task.Factory.StartNew(() => 80 | { 81 | var calc = new ServerDifficultyCalculator(Rulesets, Converts, DryRun); 82 | 83 | while (beatmaps.TryDequeue(out int beatmapId)) 84 | { 85 | threadBeatmapIds[tmp] = beatmapId; 86 | reporter.Verbose($"Processing difficulty for beatmap {beatmapId}."); 87 | 88 | try 89 | { 90 | var beatmap = BeatmapLoader.GetBeatmap(beatmapId, Verbose, ForceDownload, reporter); 91 | 92 | // ensure the correct online id is set 93 | beatmap.BeatmapInfo.OnlineID = beatmapId; 94 | 95 | calc.Process(beatmap, ProcessingMode); 96 | 97 | reporter.Verbose($"Difficulty updated for beatmap {beatmapId}."); 98 | } 99 | catch (Exception e) 100 | { 101 | reporter.Error($"{beatmapId} failed with: {e.Message}"); 102 | } 103 | 104 | Interlocked.Increment(ref processedBeatmaps); 105 | } 106 | }); 107 | } 108 | 109 | reporter.Output($"Processing {totalBeatmaps} beatmaps."); 110 | 111 | using (new Timer(_ => outputProgress(), null, 1000, 1000)) 112 | using (new Timer(_ => outputHealth(), null, 5000, 5000)) 113 | Task.WaitAll(tasks); 114 | 115 | outputProgress(); 116 | 117 | reporter.Output("Done."); 118 | } 119 | 120 | private int lastProgress; 121 | 122 | private void outputProgress() 123 | { 124 | int processed = processedBeatmaps; 125 | reporter.Output($"Processed {processed} / {totalBeatmaps} ({processed - lastProgress}/sec)"); 126 | lastProgress = processed; 127 | } 128 | 129 | private void outputHealth() 130 | { 131 | // var process = Process.GetCurrentProcess(); 132 | //reporter.Output($"Health p:{process.PrivateMemorySize64.Bytes()} v:{process.VirtualMemorySize64.Bytes()} w:{process.WorkingSet64.Bytes()}"); 133 | 134 | string threadsString = string.Empty; 135 | for (int i = 0; i < threadBeatmapIds.Length; i++) 136 | threadsString += $"{i}:{threadBeatmapIds[i]} "; 137 | 138 | reporter.Output($"Threads {threadsString}"); 139 | } 140 | 141 | protected string CombineSqlConditions(params string?[] conditions) 142 | { 143 | var builder = new StringBuilder(); 144 | 145 | foreach (var c in conditions) 146 | { 147 | if (string.IsNullOrEmpty(c)) 148 | continue; 149 | 150 | builder.Append(builder.Length > 0 ? " AND " : " WHERE "); 151 | builder.Append(c); 152 | } 153 | 154 | return builder.ToString(); 155 | } 156 | 157 | protected abstract IEnumerable GetBeatmaps(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/CommandBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using McMaster.Extensions.CommandLineUtils; 5 | 6 | namespace osu.Server.DifficultyCalculator.Commands 7 | { 8 | [HelpOption("-?|-h|--help")] 9 | public abstract class CommandBase 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/FilesCommand.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using McMaster.Extensions.CommandLineUtils; 7 | 8 | namespace osu.Server.DifficultyCalculator.Commands 9 | { 10 | [Command(Name = "files", Description = "Computes the difficulty of all files in the beatmaps path.")] 11 | public class FilesCommand : CalculatorCommand 12 | { 13 | protected override IEnumerable GetBeatmaps() 14 | { 15 | var ids = new List(); 16 | 17 | foreach (var f in Directory.GetFiles(AppSettings.BEATMAPS_PATH)) 18 | { 19 | var filename = Path.GetFileNameWithoutExtension(f); 20 | 21 | if (int.TryParse(filename.Split('.')[0], out var id)) 22 | ids.Add(id); 23 | } 24 | 25 | return ids; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Commands/ProcessingMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | namespace osu.Server.DifficultyCalculator.Commands 5 | { 6 | public enum ProcessingMode 7 | { 8 | All, 9 | Difficulty, 10 | ScoreAttributes 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env 2 | WORKDIR /app 3 | 4 | # Handle project files and dependencies first for caching benefits 5 | COPY *.sln ./ 6 | COPY osu.Server.DifficultyCalculator/osu.Server.DifficultyCalculator.csproj ./osu.Server.DifficultyCalculator/ 7 | COPY osu.Server.Queues.BeatmapProcessor/osu.Server.Queues.BeatmapProcessor.csproj ./osu.Server.Queues.BeatmapProcessor/ 8 | 9 | RUN dotnet restore 10 | 11 | # Copy everything else and build 12 | COPY . ./ 13 | WORKDIR /app/osu.Server.DifficultyCalculator 14 | RUN dotnet publish -c Release -o out 15 | # get rid of bloat 16 | RUN rm -rf ./out/runtimes ./out/osu.Game.Resources.dll 17 | 18 | # Build runtime image 19 | FROM mcr.microsoft.com/dotnet/runtime:8.0 20 | WORKDIR /app 21 | COPY --from=build-env /app/osu.Server.DifficultyCalculator/out . 22 | ENTRYPOINT ["dotnet", "osu.Server.DifficultyCalculator.dll"] 23 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System.Net; 5 | using McMaster.Extensions.CommandLineUtils; 6 | using osu.Game.Beatmaps.Formats; 7 | using osu.Server.DifficultyCalculator.Commands; 8 | 9 | namespace osu.Server.DifficultyCalculator 10 | { 11 | [Command] 12 | [Subcommand(typeof(AllCommand))] 13 | [Subcommand(typeof(FilesCommand))] 14 | [Subcommand(typeof(BeatmapsCommand))] 15 | [Subcommand(typeof(BeatmapsStringCommand))] 16 | public class Program 17 | { 18 | public static int Main(string[] args) 19 | { 20 | LegacyDifficultyCalculatorBeatmapDecoder.Register(); 21 | ServicePointManager.DefaultConnectionLimit = 128; 22 | 23 | return CommandLineApplication.Execute(args); 24 | } 25 | 26 | public int OnExecute(CommandLineApplication app) 27 | { 28 | app.ShowHelp(); 29 | return 1; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/Reporter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.IO; 6 | using McMaster.Extensions.CommandLineUtils; 7 | 8 | namespace osu.Server.DifficultyCalculator 9 | { 10 | public class Reporter : IReporter 11 | { 12 | /// 13 | /// Whether verbose output should be displayed. 14 | /// 15 | public bool IsVerbose { get; set; } 16 | 17 | /// 18 | /// Whether quiet output should be displayed. 19 | /// 20 | public bool IsQuiet { get; set; } 21 | 22 | private readonly object writeLock = new object(); 23 | private readonly IConsole console; 24 | private readonly StreamWriter? fileWriter; 25 | private readonly StreamWriter errorFileWriter; 26 | 27 | public Reporter(IConsole console, string? file = null) 28 | { 29 | this.console = console; 30 | 31 | if (file != null) 32 | { 33 | try 34 | { 35 | fileWriter = new StreamWriter(file); 36 | } 37 | catch (Exception e) 38 | { 39 | Warn($"Failed to initialise log file ({file}): {e}"); 40 | Warn("Continuing without log file."); 41 | } 42 | } 43 | 44 | errorFileWriter = new StreamWriter("error.log", true) { AutoFlush = true }; 45 | } 46 | 47 | private void writeLine(TextWriter consoleWriter, string message, ConsoleColor? foregroundColour = null) 48 | { 49 | lock (writeLock) 50 | { 51 | if (foregroundColour.HasValue) 52 | Console.ForegroundColor = foregroundColour.Value; 53 | 54 | string line = $"[{DateTime.UtcNow}]: {message}"; 55 | 56 | if (!IsQuiet) 57 | consoleWriter.WriteLine(line); 58 | fileWriter?.WriteLine(line); 59 | 60 | if (consoleWriter == console.Error) 61 | errorFileWriter.WriteLine(line); 62 | 63 | if (foregroundColour.HasValue) 64 | Console.ResetColor(); 65 | } 66 | } 67 | 68 | /// 69 | /// Writes a message in to the console/file outputs. 70 | /// 71 | /// The message to write. 72 | public void Verbose(string message) 73 | { 74 | if (!IsVerbose) 75 | return; 76 | 77 | writeLine(console.Out, message, ConsoleColor.DarkGray); 78 | } 79 | 80 | public void Output(string message) => writeLine(console.Out, message); 81 | 82 | public void Warn(string message) => writeLine(console.Out, message, ConsoleColor.Yellow); 83 | 84 | public void Error(string message) => writeLine(console.Error, message, ConsoleColor.Red); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/ServerDifficultyCalculator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using Dapper; 10 | using MySqlConnector; 11 | using osu.Game.Beatmaps; 12 | using osu.Game.Beatmaps.Legacy; 13 | using osu.Game.Rulesets; 14 | using osu.Game.Rulesets.Mods; 15 | using osu.Game.Rulesets.Objects.Legacy; 16 | using osu.Game.Rulesets.Scoring.Legacy; 17 | using osu.Server.DifficultyCalculator.Commands; 18 | using osu.Server.QueueProcessor; 19 | 20 | namespace osu.Server.DifficultyCalculator 21 | { 22 | public class ServerDifficultyCalculator 23 | { 24 | private static readonly List available_rulesets = getRulesets(); 25 | 26 | private readonly bool processConverts; 27 | private readonly bool dryRun; 28 | private readonly List processableRulesets = new List(); 29 | 30 | public ServerDifficultyCalculator(int[]? rulesetIds = null, bool processConverts = true, bool dryRun = false) 31 | { 32 | this.processConverts = processConverts; 33 | this.dryRun = dryRun; 34 | 35 | if (rulesetIds != null) 36 | { 37 | foreach (int id in rulesetIds) 38 | processableRulesets.Add(available_rulesets.Single(r => r.RulesetInfo.OnlineID == id)); 39 | } 40 | else 41 | { 42 | processableRulesets.AddRange(available_rulesets); 43 | } 44 | } 45 | 46 | public void Process(WorkingBeatmap beatmap, ProcessingMode mode) 47 | { 48 | switch (mode) 49 | { 50 | case ProcessingMode.All: 51 | ProcessDifficulty(beatmap); 52 | ProcessLegacyAttributes(beatmap); 53 | break; 54 | 55 | case ProcessingMode.Difficulty: 56 | ProcessDifficulty(beatmap); 57 | break; 58 | 59 | case ProcessingMode.ScoreAttributes: 60 | ProcessLegacyAttributes(beatmap); 61 | break; 62 | 63 | default: 64 | throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported processing mode supplied"); 65 | } 66 | } 67 | 68 | public void ProcessDifficulty(WorkingBeatmap beatmap) => run(beatmap, processDifficulty); 69 | 70 | public void ProcessLegacyAttributes(WorkingBeatmap beatmap) => run(beatmap, processLegacyAttributes); 71 | 72 | private void run(WorkingBeatmap beatmap, Action callback) 73 | { 74 | try 75 | { 76 | bool ranked; 77 | 78 | using (var conn = DatabaseAccess.GetConnection()) 79 | { 80 | ranked = conn.QuerySingleOrDefault("SELECT `approved` FROM `osu_beatmaps` WHERE `beatmap_id` = @BeatmapId", new 81 | { 82 | BeatmapId = beatmap.BeatmapInfo.OnlineID 83 | }) > 0; 84 | 85 | if (ranked && beatmap.Beatmap.HitObjects.Count == 0) 86 | throw new ArgumentException($"Ranked beatmap {beatmap.BeatmapInfo.OnlineInfo} has 0 hitobjects!"); 87 | } 88 | 89 | using (var conn = DatabaseAccess.GetConnection()) 90 | { 91 | if (processConverts && beatmap.BeatmapInfo.Ruleset.OnlineID == 0) 92 | { 93 | foreach (var ruleset in processableRulesets) 94 | callback(new ProcessableItem(beatmap, ruleset, ranked), conn); 95 | } 96 | else if (processableRulesets.Any(r => r.RulesetInfo.OnlineID == beatmap.BeatmapInfo.Ruleset.OnlineID)) 97 | callback(new ProcessableItem(beatmap, beatmap.BeatmapInfo.Ruleset.CreateInstance(), ranked), conn); 98 | } 99 | } 100 | catch (Exception e) 101 | { 102 | throw new Exception($"{beatmap.BeatmapInfo.OnlineID} failed with: {e.Message}"); 103 | } 104 | } 105 | 106 | private void processDifficulty(ProcessableItem item, MySqlConnection conn) 107 | { 108 | foreach (var attribute in item.Ruleset.CreateDifficultyCalculator(item.WorkingBeatmap).CalculateAllLegacyCombinations()) 109 | { 110 | if (dryRun) 111 | continue; 112 | 113 | LegacyMods legacyMods = item.Ruleset.ConvertToLegacyMods(attribute.Mods); 114 | 115 | conn.Execute( 116 | "INSERT INTO `osu_beatmap_difficulty` (`beatmap_id`, `mode`, `mods`, `diff_unified`) " 117 | + "VALUES (@BeatmapId, @Mode, @Mods, @Diff) " 118 | + "ON DUPLICATE KEY UPDATE `diff_unified` = @Diff", 119 | new 120 | { 121 | BeatmapId = item.BeatmapID, 122 | Mode = item.RulesetID, 123 | Mods = (int)legacyMods, 124 | Diff = attribute.StarRating 125 | }); 126 | 127 | if (item.Ranked && !AppSettings.SKIP_INSERT_ATTRIBUTES) 128 | { 129 | var parameters = new List(); 130 | 131 | foreach (var mapping in attribute.ToDatabaseAttributes()) 132 | { 133 | parameters.Add(new 134 | { 135 | BeatmapId = item.BeatmapID, 136 | Mode = item.RulesetID, 137 | Mods = (int)legacyMods, 138 | Attribute = mapping.attributeId, 139 | Value = Convert.ToSingle(mapping.value) 140 | }); 141 | } 142 | 143 | conn.Execute( 144 | "INSERT INTO `osu_beatmap_difficulty_attribs` (`beatmap_id`, `mode`, `mods`, `attrib_id`, `value`) " 145 | + "VALUES (@BeatmapId, @Mode, @Mods, @Attribute, @Value) " 146 | + "ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)", 147 | parameters.ToArray()); 148 | } 149 | 150 | if (legacyMods == LegacyMods.None && item.Ruleset.RulesetInfo.Equals(item.WorkingBeatmap.BeatmapInfo.Ruleset)) 151 | { 152 | double beatLength = item.WorkingBeatmap.Beatmap.GetMostCommonBeatLength(); 153 | double bpm = beatLength > 0 ? 60000 / beatLength : 0; 154 | 155 | int countCircle = 0; 156 | int countSlider = 0; 157 | int countSpinner = 0; 158 | 159 | foreach (var obj in item.WorkingBeatmap.Beatmap.HitObjects.OfType()) 160 | { 161 | if ((obj.LegacyType & LegacyHitObjectType.Circle) > 0) 162 | countCircle++; 163 | if ((obj.LegacyType & LegacyHitObjectType.Slider) > 0 || (obj.LegacyType & LegacyHitObjectType.Hold) > 0) 164 | countSlider++; 165 | if ((obj.LegacyType & LegacyHitObjectType.Spinner) > 0) 166 | countSpinner++; 167 | } 168 | 169 | object param = new 170 | { 171 | BeatmapId = item.BeatmapID, 172 | Diff = attribute.StarRating, 173 | AR = item.WorkingBeatmap.BeatmapInfo.Difficulty.ApproachRate, 174 | OD = item.WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty, 175 | HP = item.WorkingBeatmap.BeatmapInfo.Difficulty.DrainRate, 176 | CS = item.WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize, 177 | BPM = Math.Round(bpm, 2), 178 | MaxCombo = attribute.MaxCombo, 179 | CountCircle = countCircle, 180 | CountSlider = countSlider, 181 | CountSpinner = countSpinner, 182 | CountTotal = countCircle + countSlider + countSpinner 183 | }; 184 | 185 | if (AppSettings.INSERT_BEATMAPS) 186 | { 187 | conn.Execute( 188 | "INSERT INTO `osu_beatmaps` (`beatmap_id`, `difficultyrating`, `diff_approach`, `diff_overall`, `diff_drain`, `diff_size`, `bpm`, `max_combo`, `countNormal`, `countSlider`, `countSpinner`, `countTotal`) " 189 | + "VALUES (@BeatmapId, @Diff, @AR, @OD, @HP, @CS, @BPM, @MaxCombo, @CountCircle, @CountSlider, @CountSpinner, @CountTotal) " 190 | + "ON DUPLICATE KEY UPDATE `difficultyrating` = @Diff, `diff_approach` = @AR, `diff_overall` = @OD, `diff_drain` = @HP, `diff_size` = @CS, `bpm` = @BPM, `max_combo` = @MaxCombo, `countNormal` = @CountCircle, `countSlider` = @CountSlider, `countSpinner` = @CountSpinner, `countTotal` = @CountTotal", 191 | param); 192 | } 193 | else 194 | { 195 | conn.Execute( 196 | "UPDATE `osu_beatmaps` SET `difficultyrating` = @Diff, `diff_approach` = @AR, `diff_overall` = @OD, `diff_drain` = @HP, `diff_size` = @CS, `bpm` = @BPM , `max_combo` = @MaxCombo, `countNormal` = @CountCircle, `countSlider` = @CountSlider, `countSpinner` = @CountSpinner, `countTotal` = @CountTotal " 197 | + "WHERE `beatmap_id` = @BeatmapId", 198 | param); 199 | } 200 | } 201 | } 202 | } 203 | 204 | private void processLegacyAttributes(ProcessableItem item, MySqlConnection conn) 205 | { 206 | Mod? classicMod = item.Ruleset.CreateMod(); 207 | Mod[] mods = classicMod != null ? new[] { classicMod } : Array.Empty(); 208 | 209 | ILegacyScoreSimulator simulator = ((ILegacyRuleset)item.Ruleset).CreateLegacyScoreSimulator(); 210 | LegacyScoreAttributes attributes = simulator.Simulate(item.WorkingBeatmap, item.WorkingBeatmap.GetPlayableBeatmap(item.Ruleset.RulesetInfo, mods)); 211 | 212 | if (dryRun) 213 | return; 214 | 215 | conn.Execute( 216 | "INSERT INTO `osu_beatmap_scoring_attribs` (`beatmap_id`, `mode`, `legacy_accuracy_score`, `legacy_combo_score`, `legacy_bonus_score_ratio`, `legacy_bonus_score`, `max_combo`) " 217 | + "VALUES (@BeatmapId, @Mode, @AccuracyScore, @ComboScore, @BonusScoreRatio, @BonusScore, @MaxCombo) " 218 | + "ON DUPLICATE KEY UPDATE `legacy_accuracy_score` = @AccuracyScore, `legacy_combo_score` = @ComboScore, `legacy_bonus_score_ratio` = @BonusScoreRatio, `legacy_bonus_score` = @BonusScore, `max_combo` = @MaxCombo", 219 | new 220 | { 221 | BeatmapId = item.BeatmapID, 222 | Mode = item.RulesetID, 223 | AccuracyScore = attributes.AccuracyScore, 224 | ComboScore = attributes.ComboScore, 225 | BonusScoreRatio = attributes.BonusScoreRatio, 226 | BonusScore = attributes.BonusScore, 227 | MaxCombo = attributes.MaxCombo 228 | }); 229 | } 230 | 231 | private static List getRulesets() 232 | { 233 | const string ruleset_library_prefix = "osu.Game.Rulesets"; 234 | 235 | var rulesetsToProcess = new List(); 236 | 237 | foreach (string file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, $"{ruleset_library_prefix}.*.dll")) 238 | { 239 | try 240 | { 241 | var assembly = Assembly.LoadFrom(file); 242 | Type type = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); 243 | rulesetsToProcess.Add((Ruleset)Activator.CreateInstance(type)!); 244 | } 245 | catch 246 | { 247 | throw new Exception($"Failed to load ruleset ({file})"); 248 | } 249 | } 250 | 251 | return rulesetsToProcess; 252 | } 253 | 254 | private readonly record struct ProcessableItem(WorkingBeatmap WorkingBeatmap, Ruleset Ruleset, bool Ranked) 255 | { 256 | public int BeatmapID => WorkingBeatmap.BeatmapInfo.OnlineID; 257 | public int RulesetID => Ruleset.RulesetInfo.OnlineID; 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /osu.Server.DifficultyCalculator/osu.Server.DifficultyCalculator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | osu.Server.DifficultyCalculator 6 | osu.Server.DifficultyCalculator 7 | latest 8 | enable 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/.dockerignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/BeatmapItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using System.Diagnostics.CodeAnalysis; 5 | using osu.Server.QueueProcessor; 6 | 7 | namespace osu.Server.Queues.BeatmapProcessor 8 | { 9 | [SuppressMessage("ReSharper", "InconsistentNaming")] 10 | public class BeatmapItem : QueueItem 11 | { 12 | public long beatmapset_id { get; set; } 13 | 14 | public override string ToString() => $"beatmapset_id:{beatmapset_id}"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/BeatmapProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using Dapper; 5 | using osu.Server.DifficultyCalculator; 6 | using osu.Server.DifficultyCalculator.Commands; 7 | using osu.Server.QueueProcessor; 8 | 9 | namespace osu.Server.Queues.BeatmapProcessor 10 | { 11 | internal class BeatmapProcessor : QueueProcessor 12 | { 13 | private readonly ProcessingMode processingMode; 14 | private readonly ServerDifficultyCalculator calculator; 15 | 16 | public BeatmapProcessor(ProcessingMode processingMode, string queueName) 17 | : base(new QueueConfiguration 18 | { 19 | InputQueueName = queueName, 20 | MaxInFlightItems = 4, 21 | }) 22 | { 23 | this.processingMode = processingMode; 24 | calculator = new ServerDifficultyCalculator(new[] { 0, 1, 2, 3 }); 25 | } 26 | 27 | protected override void ProcessResult(BeatmapItem item) 28 | { 29 | using (var db = GetDatabaseConnection()) 30 | { 31 | var beatmaps = db.Query("SELECT beatmap_id FROM osu_beatmaps WHERE beatmapset_id = @beatmapset_id AND deleted_at IS NULL", item); 32 | 33 | foreach (long beatmapId in beatmaps) 34 | { 35 | var working = BeatmapLoader.GetBeatmap((int)beatmapId); 36 | 37 | // ensure the correct online id is set 38 | working.BeatmapInfo.OnlineID = (int)beatmapId; 39 | 40 | calculator.Process(working, processingMode); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env 2 | WORKDIR /app 3 | 4 | # Handle project files and dependencies first for caching benefits 5 | COPY *.sln ./ 6 | COPY osu.Server.DifficultyCalculator/osu.Server.DifficultyCalculator.csproj ./osu.Server.DifficultyCalculator/ 7 | COPY osu.Server.Queues.BeatmapProcessor/osu.Server.Queues.BeatmapProcessor.csproj ./osu.Server.Queues.BeatmapProcessor/ 8 | 9 | RUN dotnet restore 10 | 11 | # Copy everything else and build 12 | COPY . ./ 13 | WORKDIR /app/osu.Server.Queues.BeatmapProcessor 14 | RUN dotnet publish -c Release -o out 15 | # get rid of bloat 16 | RUN rm -rf ./out/runtimes ./out/osu.Game.Resources.dll 17 | 18 | # Build runtime image 19 | FROM mcr.microsoft.com/dotnet/runtime:8.0 20 | WORKDIR /app 21 | COPY --from=build-env /app/osu.Server.Queues.BeatmapProcessor/out . 22 | ENTRYPOINT ["dotnet", "osu.Server.Queues.BeatmapProcessor.dll"] 23 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. 2 | // See the LICENCE file in the repository root for full licence text. 3 | 4 | using JetBrains.Annotations; 5 | using McMaster.Extensions.CommandLineUtils; 6 | using osu.Server.DifficultyCalculator.Commands; 7 | 8 | namespace osu.Server.Queues.BeatmapProcessor 9 | { 10 | [Command] 11 | public class Program 12 | { 13 | [Argument(0, "mode", "The target mode to process the beatmaps from the queue in.")] 14 | [UsedImplicitly] 15 | private ProcessingMode processingMode { get; } = ProcessingMode.All; 16 | 17 | [Argument(1, "queue-name", "The name of the queue to watch. The `osu-queue:` prefix must be omitted.")] 18 | [UsedImplicitly] 19 | private string queueName { get; } = "beatmap"; 20 | 21 | public static void Main(string[] args) => CommandLineApplication.Execute(args); 22 | 23 | [UsedImplicitly] 24 | public int OnExecute(CommandLineApplication app) 25 | { 26 | new BeatmapProcessor(processingMode, queueName).Run(); 27 | return 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /osu.Server.Queues.BeatmapProcessor/osu.Server.Queues.BeatmapProcessor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | latest 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------