mirror of
https://github.com/ppy/osu.git
synced 2026-05-14 05:24:07 +08:00
Compare commits
32 Commits
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2025.2.3",
|
||||
"commands": [
|
||||
"jb"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"codefilesanity": {
|
||||
"version": "0.0.41",
|
||||
"commands": [
|
||||
"CodeFileSanity"
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2025.1208.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-193
@@ -1,17 +1,6 @@
|
||||
# EditorConfig is awesome: http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*.{csproj,props,targets}]
|
||||
charset = utf-8-bom
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[g_*.cs]
|
||||
generated_code = true
|
||||
|
||||
[*.cs]
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
@@ -19,196 +8,20 @@ indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# temporary workaround for https://youtrack.jetbrains.com/issue/RIDER-130051/Cannot-resolve-symbol-inspections-incorrectly-firing-for-xmldoc-protected-member-references
|
||||
resharper_c_sharp_warnings_cs1574_cs1584_cs1581_cs1580_highlighting = hint
|
||||
# temporary workaround for https://youtrack.jetbrains.com/issue/RIDER-130381/Rider-does-not-respect-propagated-NoWarn-CS1591?backToIssues=false
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
|
||||
#license header
|
||||
file_header_template = Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#Roslyn naming styles
|
||||
|
||||
#PascalCase for public and protected members
|
||||
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
|
||||
dotnet_naming_rule.public_members_pascalcase.severity = error
|
||||
dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal
|
||||
dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate
|
||||
dotnet_naming_rule.public_members_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.public_members_pascalcase.symbols = public_members
|
||||
dotnet_naming_rule.public_members_pascalcase.style = pascalcase
|
||||
|
||||
#camelCase for private members
|
||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||
|
||||
dotnet_naming_symbols.private_members.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
|
||||
dotnet_naming_rule.private_members_camelcase.severity = warning
|
||||
dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate
|
||||
dotnet_naming_rule.private_members_camelcase.severity = suggestion
|
||||
dotnet_naming_rule.private_members_camelcase.symbols = private_members
|
||||
dotnet_naming_rule.private_members_camelcase.style = camelcase
|
||||
|
||||
dotnet_naming_symbols.local_function.applicable_kinds = local_function
|
||||
dotnet_naming_rule.local_function_camelcase.severity = warning
|
||||
dotnet_naming_rule.local_function_camelcase.symbols = local_function
|
||||
dotnet_naming_rule.local_function_camelcase.style = camelcase
|
||||
|
||||
#all_lower for private and local constants/static readonlys
|
||||
dotnet_naming_style.all_lower.capitalization = all_lower
|
||||
dotnet_naming_style.all_lower.word_separator = _
|
||||
|
||||
dotnet_naming_symbols.private_constants.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_constants.required_modifiers = const
|
||||
dotnet_naming_symbols.private_constants.applicable_kinds = field
|
||||
dotnet_naming_rule.private_const_all_lower.severity = warning
|
||||
dotnet_naming_rule.private_const_all_lower.symbols = private_constants
|
||||
dotnet_naming_rule.private_const_all_lower.style = all_lower
|
||||
|
||||
dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
|
||||
dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
|
||||
dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
|
||||
dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
|
||||
dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
|
||||
|
||||
dotnet_naming_symbols.local_constants.applicable_kinds = local
|
||||
dotnet_naming_symbols.local_constants.required_modifiers = const
|
||||
dotnet_naming_rule.local_const_all_lower.severity = warning
|
||||
dotnet_naming_rule.local_const_all_lower.symbols = local_constants
|
||||
dotnet_naming_rule.local_const_all_lower.style = all_lower
|
||||
|
||||
#ALL_UPPER for non private constants/static readonlys
|
||||
dotnet_naming_style.all_upper.capitalization = all_upper
|
||||
dotnet_naming_style.all_upper.word_separator = _
|
||||
|
||||
dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_constants.required_modifiers = const
|
||||
dotnet_naming_symbols.public_constants.applicable_kinds = field
|
||||
dotnet_naming_rule.public_const_all_upper.severity = warning
|
||||
dotnet_naming_rule.public_const_all_upper.symbols = public_constants
|
||||
dotnet_naming_rule.public_const_all_upper.style = all_upper
|
||||
|
||||
dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
|
||||
dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
|
||||
dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
|
||||
dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
|
||||
dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
|
||||
|
||||
#Roslyn formating options
|
||||
|
||||
#Formatting - indentation options
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = false
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
#Formatting - new line options
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
#csharp_new_line_before_members_in_anonymous_types = true
|
||||
#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
#Formatting - organize using options
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
#Formatting - spacing options
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
|
||||
#Formatting - wrapping options
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#Roslyn language styles
|
||||
|
||||
#Style - this. qualification
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
|
||||
#Style - type names
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
|
||||
dotnet_style_predefined_type_for_member_access = true:warning
|
||||
csharp_style_var_when_type_is_apparent = true:none
|
||||
csharp_style_var_for_built_in_types = false:warning
|
||||
csharp_style_var_elsewhere = true:silent
|
||||
|
||||
#Style - modifiers
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
|
||||
|
||||
#Style - parentheses
|
||||
# Skipped because roslyn cannot separate +-*/ with << >>
|
||||
|
||||
#Style - expression bodies
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_indexers = true:warning
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_operators = true:warning
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
csharp_style_expression_bodied_local_functions = true:silent
|
||||
|
||||
#Style - expression preferences
|
||||
dotnet_style_object_initializer = true:warning
|
||||
dotnet_style_collection_initializer = true:warning
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
|
||||
dotnet_style_prefer_auto_properties = true:warning
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_compound_assignment = true:warning
|
||||
|
||||
#Style - null/type checks
|
||||
dotnet_style_coalesce_expression = true:warning
|
||||
dotnet_style_null_propagation = true:warning
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||
csharp_style_throw_expression = true:silent
|
||||
csharp_style_conditional_delegate_call = true:warning
|
||||
|
||||
#Style - unused
|
||||
dotnet_style_readonly_field = true:silent
|
||||
dotnet_code_quality_unused_parameters = non_public:silent
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||
|
||||
#Style - variable declaration
|
||||
csharp_style_inlined_variable_declaration = true:warning
|
||||
csharp_style_deconstructed_variable_declaration = false:silent
|
||||
|
||||
#Style - other C# 7.x features
|
||||
dotnet_style_prefer_inferred_tuple_names = true:warning
|
||||
csharp_prefer_simple_default_expression = true:warning
|
||||
csharp_style_pattern_local_over_anonymous_function = true:warning
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
|
||||
#Style - C# 8 features
|
||||
csharp_prefer_static_local_function = true:warning
|
||||
csharp_prefer_simple_using_statement = true:silent
|
||||
csharp_style_prefer_index_operator = false:silent
|
||||
csharp_style_prefer_range_operator = false:silent
|
||||
csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
|
||||
#Style - C# 12 features
|
||||
csharp_style_prefer_primary_constructors = false
|
||||
|
||||
[*.{yaml,yml}]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
dotnet_diagnostic.OLOC001.words_in_name = 5
|
||||
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
|
||||
dotnet_naming_rule.private_members_camelcase.style = camelcase
|
||||
@@ -1,10 +0,0 @@
|
||||
# Normalize all the line endings
|
||||
32a74f95a5c80a0ed18e693f13a47522099df5c3
|
||||
# Partial everything
|
||||
7bc8908ca9c026fed1d831eb6e58df7624a8d614
|
||||
# Add a few more missing partial specs
|
||||
212d78865a6b5f091173a347bad5686834d1d5fe
|
||||
# Add partial specs in mobile projects too
|
||||
00c11b2b4e389e48f3995d63484a6bc66a7afbdb
|
||||
# Mass NRT enabling
|
||||
0ab0c52ad577b3e7b406d09fa6056a56ff997c3e
|
||||
+19
-25
@@ -1,25 +1,19 @@
|
||||
# Autodetect text files and ensure that we normalise their
|
||||
# line endings to lf internally. When checked out they may
|
||||
# use different line endings.
|
||||
* text=auto
|
||||
|
||||
# Check out with crlf (Windows) line endings
|
||||
*.sln text eol=crlf
|
||||
*.csproj text eol=crlf
|
||||
*.cs text diff=csharp eol=crlf
|
||||
*.resx text eol=crlf
|
||||
*.vsixmanifest text eol=crlf
|
||||
packages.config text eol=crlf
|
||||
App.config text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.snippet text eol=crlf
|
||||
*.manifest text eol=crlf
|
||||
*.licenseheader text eol=crlf
|
||||
|
||||
# Check out with lf (UNIX) line endings
|
||||
*.sh text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
*.md text eol=lf
|
||||
.travis.yml text eol=lf
|
||||
# This won't normalise line endings, but it will ensure that merge drivers use CRLF
|
||||
* -text eol=crlf
|
||||
|
||||
# Currently in-use binary file extensions
|
||||
*.blend binary
|
||||
*.bmp binary
|
||||
*.dll binary
|
||||
*.exe binary
|
||||
*.icns binary
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.osz2 binary
|
||||
*.pdn binary
|
||||
*.psd binary
|
||||
*.PSD binary
|
||||
*.tga binary
|
||||
*.ttf binary
|
||||
*.wav binary
|
||||
*.xnb binary
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
github: ppy
|
||||
custom: https://osu.ppy.sh/home/support
|
||||
@@ -1,75 +0,0 @@
|
||||
name: Bug report
|
||||
description: Report a very clearly broken issue.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# osu! bug report
|
||||
|
||||
Important to note that your issue may have already been reported before. Please check:
|
||||
- Pinned issues, at the top of https://github.com/ppy/osu/issues.
|
||||
- Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0).
|
||||
- And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful.
|
||||
|
||||
# ATTENTION LINUX USERS
|
||||
|
||||
If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software.
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Crash to desktop
|
||||
- Game behaviour
|
||||
- Performance
|
||||
- Cosmetic
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: How did you find the bug? Any additional details that might help?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots or videos
|
||||
description: Add screenshots or videos that show the bug here.
|
||||
placeholder: Drag and drop the screenshots/videos into this box.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: The version you encountered this bug on. This is shown at the end of the settings overlay.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Logs
|
||||
|
||||
Attaching log files is required for **every** issue, regardless of whether you deem them required or not. See instructions below on how to find them.
|
||||
|
||||
### Desktop platforms
|
||||
|
||||
If the game has not yet been closed since you found the bug:
|
||||
1. Head on to game settings and click on "Export logs"
|
||||
2. Click the notification to locate the file
|
||||
3. Drag the generated `.zip` files into the github issue window
|
||||
|
||||

|
||||
|
||||
### Mobile platforms
|
||||
|
||||
The places to find the logs on mobile platforms are as follows:
|
||||
- *On Android*, navigate to `Android/data/sh.ppy.osulazer/files/logs` using a file browser app.
|
||||
- *On iOS*, connect your device to a PC and copy the `logs` directory from the app's document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
placeholder: Drag and drop the log files into this box.
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,12 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Help
|
||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||
about: osu! not working or performing as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: osu!stable issues
|
||||
url: https://github.com/ppy/osu-stable-issues
|
||||
about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: nuget
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "17:00"
|
||||
open-pull-requests-limit: 0 # disabled until https://github.com/dependabot/dependabot-core/issues/369 is resolved.
|
||||
ignore:
|
||||
- dependency-name: Microsoft.EntityFrameworkCore.Design
|
||||
versions:
|
||||
- "> 2.2.6"
|
||||
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
|
||||
versions:
|
||||
- "> 2.2.6"
|
||||
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite.Core
|
||||
versions:
|
||||
- "> 2.2.6"
|
||||
- dependency-name: Microsoft.Extensions.DependencyInjection
|
||||
versions:
|
||||
- ">= 5.a, < 6"
|
||||
- dependency-name: NUnit3TestAdapter
|
||||
versions:
|
||||
- ">= 3.16.a, < 3.17"
|
||||
- dependency-name: Microsoft.NET.Test.Sdk
|
||||
versions:
|
||||
- 16.9.1
|
||||
- dependency-name: Microsoft.Extensions.DependencyInjection
|
||||
versions:
|
||||
- 3.1.11
|
||||
- 3.1.12
|
||||
- dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson
|
||||
versions:
|
||||
- 3.1.11
|
||||
- dependency-name: Microsoft.NETCore.Targets
|
||||
versions:
|
||||
- 5.0.0
|
||||
- dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack
|
||||
versions:
|
||||
- 5.0.2
|
||||
- dependency-name: NUnit
|
||||
versions:
|
||||
- 3.13.1
|
||||
- dependency-name: Microsoft.AspNetCore.SignalR.Client
|
||||
versions:
|
||||
- 3.1.11
|
||||
@@ -1,228 +0,0 @@
|
||||
name: "🔒diffcalc (do not use)"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
id:
|
||||
type: string
|
||||
head-sha:
|
||||
type: string
|
||||
pr-url:
|
||||
type: string
|
||||
pr-text:
|
||||
type: string
|
||||
dispatch-inputs:
|
||||
type: string
|
||||
outputs:
|
||||
target:
|
||||
description: The comparison target.
|
||||
value: ${{ jobs.generator.outputs.target }}
|
||||
sheet:
|
||||
description: The comparison spreadsheet.
|
||||
value: ${{ jobs.generator.outputs.sheet }}
|
||||
secrets:
|
||||
DIFFCALC_GOOGLE_CREDENTIALS:
|
||||
required: true
|
||||
|
||||
env:
|
||||
GENERATOR_DIR: ${{ github.workspace }}/${{ inputs.id }}
|
||||
GENERATOR_ENV: ${{ github.workspace }}/${{ inputs.id }}/.env
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -euo pipefail {0}
|
||||
|
||||
jobs:
|
||||
generator:
|
||||
name: Run
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 1440
|
||||
|
||||
outputs:
|
||||
target: ${{ steps.run.outputs.target }}
|
||||
sheet: ${{ steps.run.outputs.sheet }}
|
||||
|
||||
steps:
|
||||
- name: Checkout diffcalc-sheet-generator
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: ${{ inputs.id }}
|
||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||
|
||||
- name: Add base environment
|
||||
env:
|
||||
GOOGLE_CREDS_FILE: ${{ github.workspace }}/${{ inputs.id }}/google-credentials.json
|
||||
VARS_JSON: ${{ (vars != null && toJSON(vars)) || '' }}
|
||||
run: |
|
||||
# Required by diffcalc-sheet-generator
|
||||
cp '${{ env.GENERATOR_DIR }}/.env.sample' "${{ env.GENERATOR_ENV }}"
|
||||
|
||||
# Add Google credentials
|
||||
echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ env.GOOGLE_CREDS_FILE }}"
|
||||
|
||||
# Add repository variables
|
||||
echo "${VARS_JSON}" | jq -c '. | to_entries | .[]' | while read -r line; do
|
||||
opt=$(jq -r '.key' <<< ${line})
|
||||
val=$(jq -r '.value' <<< ${line})
|
||||
|
||||
if [[ "${opt}" =~ ^DIFFCALC_ ]]; then
|
||||
optNoPrefix=$(echo "${opt}" | cut -d '_' -f2-)
|
||||
sed -i "s;^${optNoPrefix}=.*$;${optNoPrefix}=${val};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Add HEAD environment
|
||||
run: |
|
||||
sed -i "s;^OSU_A=.*$;OSU_A=${{ inputs.head-sha }};" "${{ env.GENERATOR_ENV }}"
|
||||
|
||||
- name: Add pull-request environment
|
||||
if: ${{ inputs.pr-url != '' }}
|
||||
run: |
|
||||
sed -i "s;^OSU_B=.*$;OSU_B=${{ inputs.pr-url }};" "${{ env.GENERATOR_ENV }}"
|
||||
|
||||
- name: Add comment environment
|
||||
if: ${{ inputs.pr-text != '' }}
|
||||
env:
|
||||
PR_TEXT: ${{ inputs.pr-text }}
|
||||
run: |
|
||||
# Add comment environment
|
||||
echo "${PR_TEXT}" | sed -r 's/\r$//' | { grep -E '^\w+=' || true; } | while read -r line; do
|
||||
opt=$(echo "${line}" | cut -d '=' -f1)
|
||||
sed -i "s;^${opt}=.*$;${line};" "${{ env.GENERATOR_ENV }}"
|
||||
done
|
||||
|
||||
- name: Add dispatch environment
|
||||
if: ${{ inputs.dispatch-inputs != '' }}
|
||||
env:
|
||||
DISPATCH_INPUTS_JSON: ${{ inputs.dispatch-inputs }}
|
||||
run: |
|
||||
function get_input() {
|
||||
echo "${DISPATCH_INPUTS_JSON}" | jq -r ".\"$1\""
|
||||
}
|
||||
|
||||
osu_a=$(get_input 'osu-a')
|
||||
osu_b=$(get_input 'osu-b')
|
||||
ruleset=$(get_input 'ruleset')
|
||||
generators=$(get_input 'generators')
|
||||
difficulty_calculator_a=$(get_input 'difficulty-calculator-a')
|
||||
difficulty_calculator_b=$(get_input 'difficulty-calculator-b')
|
||||
score_processor_a=$(get_input 'score-processor-a')
|
||||
score_processor_b=$(get_input 'score-processor-b')
|
||||
converts=$(get_input 'converts')
|
||||
ranked_only=$(get_input 'ranked-only')
|
||||
|
||||
sed -i "s;^OSU_B=.*$;OSU_B=${osu_b};" "${{ env.GENERATOR_ENV }}"
|
||||
sed -i "s/^RULESET=.*$/RULESET=${ruleset}/" "${{ env.GENERATOR_ENV }}"
|
||||
sed -i "s/^GENERATORS=.*$/GENERATORS=${generators}/" "${{ env.GENERATOR_ENV }}"
|
||||
|
||||
if [[ "${osu_a}" != 'latest' ]]; then
|
||||
sed -i "s;^OSU_A=.*$;OSU_A=${osu_a};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${difficulty_calculator_a}" != 'latest' ]]; then
|
||||
sed -i "s;^DIFFICULTY_CALCULATOR_A=.*$;DIFFICULTY_CALCULATOR_A=${difficulty_calculator_a};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${difficulty_calculator_b}" != 'latest' ]]; then
|
||||
sed -i "s;^DIFFICULTY_CALCULATOR_B=.*$;DIFFICULTY_CALCULATOR_B=${difficulty_calculator_b};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${score_processor_a}" != 'latest' ]]; then
|
||||
sed -i "s;^SCORE_PROCESSOR_A=.*$;SCORE_PROCESSOR_A=${score_processor_a};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${score_processor_b}" != 'latest' ]]; then
|
||||
sed -i "s;^SCORE_PROCESSOR_B=.*$;SCORE_PROCESSOR_B=${score_processor_b};" "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${converts}" == 'true' ]]; then
|
||||
sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=0/' "${{ env.GENERATOR_ENV }}"
|
||||
else
|
||||
sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=1/' "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
if [[ "${ranked_only}" == 'true' ]]; then
|
||||
sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=1/' "${{ env.GENERATOR_ENV }}"
|
||||
else
|
||||
sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ env.GENERATOR_ENV }}"
|
||||
fi
|
||||
|
||||
- name: Query latest scores
|
||||
id: query-scores
|
||||
run: |
|
||||
ruleset=$(cat ${{ env.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-)
|
||||
performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g')
|
||||
|
||||
echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}"
|
||||
echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}"
|
||||
echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Restore score cache
|
||||
id: restore-score-cache
|
||||
uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2
|
||||
with:
|
||||
path: ${{ steps.query-scores.outputs.DATA_PKG }}
|
||||
key: ${{ steps.query-scores.outputs.DATA_NAME }}
|
||||
|
||||
- name: Download scores
|
||||
if: steps.restore-score-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget -q -O "${{ steps.query-scores.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-scores.outputs.DATA_PKG }}"
|
||||
|
||||
- name: Extract scores
|
||||
run: |
|
||||
tar -I lbzip2 -xf "${{ steps.query-scores.outputs.DATA_PKG }}"
|
||||
rm -r "${{ steps.query-scores.outputs.TARGET_DIR }}"
|
||||
mv "${{ steps.query-scores.outputs.DATA_NAME }}" "${{ steps.query-scores.outputs.TARGET_DIR }}"
|
||||
|
||||
- name: Query latest beatmaps
|
||||
id: query-beatmaps
|
||||
run: |
|
||||
beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g')
|
||||
|
||||
echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}"
|
||||
echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}"
|
||||
echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Restore beatmap cache
|
||||
id: restore-beatmap-cache
|
||||
uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2
|
||||
with:
|
||||
path: ${{ steps.query-beatmaps.outputs.DATA_PKG }}
|
||||
key: ${{ steps.query-beatmaps.outputs.DATA_NAME }}
|
||||
|
||||
- name: Download beatmap
|
||||
if: steps.restore-beatmap-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
wget -q -O "${{ steps.query-beatmaps.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-beatmaps.outputs.DATA_PKG }}"
|
||||
|
||||
- name: Extract beatmap
|
||||
run: |
|
||||
tar -I lbzip2 -xf "${{ steps.query-beatmaps.outputs.DATA_PKG }}"
|
||||
rm -r "${{ steps.query-beatmaps.outputs.TARGET_DIR }}"
|
||||
mv "${{ steps.query-beatmaps.outputs.DATA_NAME }}" "${{ steps.query-beatmaps.outputs.TARGET_DIR }}"
|
||||
|
||||
- name: Run
|
||||
id: run
|
||||
run: |
|
||||
# Add the GitHub token. This needs to be done here because it's unique per-job.
|
||||
sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ env.GENERATOR_ENV }}"
|
||||
|
||||
cd "${{ env.GENERATOR_DIR }}"
|
||||
|
||||
docker compose up --build --detach
|
||||
docker compose logs --follow &
|
||||
docker compose wait generator
|
||||
|
||||
link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/')
|
||||
target=$(cat "${{ env.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-)
|
||||
|
||||
echo "target=${target}" >> "${GITHUB_OUTPUT}"
|
||||
echo "sheet=${link}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Shutdown
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
cd "${{ env.GENERATOR_DIR }}"
|
||||
docker compose down --volumes
|
||||
rm -rf "${{ env.GENERATOR_DIR }}"
|
||||
@@ -1,187 +0,0 @@
|
||||
on: [push, pull_request]
|
||||
name: Continuous Integration
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
security-events: write # for reporting InspectCode issues
|
||||
|
||||
jobs:
|
||||
inspect-code:
|
||||
name: Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Restore Tools
|
||||
run: dotnet tool restore
|
||||
|
||||
- name: Restore Packages
|
||||
run: dotnet restore osu.Desktop.slnf
|
||||
|
||||
- name: Restore inspectcode cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ github.workspace }}/inspectcode
|
||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
||||
|
||||
- name: Dotnet code style
|
||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true
|
||||
|
||||
- name: CodeFileSanity
|
||||
run: |
|
||||
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
||||
# FIXME: Suppress warnings from templates project
|
||||
exit_code=0
|
||||
while read -r line; do
|
||||
if [[ ! -z "$line" ]]; then
|
||||
echo "::error::$line"
|
||||
exit_code=1
|
||||
fi
|
||||
done <<< $(dotnet codefilesanity)
|
||||
exit $exit_code
|
||||
|
||||
- name: InspectCode
|
||||
uses: JetBrains/ReSharper-InspectCode@v0.12
|
||||
with:
|
||||
# this is WTF tier but if you don't specify *both* of these the defaults assume `build: true`
|
||||
build: false
|
||||
no-build: true
|
||||
solution: ./osu.Desktop.slnf
|
||||
caches-home: inspectcode
|
||||
verbosity: WARN
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{matrix.os.fullname}}
|
||||
env:
|
||||
OSU_EXECUTION_MODE: ${{matrix.threadingMode}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- { prettyname: Windows, fullname: windows-latest }
|
||||
# macOS runner performance has gotten unbearably slow so let's turn them off temporarily.
|
||||
# - { prettyname: macOS, fullname: macos-latest }
|
||||
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Compile
|
||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||
|
||||
- name: Test
|
||||
run: >
|
||||
dotnet test
|
||||
osu.Game.Tests/bin/Debug/**/osu.Game.Tests.dll
|
||||
osu.Game.Rulesets.Osu.Tests/bin/Debug/**/osu.Game.Rulesets.Osu.Tests.dll
|
||||
osu.Game.Rulesets.Taiko.Tests/bin/Debug/**/osu.Game.Rulesets.Taiko.Tests.dll
|
||||
osu.Game.Rulesets.Catch.Tests/bin/Debug/**/osu.Game.Rulesets.Catch.Tests.dll
|
||||
osu.Game.Rulesets.Mania.Tests/bin/Debug/**/osu.Game.Rulesets.Mania.Tests.dll
|
||||
osu.Game.Tournament.Tests/bin/Debug/**/osu.Game.Tournament.Tests.dll
|
||||
Templates/**/*.Tests/bin/Debug/**/*.Tests.dll
|
||||
--logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
|
||||
--
|
||||
NUnit.ConsoleOut=0
|
||||
|
||||
# Attempt to upload results even if test fails.
|
||||
# https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#cancelled
|
||||
- name: Upload Test Results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||
path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx
|
||||
|
||||
test-results:
|
||||
name: Test results
|
||||
runs-on: ubuntu-latest
|
||||
# we want to wait for the `test` job to complete, but run regardless of whether it succeeds or fails
|
||||
# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-not-requiring-successful-dependent-jobs
|
||||
if: ${{ !cancelled() }}
|
||||
needs: test
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download results
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: osu-test-results-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Add test results summary to workflow run
|
||||
uses: dorny/test-reporter@v3.0.0
|
||||
with:
|
||||
name: Results
|
||||
path: "*.trx"
|
||||
reporter: dotnet-trx
|
||||
list-suites: 'failed'
|
||||
list-tests: 'failed'
|
||||
use-actions-summary: 'true'
|
||||
|
||||
build-only-android:
|
||||
name: Build only (Android)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: microsoft
|
||||
java-version: 11
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Install .NET workloads
|
||||
run: dotnet workload install android
|
||||
|
||||
- name: Compile
|
||||
run: dotnet build -c Debug osu.Android.slnf
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Install .NET Workloads
|
||||
run: dotnet workload install ios
|
||||
|
||||
# https://github.com/dotnet/macios/issues/19157
|
||||
# https://github.com/actions/runner-images/issues/12758
|
||||
- name: Use Xcode 16.4
|
||||
run: sudo xcode-select -switch /Applications/Xcode_16.4.app
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS.slnf
|
||||
@@ -1,88 +0,0 @@
|
||||
name: Pack and nuget
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
- '!*-*'
|
||||
|
||||
jobs:
|
||||
notify_pending_production_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Submit pending deployment notification
|
||||
run: |
|
||||
export TITLE="Pending osu Production Deployment: $GITHUB_REF_NAME"
|
||||
export URL="https://github.com/ppy/osu/actions/runs/$GITHUB_RUN_ID"
|
||||
export DESCRIPTION="Awaiting approval for building NuGet packages for tag $GITHUB_REF_NAME:
|
||||
[View Workflow Run]($URL)"
|
||||
export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"
|
||||
|
||||
BODY="$(jq --null-input '{
|
||||
"embeds": [
|
||||
{
|
||||
"title": env.TITLE,
|
||||
"color": 15098112,
|
||||
"description": env.DESCRIPTION,
|
||||
"url": env.URL,
|
||||
"author": {
|
||||
"name": env.GITHUB_ACTOR,
|
||||
"icon_url": env.ACTOR_ICON
|
||||
}
|
||||
}
|
||||
]
|
||||
}')"
|
||||
|
||||
curl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY" \
|
||||
"${{ secrets.DISCORD_INFRA_WEBHOOK_URL }}"
|
||||
|
||||
pack:
|
||||
name: Pack
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set artifacts directory
|
||||
id: artifactsPath
|
||||
run: echo "::set-output name=nuget_artifacts::${{github.workspace}}/artifacts"
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Pack
|
||||
run: |
|
||||
# Replace project references in templates with package reference, because they're included as source files.
|
||||
dotnet remove Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj reference osu.Game/osu.Game.csproj
|
||||
|
||||
dotnet add Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
|
||||
# Pack
|
||||
dotnet pack -c Release osu.Game /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Osu /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Taiko /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Catch /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Mania /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release Templates /p:Version=${{ github.ref_name }} -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: osu
|
||||
path: |
|
||||
${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg
|
||||
${{steps.artifactsPath.outputs.nuget_artifacts}}/*.snupkg
|
||||
|
||||
- name: Publish packages to nuget.org
|
||||
run: dotnet nuget push ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
|
||||
@@ -1,196 +0,0 @@
|
||||
# ## Description
|
||||
#
|
||||
# Uses [diffcalc-sheet-generator](https://github.com/smoogipoo/diffcalc-sheet-generator) to run two builds of osu and generate an SR/PP/Score comparison spreadsheet.
|
||||
#
|
||||
# ## Requirements
|
||||
#
|
||||
# Self-hosted runner with installed:
|
||||
# - `docker >= 20.10.16`
|
||||
# - `docker-compose >= 2.5.1`
|
||||
# - `lbzip2`
|
||||
# - `jq`
|
||||
#
|
||||
# ## Usage
|
||||
#
|
||||
# The workflow can be run in two ways:
|
||||
# 1. Via workflow dispatch.
|
||||
# 2. By an owner of the repository posting a pull request or issue comment containing `!diffcalc`.
|
||||
# For pull requests, the workflow will assume the pull request as the target to compare against (i.e. the `OSU_B` variable).
|
||||
# Any lines in the comment of the form `KEY=VALUE` are treated as variables for the generator.
|
||||
#
|
||||
# ## Google Service Account
|
||||
#
|
||||
# Spreadsheets are uploaded to a Google Service Account, and exposed with read-only permissions to the wider audience.
|
||||
#
|
||||
# 1. Create a project at https://console.cloud.google.com
|
||||
# 2. Enable the `Google Sheets` and `Google Drive` APIs.
|
||||
# 3. Create a Service Account
|
||||
# 4. Generate a key in the JSON format.
|
||||
# 5. Encode the key as base64 and store as an **actions secret** with name **`DIFFCALC_GOOGLE_CREDENTIALS`**
|
||||
#
|
||||
# ## Environment variables
|
||||
#
|
||||
# The default environment may be configured via **actions variables**.
|
||||
#
|
||||
# Refer to [the sample environment](https://github.com/smoogipoo/diffcalc-sheet-generator/blob/master/.env.sample), and prefix each variable with `DIFFCALC_` (e.g. `DIFFCALC_THREADS`, `DIFFCALC_INNODB_BUFFER_SIZE`, etc...).
|
||||
|
||||
name: Run difficulty calculation comparison
|
||||
|
||||
run-name: "${{ github.event_name == 'workflow_dispatch' && format('Manual run: {0}', inputs.osu-b) || 'Automatic comment trigger' }}"
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [ created ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
osu-b:
|
||||
description: "The target build of ppy/osu"
|
||||
type: string
|
||||
required: true
|
||||
ruleset:
|
||||
description: "The ruleset to process"
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- osu
|
||||
- taiko
|
||||
- catch
|
||||
- mania
|
||||
converts:
|
||||
description: "Include converted beatmaps"
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
ranked-only:
|
||||
description: "Only ranked beatmaps"
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
generators:
|
||||
description: "Comma-separated list of generators (available: [sr, pp, score])"
|
||||
type: string
|
||||
required: false
|
||||
default: 'pp,sr'
|
||||
osu-a:
|
||||
description: "The source build of ppy/osu"
|
||||
type: string
|
||||
required: false
|
||||
default: 'latest'
|
||||
difficulty-calculator-a:
|
||||
description: "The source build of ppy/osu-difficulty-calculator"
|
||||
type: string
|
||||
required: false
|
||||
default: 'latest'
|
||||
difficulty-calculator-b:
|
||||
description: "The target build of ppy/osu-difficulty-calculator"
|
||||
type: string
|
||||
required: false
|
||||
default: 'latest'
|
||||
score-processor-a:
|
||||
description: "The source build of ppy/osu-queue-score-statistics"
|
||||
type: string
|
||||
required: false
|
||||
default: 'latest'
|
||||
score-processor-b:
|
||||
description: "The target build of ppy/osu-queue-score-statistics"
|
||||
type: string
|
||||
required: false
|
||||
default: 'latest'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -euo pipefail {0}
|
||||
|
||||
jobs:
|
||||
check-permissions:
|
||||
name: Check permissions
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }}
|
||||
steps:
|
||||
- name: Check permissions
|
||||
run: |
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte tsunyoku stanriders)
|
||||
for i in "${ALLOWED_USERS[@]}"; do
|
||||
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
exit 1
|
||||
|
||||
run-diffcalc:
|
||||
name: Run spreadsheet generator
|
||||
needs: check-permissions
|
||||
uses: ./.github/workflows/_diffcalc_processor.yml
|
||||
with:
|
||||
# Can't reference env... Why GitHub, WHY?
|
||||
id: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
head-sha: https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha || github.sha }}
|
||||
pr-url: ${{ github.event.issue.pull_request.html_url || '' }}
|
||||
pr-text: ${{ github.event.comment.body || '' }}
|
||||
dispatch-inputs: ${{ (github.event.type == 'workflow_dispatch' && toJSON(inputs)) || '' }}
|
||||
secrets:
|
||||
DIFFCALC_GOOGLE_CREDENTIALS: ${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}
|
||||
|
||||
create-comment:
|
||||
name: Create PR comment
|
||||
needs: check-permissions
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||
with:
|
||||
comment_tag: ${{ env.EXECUTION_ID }}
|
||||
message: |
|
||||
Difficulty calculation queued -- please wait! (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
||||
*This comment will update on completion*
|
||||
|
||||
output-cli:
|
||||
name: Info
|
||||
needs: run-diffcalc
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Output info
|
||||
run: |
|
||||
echo "Target: ${{ needs.run-diffcalc.outputs.target }}"
|
||||
echo "Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }}"
|
||||
|
||||
update-comment:
|
||||
name: Update PR comment
|
||||
needs: [ create-comment, run-diffcalc ]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() && needs.create-comment.result == 'success' }}
|
||||
steps:
|
||||
- name: Update comment on success
|
||||
if: ${{ needs.run-diffcalc.result == 'success' }}
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||
with:
|
||||
comment_tag: ${{ env.EXECUTION_ID }}
|
||||
mode: recreate
|
||||
message: |
|
||||
Target: ${{ needs.run-diffcalc.outputs.target }}
|
||||
Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }}
|
||||
|
||||
- name: Update comment on failure
|
||||
if: ${{ needs.run-diffcalc.result == 'failure' }}
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||
with:
|
||||
comment_tag: ${{ env.EXECUTION_ID }}
|
||||
mode: recreate
|
||||
message: |
|
||||
Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: Update comment on cancellation
|
||||
if: ${{ needs.run-diffcalc.result == 'cancelled' }}
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||
with:
|
||||
comment_tag: ${{ env.EXECUTION_ID }}
|
||||
mode: delete
|
||||
message: '.' # Appears to be required by this action for non-error status code.
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Add Release to Sentry
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
sentry_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create Sentry release
|
||||
uses: getsentry/action-release@v3
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ppy
|
||||
SENTRY_PROJECT: osu
|
||||
SENTRY_URL: https://sentry.ppy.sh/
|
||||
with:
|
||||
environment: production
|
||||
version: osu@${{ github.ref_name }}
|
||||
@@ -1,57 +0,0 @@
|
||||
name: Update osu-web mod definitions
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
update-mod-definitions:
|
||||
name: Update osu-web mod definitions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Checkout ppy/osu
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: osu
|
||||
|
||||
- name: Checkout ppy/osu-tools
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-tools
|
||||
path: osu-tools
|
||||
|
||||
- name: Checkout ppy/osu-web
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-web
|
||||
path: osu-web
|
||||
|
||||
- name: Setup local game checkout for tools
|
||||
run: ./UseLocalOsu.sh
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Build tools
|
||||
run: dotnet build PerformanceCalculator --nologo --verbosity quiet
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Regenerate mod definitions
|
||||
run: dotnet run --project PerformanceCalculator --no-build -- mods > ../osu-web/database/mods.json
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: Update mod definitions
|
||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||
branch: update-mod-definitions
|
||||
commit-message: Update mod definitions
|
||||
path: osu-web
|
||||
token: ${{ secrets.OSU_WEB_PULL_REQUEST_PAT }}
|
||||
+259
-346
@@ -1,346 +1,259 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
Resource.designer.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake #
|
||||
/tools/**
|
||||
/build/tools/**
|
||||
/build/temp/**
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/*/.idea/projectSettingsUpdater.xml
|
||||
.idea/*/.idea/encodings.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
|
||||
# inspectcode
|
||||
inspectcodereport.xml
|
||||
inspectcode
|
||||
|
||||
# BenchmarkDotNet
|
||||
/BenchmarkDotNet.Artifacts
|
||||
|
||||
*.GeneratedMSBuildEditorConfig.editorconfig
|
||||
|
||||
# Fody (pulled in by Realm) - schema file
|
||||
FodyWeavers.xsd
|
||||
|
||||
.idea/.idea.osu.Desktop/.idea/misc.xml
|
||||
.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
bin/[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
Staging/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[submodule "osu-framework"]
|
||||
path = osu-framework
|
||||
url = https://github.com/ppy/osu-framework
|
||||
[submodule "osu-resources"]
|
||||
path = osu-resources
|
||||
url = https://github.com/ppy/osu-resources
|
||||
Generated
Generated
-1
@@ -1 +0,0 @@
|
||||
osu.Android
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="osu.Android">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
osu.Desktop
|
||||
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net8.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Catch.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Mania.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Osu.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Taiko.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
-16
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="SubjectBodySeparation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="GitSharedSettings">
|
||||
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
|
||||
<list />
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
osu.iOS
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,7 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Dual client test" type="CompoundRunConfigurationType">
|
||||
<toRun name="osu!" type="DotNetProject" />
|
||||
<toRun name="osu! (Second Client)" type="DotNetProject" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu! (Second Client)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--debug-client-id=1" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,2 @@
|
||||
language: csharp
|
||||
solution: osu.sln
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"ms-dotnettools.csdevkit"
|
||||
]
|
||||
}
|
||||
Vendored
+44
-109
@@ -1,131 +1,66 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "osu! (Debug)",
|
||||
"type": "coreclr",
|
||||
"configurations": [{
|
||||
"name": "osu! VisualTests (Debug)",
|
||||
"windows": {
|
||||
"type": "clr"
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
"--tests"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
"runtimeExecutable": null,
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Debug, Second Client)",
|
||||
"type": "coreclr",
|
||||
"name": "osu! VisualTests (Release)",
|
||||
"windows": {
|
||||
"type": "clr"
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"--debug-client-id=1"
|
||||
"--tests"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
"runtimeExecutable": null,
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Debug)",
|
||||
"windows": {
|
||||
"type": "clr"
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
"runtimeExecutable": null,
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Release)",
|
||||
"type": "coreclr",
|
||||
"windows": {
|
||||
"type": "clr"
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
],
|
||||
"program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Tests, Debug)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Tests, Release)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Tournament (Debug)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Tournament (Release)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Tournament (Tests, Debug)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tournament tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Tournament (Tests, Release)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tournament tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Benchmark",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"args": [
|
||||
"--filter",
|
||||
"*"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build benchmarks",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
"runtimeExecutable": null,
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
}
|
||||
Vendored
+43
-81
@@ -2,107 +2,69 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build osu! (Debug)",
|
||||
"tasks": [{
|
||||
"label": "Build (Debug)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"command": "msbuild",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Desktop",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/p:DebugType=portable",
|
||||
"/m",
|
||||
"/v:m"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build (Release)",
|
||||
"type": "shell",
|
||||
"command": "msbuild",
|
||||
"args": [
|
||||
"/p:Configuration=Release",
|
||||
"/p:DebugType=portable",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/v:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build osu! (Release)",
|
||||
"label": "Clean (Debug)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"command": "msbuild",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Desktop",
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
"/p:DebugType=portable",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/t:Clean",
|
||||
"/v:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tests (Debug)",
|
||||
"label": "Clean (Release)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"command": "msbuild",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Game.Tests",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/p:DebugType=portable",
|
||||
"/m",
|
||||
"/t:Clean",
|
||||
"/v:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tests (Release)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Game.Tests",
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
"label": "Clean All",
|
||||
"dependsOn": [
|
||||
"Clean (Debug)",
|
||||
"Clean (Release)"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tournament tests (Debug)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Game.Tournament.Tests",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tournament tests (Release)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Game.Tournament.Tests",
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build benchmarks",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"osu.Game.Benchmarks",
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Linux
|
||||
### 1. Requirements:
|
||||
Mono >= 5.4.0 (>= 5.8.0 recommended)
|
||||
Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release.
|
||||
NuGet >= 4.4.0
|
||||
msbuild
|
||||
git
|
||||
|
||||
### 2. Cloning project
|
||||
Clone the entire repository with submodules using
|
||||
```
|
||||
git clone https://github.com/ppy/osu --recursive
|
||||
```
|
||||
Then restore NuGet packages from the repository
|
||||
```
|
||||
nuget restore
|
||||
```
|
||||
### 3. Compiling
|
||||
Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`.
|
||||
### 4. Optimizing
|
||||
If you want additional performance you can change build type to Release with
|
||||
```
|
||||
msbuild -p:Configuration=Release
|
||||
```
|
||||
Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running
|
||||
```
|
||||
mono --aot ./osu\!.exe
|
||||
```
|
||||
### 5. Troubleshooting
|
||||
You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run
|
||||
```
|
||||
nuget
|
||||
sudo nuget update -self
|
||||
```
|
||||
**Warning** NuGet creates few config files when it's run for the first time.
|
||||
Do not run NuGet as root on the first run or you might run into very peculiar issues.
|
||||
@@ -1,94 +0,0 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
Thank you for showing interest in the development of osu!. We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
|
||||
|
||||
## Foreword on AI usage
|
||||
|
||||
Our team believes in **human contributions**. Any contribution – be it an issue report or a pull request – which is created by, documented by, or aided by AI/LLM usage will typically be **closed and locked without further discussion**.
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Reporting bugs](#reporting-bugs)
|
||||
2. [Providing general feedback](#providing-general-feedback)
|
||||
3. [Issue or discussion?](#issue-or-discussion)
|
||||
4. [Submitting pull requests](#submitting-pull-requests)
|
||||
5. [Resources](#resources)
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
A **bug** is a situation in which there is something clearly *and objectively* wrong with the game. Examples of applicable bug reports are:
|
||||
|
||||
- The game crashes to desktop when I start a beatmap
|
||||
- Friends appear twice in the friend listing
|
||||
- The game slows down a lot when I play this specific map
|
||||
- A piece of text is overlapping another piece of text on the screen
|
||||
|
||||
To track bug reports, we primarily use GitHub **issues**. When opening an issue, please keep in mind the following:
|
||||
|
||||
- Before opening the issue, please search for any similar existing issues using the text search bar and the issue labels. This includes both open and closed issues (we may have already fixed something, but the fix hasn't yet been released).
|
||||
- When opening the issue, please fill out as much of the issue template as you can. In particular, please make sure to include logs and screenshots as much as possible. The instructions on how to find the log files are included in the issue template.
|
||||
- We may ask you for follow-up information to reproduce or debug the problem. Please look out for this and provide follow-up info if we request it.
|
||||
|
||||
If we cannot reproduce the issue, it is deemed low priority, or it is deemed to be specific to your setup in some way, the issue may be downgraded to a discussion. This will be done by a maintainer for you.
|
||||
|
||||
## Providing general feedback
|
||||
|
||||
If you wish to:
|
||||
|
||||
- provide *subjective* feedback on the game (about how the UI looks, about how the default skin works, about game mechanics, about how the PP and scoring systems work, etc.),
|
||||
- suggest a new feature to be added to the game,
|
||||
- report a non-specific problem with the game that you think may be connected to your hardware or operating system specifically,
|
||||
|
||||
then it is generally best to start with a **discussion** first. Discussions are a good avenue to group subjective feedback on a single topic, or gauge interest in a particular feature request.
|
||||
|
||||
When opening a discussion, please keep in mind the following:
|
||||
|
||||
- Use the search function to see if your idea has been proposed before, or if there is already a thread about a particular issue you wish to raise.
|
||||
- If proposing a feature, please try to explain the feature in as much detail as possible.
|
||||
- If you're reporting a non-specific problem, please provide applicable logs, screenshots, or video that illustrate the issue.
|
||||
|
||||
If a discussion gathers enough traction, then it may be converted into an issue. This will be done by a maintainer for you.
|
||||
|
||||
## Issue or discussion?
|
||||
|
||||
We realise that the line between an issue and a discussion may be fuzzy, so while we ask you to use your best judgement based on the description above, please don't think about it too hard either. Feedback in a slightly wrong place is better than no feedback at all.
|
||||
|
||||
When in doubt, it's probably best to start with a discussion first. We will escalate to issues as needed.
|
||||
|
||||
## Submitting pull requests
|
||||
|
||||
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
|
||||
|
||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||
|
||||
If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library).
|
||||
|
||||
Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes:
|
||||
|
||||
- Make sure you're comfortable with the principles of object-oriented programming, the syntax of C\# and your development environment.
|
||||
- Make sure you are familiar with [git](https://git-scm.com/) and [the pull request workflow](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests).
|
||||
- Please do not make code changes via the GitHub web interface.
|
||||
- Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing).
|
||||
- Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so.
|
||||
- **Do not run the game in release configuration at any point during your testing** (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions.
|
||||
|
||||
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
|
||||
|
||||
- Please submit the pull request from a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary.
|
||||
- Please pick the following target branch for your pull request:
|
||||
- `pp-dev`, if the change impacts star rating or performance points calculations for any of the rulesets,
|
||||
- `master`, otherwise.
|
||||
- Please avoid pushing untested or incomplete code.
|
||||
- Please do not force-push or rebase unless we ask you to.
|
||||
- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change is ready for merge.
|
||||
|
||||
We are highly committed to quality when it comes to the osu! project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience.
|
||||
|
||||
If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue, discussion, or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Development roadmap](https://github.com/orgs/ppy/projects/7/views/6): What the core team is currently working on
|
||||
- [`ppy/osu-framework` wiki](https://github.com/ppy/osu-framework/wiki): Contains introductory information about osu!framework, the bespoke 2D game framework we use for the game
|
||||
- [`ppy/osu` wiki](https://github.com/ppy/osu/wiki): Contains articles about various technical aspects of the game
|
||||
- [Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library): Contains finished and draft designs for osu!
|
||||
@@ -1,27 +0,0 @@
|
||||
M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
||||
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
||||
T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> instead.
|
||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
||||
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
||||
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
||||
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
||||
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
||||
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
||||
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
|
||||
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.
|
||||
M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead.
|
||||
M:osuTK.MathHelper.Clamp(System.Int32,System.Int32,System.Int32)~System.Int32;Use Math.Clamp() instead.
|
||||
M:osuTK.MathHelper.Clamp(System.Single,System.Single,System.Single)~System.Single;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
|
||||
M:osuTK.MathHelper.Clamp(System.Double,System.Double,System.Double)~System.Double;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
|
||||
M:TagLib.File.Create(System.String);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(TagLib.File.IFileAbstraction);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(System.String,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
M:TagLib.File.Create(TagLib.File.IFileAbstraction,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead.
|
||||
@@ -1,112 +0,0 @@
|
||||
# .NET Code Style
|
||||
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||
is_global = true
|
||||
|
||||
# IDE0001: Simplify names
|
||||
dotnet_diagnostic.IDE0001.severity = warning
|
||||
|
||||
# IDE0002: Simplify member access
|
||||
dotnet_diagnostic.IDE0002.severity = warning
|
||||
|
||||
# IDE0003: Remove qualification
|
||||
dotnet_diagnostic.IDE0003.severity = warning
|
||||
|
||||
# IDE0004: Remove unnecessary cast
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# IDE0005: Remove unnecessary imports
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# IDE0034: Simplify default literal
|
||||
dotnet_diagnostic.IDE0034.severity = warning
|
||||
|
||||
# IDE0036: Sort modifiers
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0040: Add accessibility modifier
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0049: Use keyword for type name
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
|
||||
# IDE0051: Private method is unused
|
||||
dotnet_diagnostic.IDE0051.severity = silent
|
||||
|
||||
# IDE0052: Private member is unused
|
||||
dotnet_diagnostic.IDE0052.severity = silent
|
||||
|
||||
# IDE0073: File header
|
||||
dotnet_diagnostic.IDE0073.severity = warning
|
||||
|
||||
# IDE0130: Namespace mismatch with folder
|
||||
dotnet_diagnostic.IDE0130.severity = warning
|
||||
|
||||
# IDE1006: Naming style
|
||||
dotnet_diagnostic.IDE1006.severity = warning
|
||||
|
||||
# CA1305: Specify IFormatProvider
|
||||
# Too many noisy warnings for parsing/formatting numbers
|
||||
dotnet_diagnostic.CA1305.severity = none
|
||||
|
||||
# messagepack complains about "osu" not being title cased due to reserved words
|
||||
dotnet_diagnostic.CS8981.severity = none
|
||||
|
||||
# CA1507: Use nameof to express symbol names
|
||||
# Flags serialization name attributes
|
||||
dotnet_diagnostic.CA1507.severity = suggestion
|
||||
|
||||
# CA1806: Do not ignore method results
|
||||
# The usages for numeric parsing are explicitly optional
|
||||
dotnet_diagnostic.CA1806.severity = suggestion
|
||||
|
||||
# CA1822: Mark members as static
|
||||
# Potential false positive around reflection/too much noise
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
# CA1826: Do not use Enumerable method on indexable collections
|
||||
dotnet_diagnostic.CA1826.severity = suggestion
|
||||
|
||||
# CA1859: Use concrete types when possible for improved performance
|
||||
# Involves design considerations
|
||||
dotnet_diagnostic.CA1859.severity = suggestion
|
||||
|
||||
# CA1860: Avoid using 'Enumerable.Any()' extension method
|
||||
dotnet_diagnostic.CA1860.severity = suggestion
|
||||
|
||||
# CA1861: Avoid constant arrays as arguments
|
||||
# Outdated with collection expressions
|
||||
dotnet_diagnostic.CA1861.severity = suggestion
|
||||
|
||||
# CA2007: Consider calling ConfigureAwait on the awaited task
|
||||
dotnet_diagnostic.CA2007.severity = warning
|
||||
|
||||
# CA2016: Forward the 'CancellationToken' parameter to methods
|
||||
# Some overloads are having special handling for debugger
|
||||
dotnet_diagnostic.CA2016.severity = suggestion
|
||||
|
||||
# CA2021: Do not call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
|
||||
# Causing a lot of false positives with generics
|
||||
dotnet_diagnostic.CA2021.severity = none
|
||||
|
||||
# CA2101: Specify marshaling for P/Invoke string arguments
|
||||
# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport
|
||||
dotnet_diagnostic.CA2101.severity = none
|
||||
|
||||
# CA2201: Do not raise reserved exception types
|
||||
dotnet_diagnostic.CA2201.severity = warning
|
||||
|
||||
# CA2208: Instantiate argument exceptions correctly
|
||||
dotnet_diagnostic.CA2208.severity = suggestion
|
||||
|
||||
# CA2242: Test for NaN correctly
|
||||
dotnet_diagnostic.CA2242.severity = warning
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
|
||||
# See: https://github.com/ppy/osu/pull/19677
|
||||
dotnet_diagnostic.OSUF001.severity = none
|
||||
@@ -1,56 +0,0 @@
|
||||
<!-- Contains required properties for osu!framework projects. -->
|
||||
<Project>
|
||||
<PropertyGroup Label="C#">
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Stabilises hot reload, see: https://platform.uno/docs/articles/studio/Hot%20Reload/hot-reload-overview.html?tabs=vswin%2Cwindows%2Cskia-desktop%2Ccommon-issues -->
|
||||
<GenerateAssemblyInfo Condition="'$(Configuration)'=='Debug'">false</GenerateAssemblyInfo>
|
||||
<!-- Required due to the above -->
|
||||
<NoWarn Condition="'$(Configuration)'=='Debug'">$(NoWarn);CA1416</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="License">
|
||||
<None Include="$(MSBuildThisFileDirectory)osu.licenseheader">
|
||||
<Link>osu.licenseheader</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<!-- Rider compatibility: .globalconfig needs to be explicitly referenced instead of using the global file name. -->
|
||||
<GlobalAnalyzerConfigFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\osu.globalconfig" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<AnalysisMode>Default</AnalysisMode>
|
||||
<AnalysisModeDesign>Default</AnalysisModeDesign>
|
||||
<AnalysisModeDocumentation>Recommended</AnalysisModeDocumentation>
|
||||
<AnalysisModeGlobalization>Recommended</AnalysisModeGlobalization>
|
||||
<AnalysisModeInteroperability>Recommended</AnalysisModeInteroperability>
|
||||
<AnalysisModeMaintainability>Recommended</AnalysisModeMaintainability>
|
||||
<AnalysisModeNaming>Default</AnalysisModeNaming>
|
||||
<AnalysisModePerformance>Minimum</AnalysisModePerformance>
|
||||
<AnalysisModeReliability>Recommended</AnalysisModeReliability>
|
||||
<AnalysisModeSecurity>Default</AnalysisModeSecurity>
|
||||
<AnalysisModeUsage>Default</AnalysisModeUsage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Documentation">
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Nuget">
|
||||
<IsPackable>false</IsPackable>
|
||||
<Authors>ppy Pty Ltd</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||
<Company>ppy Pty Ltd</Company>
|
||||
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
|
||||
<PackageTags>osu game</PackageTags>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Realm DisableAnalytics="true" />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,11 @@
|
||||
osu!lazer is currently still under heavy development!
|
||||
|
||||
Please ensure that you are making an issue for one of the following:
|
||||
|
||||
- A bug with currently implemented features (not features that don't exist)
|
||||
- A feature you are considering adding, so we can collaborate on feedback and design.
|
||||
- Discussions about technical design decisions
|
||||
|
||||
If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide.
|
||||
|
||||
Screenshots and log files are highly welcomed.
|
||||
@@ -1,11 +0,0 @@
|
||||
dotnet tool restore
|
||||
|
||||
# Temporarily disabled until the tool is upgraded to 5.0.
|
||||
# The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7.
|
||||
# - cmd: dotnet format --dry-run --check
|
||||
|
||||
dotnet CodeFileSanity
|
||||
dotnet jb inspectcode "osu.Desktop.slnf" --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||
dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
||||
|
||||
exit $LASTEXITCODE
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
dotnet tool restore
|
||||
dotnet CodeFileSanity
|
||||
dotnet jb inspectcode "osu.Desktop.slnf" --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||
dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2025 ppy Pty Ltd <contact@ppy.sh>.
|
||||
Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,148 +1,32 @@
|
||||
<p align="center">
|
||||
<img width="500" alt="osu! logo" src="assets/lazer.png">
|
||||
</p>
|
||||
# osu! [](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
|
||||
|
||||
# osu!
|
||||
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era!
|
||||
|
||||
[](https://github.com/ppy/osu/actions/workflows/ci.yml)
|
||||
[](https://github.com/ppy/osu/releases/latest)
|
||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||
[](https://discord.gg/ppy)
|
||||
[](https://crowdin.com/project/osu-web)
|
||||
# Status
|
||||
|
||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||
This is still heavily under development and is not intended for end-user use. This repository is intended for developer collaboration. You're welcome to try and use it but please do not submit bug reports without a patch. Please do not ask for help building or using this software.
|
||||
|
||||
This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
|
||||
# Requirements
|
||||
|
||||
## Status
|
||||
- A desktop platform that can compile .NET 4.6.1. 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.
|
||||
|
||||
This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
|
||||
# Getting Started
|
||||
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
|
||||
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`)
|
||||
|
||||
A few resources are available as starting points to getting involved and understanding the project:
|
||||
# Contributing
|
||||
|
||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
|
||||
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
|
||||
|
||||
## Running osu!
|
||||
Please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
|
||||
|
||||
If you are just looking to give the game a whirl, you can grab the latest release for your platform:
|
||||
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/issues).
|
||||
|
||||
### Latest release:
|
||||
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.
|
||||
|
||||
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|
||||
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
|
||||
# Licence
|
||||
|
||||
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
|
||||
|
||||
If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
|
||||
|
||||
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
||||
osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
||||
|
||||
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
|
||||
|
||||
## Developing osu!
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please make sure you have the following prerequisites:
|
||||
|
||||
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||
|
||||
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
|
||||
|
||||
### Downloading the source code
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ppy/osu
|
||||
cd osu
|
||||
```
|
||||
|
||||
To update the source code to the latest commit, run the following command inside the `osu` directory:
|
||||
|
||||
```shell
|
||||
git pull
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
#### From an IDE
|
||||
|
||||
You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
|
||||
|
||||
- `osu.Desktop.slnf` (most common)
|
||||
- `osu.Android.slnf`
|
||||
- `osu.iOS.slnf`
|
||||
|
||||
Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
|
||||
|
||||
To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
|
||||
|
||||
#### From CLI
|
||||
|
||||
You can also build and run *osu!* from the command-line with a single command:
|
||||
|
||||
```shell
|
||||
dotnet run --project osu.Desktop
|
||||
```
|
||||
|
||||
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
|
||||
|
||||
If the build fails, try to restore NuGet packages with `dotnet restore`.
|
||||
|
||||
### Testing with resource/framework modifications
|
||||
|
||||
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
|
||||
|
||||
Windows:
|
||||
|
||||
```ps
|
||||
UseLocalFramework.ps1
|
||||
UseLocalResources.ps1
|
||||
```
|
||||
|
||||
macOS / Linux:
|
||||
|
||||
```ps
|
||||
UseLocalFramework.sh
|
||||
UseLocalResources.sh
|
||||
```
|
||||
|
||||
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
|
||||
|
||||
```
|
||||
|- osu // this repository
|
||||
|- osu-framework
|
||||
|- osu-resources
|
||||
```
|
||||
|
||||
### Code analysis
|
||||
|
||||
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
|
||||
|
||||
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
|
||||
|
||||
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||
|
||||
## Contributing
|
||||
|
||||
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
|
||||
|
||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||
|
||||
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||
|
||||
Our team believes in **human contributions**. Any contribution – be it an issue report or a pull request – which is created by, documented by, or aided by AI/LLM usage will typically be **closed and locked without further discussion**.
|
||||
|
||||
## Licence
|
||||
|
||||
*osu!*'s code and framework 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.
|
||||
The osu! client code and framework 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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using osu.Framework.Graphics;
|
||||
using OpenTK;
|
||||
using Symcol.Core.Graphics.Containers;
|
||||
|
||||
namespace Symcol.Core.GameObjects
|
||||
{
|
||||
public class SymcolHitbox : SymcolContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// whether we want to do hit detection
|
||||
/// </summary>
|
||||
public int Team { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether we want to do hit detection
|
||||
/// </summary>
|
||||
public bool HitDetection { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// the shape of this object (used for hit detection)
|
||||
/// </summary>
|
||||
public Shape Shape { get; }
|
||||
|
||||
public SymcolHitbox(Vector2 size, Shape shape = Shape.Circle)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Shape = shape;
|
||||
Size = size;
|
||||
|
||||
if (Shape == Shape.Circle)
|
||||
Child = new SymcolContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = Width / 2
|
||||
};
|
||||
else if (Shape == Shape.Rectangle)
|
||||
Child = new SymcolContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
|
||||
public bool HitDetect(SymcolHitbox hitbox1, SymcolHitbox hitbox2)
|
||||
{
|
||||
if (hitbox1.HitDetection && hitbox2.HitDetection && hitbox1.Team != hitbox2.Team)
|
||||
{
|
||||
if (hitbox1.Shape == Shape.Circle && hitbox2.Shape == Shape.Circle)
|
||||
{
|
||||
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||
return true;
|
||||
}
|
||||
else if (hitbox1.Shape == Shape.Circle && hitbox2.Shape == Shape.Rectangle || hitbox1.Shape == Shape.Rectangle && hitbox2.Shape == Shape.Circle)
|
||||
{
|
||||
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||
return true;
|
||||
}
|
||||
else if (hitbox1.Shape == Shape.Rectangle && hitbox2.Shape == Shape.Rectangle)
|
||||
{
|
||||
if (hitbox1.ScreenSpaceDrawQuad.AABB.IntersectsWith(hitbox2.ScreenSpaceDrawQuad.AABB))
|
||||
return true;
|
||||
}
|
||||
else if (hitbox1.Shape == Shape.Complex || hitbox2.Shape == Shape.Complex)
|
||||
foreach (SymcolContainer child1 in hitbox1.Children)
|
||||
foreach (SymcolContainer child2 in hitbox2.Children)
|
||||
if (child1.ScreenSpaceDrawQuad.AABB.IntersectsWith(child2.ScreenSpaceDrawQuad.AABB))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Shape
|
||||
{
|
||||
Circle,
|
||||
Rectangle,
|
||||
Complex
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace Symcol.Core.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Will support base eden game functions (if we come up with any)
|
||||
/// </summary>
|
||||
public class SymcolClickableContainer : ClickableContainer
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace Symcol.Core.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// Will support base eden game functions (if we come up with any)
|
||||
/// </summary>
|
||||
public class SymcolContainer : Container
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Core.Graphics.Containers
|
||||
{
|
||||
public class SymcolDialContainer : CircularContainer
|
||||
{
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
private Vector2 mousePosition;
|
||||
|
||||
private float lastAngle;
|
||||
private float currentRotation;
|
||||
public float RotationAbsolute;
|
||||
|
||||
private int completeTick;
|
||||
|
||||
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));
|
||||
|
||||
private bool rotationTransferred;
|
||||
|
||||
protected override bool OnMouseMove(InputState state)
|
||||
{
|
||||
mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position);
|
||||
return base.OnMouseMove(state);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
|
||||
|
||||
|
||||
if (!rotationTransferred)
|
||||
{
|
||||
currentRotation = Rotation * 2;
|
||||
rotationTransferred = true;
|
||||
}
|
||||
|
||||
if (thisAngle - lastAngle > 180)
|
||||
lastAngle += 360;
|
||||
else if (lastAngle - thisAngle > 180)
|
||||
lastAngle -= 360;
|
||||
|
||||
currentRotation += thisAngle - lastAngle;
|
||||
RotationAbsolute += Math.Abs(thisAngle - lastAngle);
|
||||
|
||||
lastAngle = thisAngle;
|
||||
|
||||
foreach(Drawable drawable in Children)
|
||||
drawable.RotateTo(currentRotation / 2, 200, Easing.OutExpo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using osu.Framework.Input;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace Symcol.Core.Graphics.Containers
|
||||
{
|
||||
public class SymcolDragContainer : SymcolContainer
|
||||
{
|
||||
protected override bool OnDragStart(InputState state) => true;
|
||||
|
||||
public bool AllowLeftClickDrag { get; set; } = true;
|
||||
|
||||
private bool drag;
|
||||
|
||||
private Vector2 startPosition;
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
startPosition = Position;
|
||||
|
||||
if (args.Button == MouseButton.Left && AllowLeftClickDrag || args.Button == MouseButton.Right)
|
||||
drag = true;
|
||||
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnDrag(InputState state)
|
||||
{
|
||||
if (drag)
|
||||
Position = startPosition + state.Mouse.Position - state.Mouse.PositionMouseDown.GetValueOrDefault();
|
||||
|
||||
return base.OnDrag(state);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
if (args.Button == MouseButton.Left && AllowLeftClickDrag || args.Button == MouseButton.Right)
|
||||
drag = false;
|
||||
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using Symcol.Core.Graphics.Containers;
|
||||
|
||||
namespace Symcol.Core.Graphics.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// just a Button with a sprite
|
||||
/// </summary>
|
||||
public class SpriteButton : SymcolClickableContainer
|
||||
{
|
||||
private readonly string textureName;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return spriteText?.Text; }
|
||||
set
|
||||
{
|
||||
if (spriteText != null)
|
||||
spriteText.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Sprite sprite;
|
||||
private readonly SpriteText spriteText;
|
||||
|
||||
public SpriteButton(string textureName)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
this.textureName = textureName;
|
||||
Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
sprite = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill
|
||||
},
|
||||
spriteText = new SpriteText
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
{
|
||||
sprite.Texture = textures.Get(textureName);
|
||||
}
|
||||
|
||||
protected override bool OnClick(InputState state)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
var flash = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.5f
|
||||
};
|
||||
|
||||
Add(flash);
|
||||
|
||||
flash.Blending = BlendingMode.Additive;
|
||||
flash.FadeOut(200);
|
||||
flash.Expire();
|
||||
}
|
||||
|
||||
return base.OnClick(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Input;
|
||||
using Symcol.Core.Graphics.Containers;
|
||||
|
||||
namespace Symcol.Core.Graphics.UserInterface
|
||||
{
|
||||
public class SymcolWindow : SymcolContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Put all your stuff in this
|
||||
/// </summary>
|
||||
public SymcolContainer WindowContent { get; set; }
|
||||
public SpriteText WindowTitle;
|
||||
|
||||
private readonly SymcolContainer topBar;
|
||||
private readonly SymcolClickableContainer minimize;
|
||||
|
||||
public SymcolWindow(Vector2 size)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
CornerRadius = 6;
|
||||
Masking = true;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
topBar = new SymcolContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Height = 20,
|
||||
Width = size.X,
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
WindowTitle = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
TextSize = 18
|
||||
},
|
||||
new SymcolClickableContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 30,
|
||||
Action = Close,
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
Colour = Color4.Red,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.5f
|
||||
}
|
||||
},
|
||||
minimize = new SymcolClickableContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 30,
|
||||
Position = new Vector2(-30, 0),
|
||||
Action = Minimize,
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
WindowContent = new SymcolContainer
|
||||
{
|
||||
Size = size,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
};
|
||||
|
||||
WindowContent.Position = new Vector2(0, topBar.Height);
|
||||
}
|
||||
|
||||
protected void Close()
|
||||
{
|
||||
this.FadeOut(200);
|
||||
}
|
||||
|
||||
protected void Open()
|
||||
{
|
||||
this.FadeIn(200);
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
if (Alpha > 0)
|
||||
this.FadeOut(200);
|
||||
else
|
||||
this.FadeIn(200);
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(InputState state) => true;
|
||||
|
||||
private bool drag;
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
if (args.Button == MouseButton.Left)
|
||||
drag = true;
|
||||
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnDrag(InputState state)
|
||||
{
|
||||
if (drag)
|
||||
Position += state.Mouse.Delta;
|
||||
|
||||
return base.OnDrag(state);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
if (args.Button == MouseButton.Left)
|
||||
drag = false;
|
||||
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
public void Maximize()
|
||||
{
|
||||
WindowContent.FadeIn(200);
|
||||
WindowContent.ScaleTo(Vector2.One, 200);
|
||||
minimize.Action = Minimize;
|
||||
}
|
||||
|
||||
public void Minimize()
|
||||
{
|
||||
WindowContent.FadeOut(200);
|
||||
WindowContent.ScaleTo(new Vector2(1, 0), 200);
|
||||
minimize.Action = Maximize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Core.Networking
|
||||
{
|
||||
[Serializable]
|
||||
public class BasicPacket : Packet
|
||||
{
|
||||
/// <summary>
|
||||
/// Ask host if we can connect
|
||||
/// </summary>
|
||||
public bool Connect;
|
||||
|
||||
/// <summary>
|
||||
/// Tell the host we are breaking up
|
||||
/// </summary>
|
||||
public bool Disconnect;
|
||||
|
||||
/// <summary>
|
||||
/// Testing Connection
|
||||
/// </summary>
|
||||
public bool Test;
|
||||
|
||||
/// <summary>
|
||||
/// Send a force exit to others
|
||||
/// </summary>
|
||||
public bool Abort;
|
||||
|
||||
/// <summary>
|
||||
/// PreLoad the game
|
||||
/// </summary>
|
||||
public bool LoadGame;
|
||||
|
||||
/// <summary>
|
||||
/// Request a list of all players from Host
|
||||
/// </summary>
|
||||
public bool RequestPlayerList;
|
||||
|
||||
/// <summary>
|
||||
/// List of players in this match that we should account for
|
||||
/// </summary>
|
||||
public List<ClientInfo> PlayerList = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Tell Host we are PreLoaded
|
||||
/// </summary>
|
||||
public bool Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// Start the game already!
|
||||
/// </summary>
|
||||
public bool StartGame;
|
||||
|
||||
/// <summary>
|
||||
/// Send to host when game started
|
||||
/// </summary>
|
||||
public bool GameStarted;
|
||||
|
||||
public BasicPacket(ClientInfo clientInfo) : base(clientInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Symcol.Core.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Just a client signature basically
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ClientInfo
|
||||
{
|
||||
public string IP;
|
||||
|
||||
public int Port;
|
||||
|
||||
public int Ping;
|
||||
|
||||
public int ConncetionTryCount;
|
||||
|
||||
public double LastConnectionTime;
|
||||
|
||||
public double StartedTestConnectionTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
namespace Symcol.Core.Networking
|
||||
{
|
||||
public class NetworkingClient
|
||||
{
|
||||
public UdpClient UdpClient;
|
||||
|
||||
public IPEndPoint EndPoint;
|
||||
|
||||
/// <summary>
|
||||
/// if false we only receive
|
||||
/// </summary>
|
||||
public readonly bool Send;
|
||||
|
||||
public readonly int Port;
|
||||
|
||||
public readonly string IP;
|
||||
|
||||
public NetworkingClient(bool send, string ip, int port = 25570)
|
||||
{
|
||||
Port = port;
|
||||
IP = ip;
|
||||
|
||||
if (send)
|
||||
initializeSend();
|
||||
else
|
||||
initializeReceive();
|
||||
}
|
||||
|
||||
private void initializeSend()
|
||||
{
|
||||
UdpClient = new UdpClient(IP, Port);
|
||||
}
|
||||
|
||||
private void initializeReceive()
|
||||
{
|
||||
UdpClient = new UdpClient(Port);
|
||||
EndPoint = new IPEndPoint(IPAddress.Any, Port);
|
||||
}
|
||||
|
||||
private void sendByte(byte[] data)
|
||||
{
|
||||
UdpClient.Send(data, data.Length);
|
||||
}
|
||||
|
||||
private byte[] receiveByte()
|
||||
{
|
||||
return UdpClient.Receive(ref EndPoint);
|
||||
}
|
||||
|
||||
public static int SENTPACKETCOUNT;
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet somewhere
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendPacket(Packet packet)
|
||||
{
|
||||
SENTPACKETCOUNT++;
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
formatter.Serialize(stream, packet);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
int i = packet.PacketSize;
|
||||
retry:
|
||||
byte[] data = new byte[i];
|
||||
|
||||
try
|
||||
{
|
||||
stream.Read(data, 0, (int)stream.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
i *= 2;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
sendByte(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receive a Packet from somewhere
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Packet ReceivePacket(bool force = false)
|
||||
{
|
||||
if (UdpClient.Available > 0 || force)
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
byte[] data = receiveByte();
|
||||
stream.Write(data, 0, data.Length);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
Packet packet = (Packet)formatter.Deserialize(stream);
|
||||
packet.ClientInfo.IP = EndPoint.Address.ToString();
|
||||
|
||||
return packet;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (UdpClient != null)
|
||||
UdpClient.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
#define SoloTesting
|
||||
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Core.Networking
|
||||
{
|
||||
//TODO: This NEEDS its own clock to avoid fuckery later on with DoubleTime and HalfTime
|
||||
public class NetworkingClientHandler : Container
|
||||
{
|
||||
//30 Seconds by default
|
||||
protected virtual double TimeOutTime => 30000;
|
||||
|
||||
protected readonly NetworkingClient ReceiveClient;
|
||||
|
||||
protected readonly NetworkingClient SendClient;
|
||||
|
||||
/// <summary>
|
||||
/// Just a client signature basically
|
||||
/// </summary>
|
||||
public ClientInfo ClientInfo;
|
||||
|
||||
/// <summary>
|
||||
/// All Connecting clients
|
||||
/// </summary>
|
||||
public readonly List<ClientInfo> ConnectingClients = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// All Connected clients
|
||||
/// </summary>
|
||||
public readonly List<ClientInfo> ConncetedClients = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Clients waiting in our match
|
||||
/// </summary>
|
||||
public readonly List<ClientInfo> InMatchClients = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Clients loaded and ready to start
|
||||
/// </summary>
|
||||
public readonly List<ClientInfo> LoadedClients = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Clients ingame playing
|
||||
/// </summary>
|
||||
public readonly List<ClientInfo> InGameClients = new List<ClientInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets hit when we get a Packet
|
||||
/// </summary>
|
||||
public Action<Packet> OnPacketReceive;
|
||||
|
||||
/// <summary>
|
||||
/// (Peer) Call this when we connect to a Host (Includes list of connected peers + Host)
|
||||
/// </summary>
|
||||
public Action<List<ClientInfo>> OnConnectedToHost;
|
||||
|
||||
/// <summary>
|
||||
/// (Host) Whenever a new client Connects
|
||||
/// </summary>
|
||||
public Action<ClientInfo> OnClientConnect;
|
||||
|
||||
/// <summary>
|
||||
/// (Host) Whenever a new client Disconnects
|
||||
/// </summary>
|
||||
public Action<ClientInfo> OnClientDisconnect;
|
||||
|
||||
/// <summary>
|
||||
/// (Host/Peer) When a new Client joins the game
|
||||
/// </summary>
|
||||
public Action<ClientInfo> OnClientJoin;
|
||||
|
||||
/// <summary>
|
||||
/// Receive a full player list
|
||||
/// </summary>
|
||||
public Action<List<ClientInfo>> OnReceivePlayerList;
|
||||
|
||||
/// <summary>
|
||||
/// if we are connected and in a match
|
||||
/// </summary>
|
||||
public bool InMatch;
|
||||
|
||||
/// <summary>
|
||||
/// Are we in a game
|
||||
/// </summary>
|
||||
public bool InGame;
|
||||
|
||||
/// <summary>
|
||||
/// Are we loaded and ready to start?
|
||||
/// </summary>
|
||||
public bool Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// Called to leave an in-progress game
|
||||
/// </summary>
|
||||
public Action OnAbort;
|
||||
|
||||
/// <summary>
|
||||
/// Called to load the game (Includes Host)
|
||||
/// </summary>
|
||||
public Action<List<ClientInfo>> OnLoadGame;
|
||||
|
||||
/// <summary>
|
||||
/// Called to start the game once loaded
|
||||
/// </summary>
|
||||
public Action StartGame;
|
||||
|
||||
public readonly ClientType ClientType;
|
||||
|
||||
public NetworkingClientHandler(ClientType type, string ip, int port = 25570, string thisLocalIp = "0.0.0.0")
|
||||
{
|
||||
AlwaysPresent = true;
|
||||
|
||||
ClientType = type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ClientType.Host:
|
||||
ReceiveClient = new NetworkingClient(false, ip, port);
|
||||
break;
|
||||
case ClientType.Peer:
|
||||
ReceiveClient = new NetworkingClient(false, thisLocalIp, port);
|
||||
SendClient = new NetworkingClient(true, ip, port);
|
||||
break;
|
||||
case ClientType.Server:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
Logger.Log("Created a RulesetNetworkingClientHandler", LoggingTarget.Network, LogLevel.Verbose);
|
||||
|
||||
if (ClientInfo == null)
|
||||
ClientInfo = new ClientInfo
|
||||
{
|
||||
Port = port
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (ClientType == ClientType.Peer)
|
||||
ConnectToHost();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
PacketRestart:
|
||||
Packet p = null;
|
||||
|
||||
if (ReceiveClient.UdpClient.Available > 0)
|
||||
p = ReceiveClient.ReceivePacket();
|
||||
|
||||
if (p is BasicPacket packet)
|
||||
{
|
||||
//Hosts
|
||||
if (SendClient == null)
|
||||
{
|
||||
if (packet.Disconnect)
|
||||
{
|
||||
OnClientDisconnect?.Invoke(packet.ClientInfo);
|
||||
foreach (ClientInfo client in ConnectingClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
ConnectingClients.Remove(client);
|
||||
Logger.Log("A Connecting Client has Disconnected", LoggingTarget.Network, LogLevel.Verbose);
|
||||
break;
|
||||
}
|
||||
foreach (ClientInfo client in ConncetedClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
ConncetedClients.Remove(client);
|
||||
Logger.Log("A Client has Disconnected", LoggingTarget.Network, LogLevel.Verbose);
|
||||
break;
|
||||
}
|
||||
foreach (ClientInfo client in InMatchClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
InMatchClients.Remove(client);
|
||||
break;
|
||||
}
|
||||
foreach (ClientInfo client in LoadedClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
LoadedClients.Remove(client);
|
||||
break;
|
||||
}
|
||||
foreach (ClientInfo client in InGameClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
InGameClients.Remove(client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.Connect)
|
||||
{
|
||||
packet.ClientInfo.StartedTestConnectionTime = Time.Current;
|
||||
ConnectingClients.Add(packet.ClientInfo);
|
||||
|
||||
NetworkingClient client = new NetworkingClient(true, packet.ClientInfo.IP, packet.ClientInfo.Port);
|
||||
|
||||
List<ClientInfo> playerList = new List<ClientInfo>
|
||||
{
|
||||
ClientInfo
|
||||
};
|
||||
|
||||
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||
playerList.Add(clientInfo);
|
||||
|
||||
client.SendPacket(new BasicPacket(ClientInfo)
|
||||
{
|
||||
PlayerList = playerList,
|
||||
Connect = true
|
||||
});
|
||||
|
||||
Logger.Log("A Client is Connecting. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
if (packet.RequestPlayerList)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, packet.ClientInfo.IP, packet.ClientInfo.Port);
|
||||
|
||||
List<ClientInfo> playerList = new List<ClientInfo>
|
||||
{
|
||||
ClientInfo
|
||||
};
|
||||
|
||||
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||
playerList.Add(clientInfo);
|
||||
|
||||
client.SendPacket(new BasicPacket(ClientInfo)
|
||||
{
|
||||
PlayerList = playerList,
|
||||
RequestPlayerList = true
|
||||
});
|
||||
|
||||
Logger.Log("A Client is Connecting. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
if (packet.Loaded)
|
||||
foreach (ClientInfo client in InMatchClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
Logger.Log("A Client has Loaded and is ready to start", LoggingTarget.Network, LogLevel.Verbose);
|
||||
InMatchClients.Remove(client);
|
||||
LoadedClients.Add(client);
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet.GameStarted)
|
||||
foreach (ClientInfo client in LoadedClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
Logger.Log("A Client has started!", LoggingTarget.Network, LogLevel.Verbose);
|
||||
LoadedClients.Remove(client);
|
||||
InGameClients.Add(client);
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet.Test)
|
||||
{
|
||||
foreach (ClientInfo client in ConnectingClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
client.Ping = (int)Time.Current - (int)client.StartedTestConnectionTime;
|
||||
ConnectingClients.Remove(client);
|
||||
ConncetedClients.Add(client);
|
||||
InMatchClients.Add(client);
|
||||
OnClientJoin?.Invoke(client);
|
||||
client.LastConnectionTime = Time.Current;
|
||||
client.ConncetionTryCount = 0;
|
||||
Logger.Log("Successfully connected to a Client! Ping: " + client.Ping, LoggingTarget.Network, LogLevel.Verbose);
|
||||
break;
|
||||
}
|
||||
foreach (ClientInfo client in ConncetedClients)
|
||||
if (client.IP == packet.ClientInfo.IP)
|
||||
{
|
||||
client.Ping = (int)Time.Current - (int)client.StartedTestConnectionTime;
|
||||
client.LastConnectionTime = Time.Current;
|
||||
client.ConncetionTryCount = 0;
|
||||
Logger.Log("Successfully maintained connection to a Client! Ping: " + client.Ping, LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !SoloTesting
|
||||
if (InMatchClients.Count == 0 && LoadedClients.Count > 0 && Loaded && !InGame)
|
||||
SendStartGame();
|
||||
#endif
|
||||
|
||||
//Peers
|
||||
else if (SendClient != null)
|
||||
{
|
||||
if (packet.Connect)
|
||||
{
|
||||
if (!InGame && !InMatch)
|
||||
{
|
||||
InMatch = true;
|
||||
OnConnectedToHost?.Invoke(packet.PlayerList);
|
||||
}
|
||||
Logger.Log("Connected to Host!", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
if (packet.Test)
|
||||
{
|
||||
SendToHost(new BasicPacket(ClientInfo) { Test = true });
|
||||
Logger.Log("Received connection test info from host, returning. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
if (packet.RequestPlayerList)
|
||||
OnReceivePlayerList?.Invoke(packet.PlayerList);
|
||||
|
||||
if (packet.StartGame)
|
||||
{
|
||||
StartGame?.Invoke();
|
||||
SendToHost(new BasicPacket(ClientInfo) { GameStarted = true });
|
||||
InGame = true;
|
||||
}
|
||||
|
||||
if (packet.Abort)
|
||||
{
|
||||
OnAbort?.Invoke();
|
||||
InGame = false;
|
||||
Loaded = false;
|
||||
}
|
||||
|
||||
if (packet.LoadGame)
|
||||
{
|
||||
Logger.Log("Received instructions to LoadGame for " + packet.PlayerList.Count + " players", LoggingTarget.Network, LogLevel.Verbose);
|
||||
OnLoadGame?.Invoke(packet.PlayerList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if SoloTesting
|
||||
if (Loaded && !InGame)
|
||||
SendStartGame();
|
||||
#endif
|
||||
|
||||
if (p != null)
|
||||
OnPacketReceive?.Invoke(p);
|
||||
|
||||
if (ReceiveClient.UdpClient.Available > 0)
|
||||
goto PacketRestart;
|
||||
|
||||
foreach (ClientInfo client in ConnectingClients)
|
||||
{
|
||||
if (client.LastConnectionTime + TimeOutTime / 10 <= Time.Current && client.ConncetionTryCount == 0)
|
||||
{
|
||||
client.StartedTestConnectionTime = Time.Current;
|
||||
TestConnection(client);
|
||||
}
|
||||
|
||||
if (client.LastConnectionTime + TimeOutTime / 6 <= Time.Current && client.ConncetionTryCount == 1)
|
||||
TestConnection(client);
|
||||
|
||||
if (client.LastConnectionTime + TimeOutTime / 3 <= Time.Current && client.ConncetionTryCount == 2)
|
||||
TestConnection(client);
|
||||
|
||||
if (client.StartedTestConnectionTime + TimeOutTime <= Time.Current)
|
||||
{
|
||||
ConnectingClients.Remove(client);
|
||||
Logger.Log("Connection to a connecting client lost! - " + client.IP + ":" + client.Port, LoggingTarget.Network, LogLevel.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ClientInfo client in ConncetedClients)
|
||||
{
|
||||
if (client.LastConnectionTime + TimeOutTime / 6 <= Time.Current && client.ConncetionTryCount == 0)
|
||||
{
|
||||
client.StartedTestConnectionTime = Time.Current;
|
||||
TestConnection(client);
|
||||
}
|
||||
|
||||
if (client.LastConnectionTime + TimeOutTime / 3 <= Time.Current && client.ConncetionTryCount == 1)
|
||||
TestConnection(client);
|
||||
|
||||
if (client.LastConnectionTime + TimeOutTime / 2 <= Time.Current && client.ConncetionTryCount == 2)
|
||||
TestConnection(client);
|
||||
|
||||
if (client.StartedTestConnectionTime + TimeOutTime <= Time.Current)
|
||||
{
|
||||
ConncetedClients.Remove(client);
|
||||
InGameClients.Remove(client);
|
||||
LoadedClients.Remove(client);
|
||||
InGameClients.Remove(client);
|
||||
Logger.Log("Connection to a connected client lost! - " + client.IP + ":" + client.Port, LoggingTarget.Network, LogLevel.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poke!
|
||||
/// </summary>
|
||||
/// <param name="clientInfo"></param>
|
||||
protected void TestConnection(ClientInfo clientInfo)
|
||||
{
|
||||
clientInfo.ConncetionTryCount++;
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(new BasicPacket(ClientInfo) { Test = true });
|
||||
Logger.Log("Testing a client's connection - " + clientInfo.IP + ":" + clientInfo.Port, LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
public void RequestPlayerList()
|
||||
{
|
||||
BasicPacket packet = new BasicPacket(ClientInfo) { RequestPlayerList = true };
|
||||
SendToHost(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell peers to start loading game
|
||||
/// </summary>
|
||||
public virtual void StartLoadingGame()
|
||||
{
|
||||
if (SendClient == null)
|
||||
{
|
||||
BasicPacket packet = new BasicPacket(ClientInfo) { LoadGame = true };
|
||||
|
||||
foreach (ClientInfo client in InMatchClients)
|
||||
packet.PlayerList.Add(client);
|
||||
packet.PlayerList.Add(ClientInfo);
|
||||
|
||||
SendToInMatchClients(packet);
|
||||
|
||||
OnLoadGame?.Invoke(packet.PlayerList);
|
||||
}
|
||||
else
|
||||
Logger.Log("Called StartLoadingGame - We are not the Host!", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this when the game is Loaded and ready to be started
|
||||
/// </summary>
|
||||
public virtual void GameLoaded()
|
||||
{
|
||||
Loaded = true;
|
||||
SendToHost(new BasicPacket(ClientInfo) { Loaded = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the Host
|
||||
/// </summary>
|
||||
public virtual void ConnectToHost()
|
||||
{
|
||||
SendToHost(new BasicPacket(ClientInfo) { Connect = true });
|
||||
Logger.Log("Attempting conection to Host. . .", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell peers to start and starts ours
|
||||
/// </summary>
|
||||
public virtual void SendStartGame()
|
||||
{
|
||||
if (SendClient == null)
|
||||
{
|
||||
SendToLoadedClients(new BasicPacket(ClientInfo) { StartGame = true });
|
||||
InGame = true;
|
||||
Logger.Log("Sending Start Game", LoggingTarget.Network, LogLevel.Verbose);
|
||||
}
|
||||
StartGame?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to the Host
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToHost(Packet packet)
|
||||
{
|
||||
if (SendClient != null)
|
||||
SendClient.SendPacket(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to all Connecting clients
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToConnectingClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in ConnectingClients)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to all clients Connected and waiting
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToConnectedClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to all clients In this Match
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToInMatchClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in InMatchClients)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to all clients Loaded
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToLoadedClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in LoadedClients)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to all clients InGame
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToInGameClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in InGameClients)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Packet to ALL clients we know
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
public void SendToAllClients(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
{
|
||||
SendToConnectingClients(packet);
|
||||
SendToConnectedClients(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send to all but the one that sent it
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
/// <param name="playerID"></param>
|
||||
public void ShareWithOtherPeers(Packet packet)
|
||||
{
|
||||
if (SendClient == null)
|
||||
foreach (ClientInfo clientInfo in ConncetedClients)
|
||||
if (packet.ClientInfo.IP != clientInfo.IP)
|
||||
{
|
||||
NetworkingClient client = new NetworkingClient(true, clientInfo.IP, clientInfo.Port);
|
||||
client.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AbortGame()
|
||||
{
|
||||
SendToLoadedClients(new BasicPacket(ClientInfo) { Abort = true });
|
||||
SendToInGameClients(new BasicPacket(ClientInfo) { Abort = true });
|
||||
|
||||
restart:
|
||||
foreach (ClientInfo client in LoadedClients)
|
||||
{
|
||||
LoadedClients.Remove(client);
|
||||
InMatchClients.Add(client);
|
||||
goto restart;
|
||||
}
|
||||
foreach (ClientInfo client in InGameClients)
|
||||
{
|
||||
InGameClients.Remove(client);
|
||||
InMatchClients.Add(client);
|
||||
goto restart;
|
||||
}
|
||||
|
||||
InGame = false;
|
||||
Loaded = false;
|
||||
|
||||
OnAbort?.Invoke();
|
||||
}
|
||||
|
||||
public virtual void Disconnect()
|
||||
{
|
||||
Packet packet = new BasicPacket(ClientInfo) { Disconnect = true };
|
||||
|
||||
OnAbort?.Invoke();
|
||||
InMatch = false;
|
||||
InGame = false;
|
||||
Loaded = false;
|
||||
|
||||
if (SendClient == null)
|
||||
{
|
||||
SendToConnectingClients(packet);
|
||||
SendToConnectedClients(packet);
|
||||
}
|
||||
else
|
||||
SendToHost(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Die
|
||||
/// </summary>
|
||||
/// <param name="isDisposing"></param>
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
ReceiveClient?.Clear();
|
||||
|
||||
if (SendClient != null)
|
||||
{
|
||||
SendToHost(new BasicPacket(ClientInfo) { Disconnect = true });
|
||||
SendClient.Clear();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClientType
|
||||
{
|
||||
Host,
|
||||
Peer,
|
||||
Server
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Symcol.Core.Networking
|
||||
{
|
||||
[Serializable]
|
||||
public class Packet
|
||||
{
|
||||
/// <summary>
|
||||
/// Just a Signature
|
||||
/// </summary>
|
||||
public readonly ClientInfo ClientInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Specify starting size of packet for efficiency
|
||||
/// </summary>
|
||||
public virtual int PacketSize => 1024;
|
||||
|
||||
public Packet(ClientInfo clientInfo)
|
||||
{
|
||||
ClientInfo = clientInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("symcol.Toys")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("symcol.Toys")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("f34ac16c-e590-4d70-a069-a748326852bf")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F34AC16C-E590-4D70-A069-A748326852BF}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Symcol.Core</RootNamespace>
|
||||
<AssemblyName>Symcol.Core</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Cyotek.Drawing.BitmapFont, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58daa28b0b2de221, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Cyotek.Drawing.BitmapFont.1.3.4-beta1\lib\net46\Cyotek.Drawing.BitmapFont.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ManagedBass, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\ManagedBass.2.0.3\lib\net45\ManagedBass.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite.Net.Platform.Generic, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite.Net.Platform.Win32, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLiteNetExtensions, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GameObjects\SymcolHitbox.cs" />
|
||||
<Compile Include="Graphics\Containers\SymcolClickableContainer.cs" />
|
||||
<Compile Include="Graphics\Containers\SymcolDialContainer.cs" />
|
||||
<Compile Include="Graphics\Containers\SymcolDragContainer.cs" />
|
||||
<Compile Include="Graphics\Containers\SymcolContainer.cs" />
|
||||
<Compile Include="Graphics\UserInterface\SpriteButton.cs" />
|
||||
<Compile Include="Graphics\UserInterface\SymcolWindow.cs" />
|
||||
<Compile Include="Networking\BasicPacket.cs" />
|
||||
<Compile Include="Networking\ClientInfo.cs" />
|
||||
<Compile Include="Networking\NetworkingClient.cs" />
|
||||
<Compile Include="Networking\Packet.cs" />
|
||||
<Compile Include="Networking\NetworkingClientHandler.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu-Framework\osu.Framework\osu.Framework.csproj">
|
||||
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
|
||||
<Name>osu.Framework</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Containers
|
||||
{
|
||||
public class LinkText : OsuSpriteText, IHasTooltip
|
||||
{
|
||||
public string TooltipText => Tooltip;
|
||||
|
||||
public virtual string Tooltip => "";
|
||||
|
||||
private readonly OsuHoverContainer content;
|
||||
|
||||
public override bool HandleKeyboardInput => content.Action != null;
|
||||
public override bool HandleMouseInput => content.Action != null;
|
||||
|
||||
protected override Container<Drawable> Content => content ?? (Container<Drawable>)this;
|
||||
|
||||
public override IEnumerable<Drawable> FlowingChildren => Children;
|
||||
|
||||
public string Url
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
content.Action = () => Process.Start(value);
|
||||
}
|
||||
}
|
||||
|
||||
public LinkText()
|
||||
{
|
||||
AddInternal(content = new OsuHoverContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO: make this more generic
|
||||
/// </summary>
|
||||
public class ProfileLink : LinkText
|
||||
{
|
||||
public override string Tooltip => "View profile in browser";
|
||||
|
||||
public ProfileLink(User user, bool maintainer = false)
|
||||
{
|
||||
if (!maintainer)
|
||||
Text = "Ruleset Creator: " + user.Username;
|
||||
else
|
||||
Text = "Ruleset Maintainer: " + user.Username;
|
||||
|
||||
Url = $@"https://osu.ppy.sh/users/{user.Id}";
|
||||
Font = @"Exo2.0-RegularItalic";
|
||||
TextSize = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Rulesets.Core.HitObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Mostly stuff copied from Container
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject"></typeparam>
|
||||
public abstract class DrawableSymcolHitObject<TObject> : DrawableHitObject<TObject>
|
||||
where TObject : HitObject
|
||||
{
|
||||
//Future prep?
|
||||
//public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { }
|
||||
//public override void ApplyTransformsAt(double time, bool propagateChildren = false) { }
|
||||
|
||||
protected virtual Container<Drawable> Content => new Container();
|
||||
|
||||
public IReadOnlyList<Drawable> Children
|
||||
{
|
||||
get
|
||||
{
|
||||
return InternalChildren;
|
||||
}
|
||||
set
|
||||
{
|
||||
ChildrenEnumerable = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Drawable> ChildrenEnumerable
|
||||
{
|
||||
set
|
||||
{
|
||||
Clear();
|
||||
AddRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<Drawable> range)
|
||||
{
|
||||
foreach (Drawable d in range)
|
||||
Add(d);
|
||||
}
|
||||
|
||||
public Drawable Child
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Children.Count != 1)
|
||||
throw new InvalidOperationException($"{nameof(Child)} is only available when there's only 1 in {nameof(Children)}!");
|
||||
|
||||
return Children[0];
|
||||
}
|
||||
set
|
||||
{
|
||||
Clear();
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() => Clear(true);
|
||||
|
||||
public virtual void Clear(bool disposeChildren)
|
||||
{
|
||||
if (Content != null)
|
||||
Content.Clear(disposeChildren);
|
||||
else
|
||||
ClearInternal(disposeChildren);
|
||||
}
|
||||
|
||||
protected DrawableSymcolHitObject(TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(Drawable drawable)
|
||||
{
|
||||
AddInternal(drawable);
|
||||
}
|
||||
|
||||
public void Remove(Drawable drawable)
|
||||
{
|
||||
RemoveInternal(drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Symcol.Core.Networking;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||
{
|
||||
[Serializable]
|
||||
public class ChatPacket : Packet
|
||||
{
|
||||
public override int PacketSize => 4096;
|
||||
|
||||
public string Author;
|
||||
|
||||
public string AuthorColor;
|
||||
|
||||
public string Message;
|
||||
|
||||
public ChatPacket(ClientInfo clientInfo) : base(clientInfo)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Symcol.Core.Networking;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Just a client signature basically
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RulesetClientInfo : ClientInfo
|
||||
{
|
||||
public string Username = "";
|
||||
|
||||
public int UserID = -1;
|
||||
|
||||
public string UserPic;
|
||||
|
||||
public string UserBackground;
|
||||
|
||||
public string UserCountry;
|
||||
|
||||
public string CountryFlagName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using Symcol.Core.Networking;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||
{
|
||||
//TODO: This NEEDS its own clock to avoid fuckery later on with DoubleTime and HalfTime
|
||||
public class RulesetNetworkingClientHandler : NetworkingClientHandler, IOnlineComponent
|
||||
{
|
||||
public RulesetClientInfo RulesetClientInfo;
|
||||
|
||||
public Action<WorkingBeatmap> OnMapChange;
|
||||
|
||||
private OsuGame osu;
|
||||
|
||||
public RulesetNetworkingClientHandler(ClientType type, string ip, int port = 25570, string thisLocalIp = "0.0.0.0") : base(type, ip, port, thisLocalIp)
|
||||
{
|
||||
if (RulesetClientInfo == null)
|
||||
{
|
||||
RulesetClientInfo = new RulesetClientInfo
|
||||
{
|
||||
Port = port
|
||||
};
|
||||
|
||||
ClientInfo = RulesetClientInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send Map to Peers
|
||||
/// </summary>
|
||||
/// <param name="map"></param>
|
||||
public void SetMap(WorkingBeatmap map)
|
||||
{
|
||||
RulesetPacket packet;
|
||||
try
|
||||
{
|
||||
packet = new RulesetPacket(RulesetClientInfo)
|
||||
{
|
||||
OnlineBeatmapSetID = (int)map.BeatmapSetInfo.OnlineBeatmapSetID,
|
||||
OnlineBeatmapID = (int)map.BeatmapInfo.OnlineBeatmapID
|
||||
};
|
||||
SendToInMatchClients(packet);
|
||||
OnMapChange?.Invoke(osu.Beatmap.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
packet = new RulesetPacket(RulesetClientInfo);
|
||||
SendToInMatchClients(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(APIAccess api, OsuGame osu)
|
||||
{
|
||||
api.Register(this);
|
||||
this.osu = osu;
|
||||
}
|
||||
|
||||
public void APIStateChanged(APIAccess api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
default:
|
||||
RulesetClientInfo.Username = "";
|
||||
RulesetClientInfo.UserID = -1;
|
||||
break;
|
||||
case APIState.Online:
|
||||
RulesetClientInfo.Username = api.LocalUser.Value.Username;
|
||||
RulesetClientInfo.UserID = (int)api.LocalUser.Value.Id;
|
||||
RulesetClientInfo.UserCountry = api.LocalUser.Value.Country.FullName;
|
||||
RulesetClientInfo.CountryFlagName = api.LocalUser.Value.Country.FlagName;
|
||||
RulesetClientInfo.UserPic = api.LocalUser.Value.AvatarUrl;
|
||||
RulesetClientInfo.UserBackground = api.LocalUser.Value.CoverUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Symcol.Core.Networking;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||
{
|
||||
[Serializable]
|
||||
public class RulesetPacket : Packet
|
||||
{
|
||||
public new readonly RulesetClientInfo ClientInfo;
|
||||
|
||||
public override int PacketSize => 4096;
|
||||
|
||||
public int OnlineBeatmapSetID = -1;
|
||||
|
||||
public int OnlineBeatmapID = -1;
|
||||
|
||||
public bool HaveMap;
|
||||
|
||||
public string ChatContent;
|
||||
|
||||
//public string RulesetName = "";
|
||||
|
||||
public RulesetPacket(RulesetClientInfo rulesetClientInfo) : base(rulesetClientInfo)
|
||||
{
|
||||
ClientInfo = rulesetClientInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Symcol.Core.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Networking
|
||||
{
|
||||
[Serializable]
|
||||
public class ScorePacket : Packet
|
||||
{
|
||||
public override int PacketSize => 2048;
|
||||
|
||||
public int Score;
|
||||
|
||||
public ScorePacket(ClientInfo clientInfo, int score) : base(clientInfo)
|
||||
{
|
||||
Score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||
{
|
||||
public class MultiplayerDropdownEnumOption<T> : MultiplayerOption
|
||||
where T : struct
|
||||
{
|
||||
public readonly Bindable<T> BindableEnum;
|
||||
|
||||
public MultiplayerDropdownEnumOption(Bindable<T> bindable, string name, int quadrant, bool sync = true) : base(name, quadrant, sync)
|
||||
{
|
||||
BindableEnum = bindable;
|
||||
|
||||
OptionContainer.Child = new BetterSettingsEnumDropdown<T>
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Bindable = bindable,
|
||||
};
|
||||
}
|
||||
|
||||
private class BetterSettingsEnumDropdown<T> : SettingsEnumDropdown<T>
|
||||
{
|
||||
protected override Drawable CreateControl() => new BetterOsuEnumDropdown<T>
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
|
||||
private class BetterOsuEnumDropdown<T> : OsuEnumDropdown<T>
|
||||
{
|
||||
public BetterOsuEnumDropdown()
|
||||
{
|
||||
Menu.MaxHeight = 160;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using System;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||
{
|
||||
public abstract class MultiplayerOption : Container
|
||||
{
|
||||
protected readonly SpriteText Title;
|
||||
|
||||
protected readonly Container OptionContainer;
|
||||
|
||||
public MultiplayerOption(string name, int quadrant, bool sync = true)
|
||||
{
|
||||
if (quadrant == 1 | quadrant == 3 | quadrant == 5 | quadrant == 7)
|
||||
{
|
||||
switch (quadrant)
|
||||
{
|
||||
case 1:
|
||||
quadrant = 0;
|
||||
break;
|
||||
case 3:
|
||||
quadrant = 1;
|
||||
break;
|
||||
case 5:
|
||||
quadrant = 2;
|
||||
break;
|
||||
case 7:
|
||||
quadrant = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
Anchor = Anchor.TopLeft;
|
||||
Origin = Anchor.TopLeft;
|
||||
Position = new Vector2(16, 4 + (64 * quadrant));
|
||||
}
|
||||
else if (quadrant == 2 | quadrant == 4 | quadrant == 6 | quadrant == 8)
|
||||
{
|
||||
switch (quadrant)
|
||||
{
|
||||
case 2:
|
||||
quadrant = 0;
|
||||
break;
|
||||
case 4:
|
||||
quadrant = 1;
|
||||
break;
|
||||
case 6:
|
||||
quadrant = 2;
|
||||
break;
|
||||
case 8:
|
||||
quadrant = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopLeft;
|
||||
Position = new Vector2(22, 4 + (64 * quadrant));
|
||||
}
|
||||
else
|
||||
throw new Exception("Globglogabgalab");
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Width = 0.49f;
|
||||
Height = 80;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Title = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
TextSize = 20,
|
||||
Text = name
|
||||
},
|
||||
OptionContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(-16, 18),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Framework.Graphics;
|
||||
using OpenTK;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Options
|
||||
{
|
||||
public class MultiplayerToggleOption : MultiplayerOption
|
||||
{
|
||||
public readonly Bindable<bool> BindableBool;
|
||||
|
||||
public MultiplayerToggleOption(Bindable<bool> bindable, string name, int quadrant, bool sync = true) : base(name, quadrant, sync)
|
||||
{
|
||||
BindableBool = bindable;
|
||||
|
||||
Child = new SettingsCheckbox
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Bindable = bindable,
|
||||
LabelText = " " + name,
|
||||
Position = new Vector2(-16, 18),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Users;
|
||||
using Symcol.Core.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class Chat : Container, IOnlineComponent
|
||||
{
|
||||
private readonly RulesetNetworkingClientHandler rulesetNetworkingClientHandler;
|
||||
|
||||
private string playerColorHex = SymcolSettingsSubsection.SymcolConfigManager.GetBindable<string>(SymcolSetting.PlayerColor);
|
||||
|
||||
private User user;
|
||||
|
||||
private readonly FillFlowContainer<ChatMessage> messageContainer;
|
||||
private readonly OsuTextBox textBox;
|
||||
|
||||
public Chat(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||
{
|
||||
this.rulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
|
||||
rulesetNetworkingClientHandler.OnPacketReceive += (packet) =>
|
||||
{
|
||||
if (packet is ChatPacket chatPacket)
|
||||
Add(chatPacket);
|
||||
if (rulesetNetworkingClientHandler.ClientType == ClientType.Host)
|
||||
rulesetNetworkingClientHandler.ShareWithOtherPeers(packet);
|
||||
};
|
||||
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Height = 0.46f;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.8f
|
||||
},
|
||||
new OsuScrollContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.9f,
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
messageContainer = new FillFlowContainer<ChatMessage>
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
},
|
||||
textBox = new OsuTextBox
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.98f,
|
||||
Height = 36,
|
||||
Position = new Vector2(0, -12),
|
||||
Colour = Color4.White,
|
||||
Text = "Type here!"
|
||||
}
|
||||
};
|
||||
|
||||
textBox.OnCommit += (s, r) =>
|
||||
{
|
||||
AddMessage(textBox.Text);
|
||||
textBox.Text = "";
|
||||
};
|
||||
}
|
||||
|
||||
public void Add(ChatPacket packet)
|
||||
{
|
||||
ChatMessage message = new ChatMessage(packet);
|
||||
messageContainer.Add(message);
|
||||
}
|
||||
|
||||
public void AddMessage(string message)
|
||||
{
|
||||
if (message == "" | message == " ")
|
||||
return;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
OsuColour.FromHex(playerColorHex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
playerColorHex = "#ffffff";
|
||||
}
|
||||
|
||||
ChatPacket packet = new ChatPacket(rulesetNetworkingClientHandler.ClientInfo)
|
||||
{
|
||||
Author = user.Username,
|
||||
AuthorColor = playerColorHex,
|
||||
Message = message,
|
||||
};
|
||||
|
||||
rulesetNetworkingClientHandler.SendToHost(packet);
|
||||
rulesetNetworkingClientHandler.SendToInMatchClients(packet);
|
||||
Add(packet);
|
||||
}
|
||||
else
|
||||
Logger.Log("You must be logged in to message!", LoggingTarget.Network, LogLevel.Error);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(APIAccess api)
|
||||
{
|
||||
api.Register(this);
|
||||
}
|
||||
|
||||
public void APIStateChanged(APIAccess api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
default:
|
||||
user = null;
|
||||
break;
|
||||
case APIState.Online:
|
||||
user = api.LocalUser.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class ChatMessage : Container
|
||||
{
|
||||
public ChatMessage(ChatPacket packet)
|
||||
{
|
||||
Anchor = Anchor.TopLeft;
|
||||
Origin = Anchor.TopLeft;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Colour = OsuColour.FromHex(packet.AuthorColor),
|
||||
TextSize = 24,
|
||||
Text = packet.Author + ":"
|
||||
},
|
||||
new OsuTextFlowContainer(t => { t.TextSize = 24; })
|
||||
{
|
||||
Position = new OpenTK.Vector2(140, 0),
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Text = packet.Message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Users;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class MatchPlayer : ClickableContainer, IHasContextMenu
|
||||
{
|
||||
public readonly RulesetClientInfo ClientInfo;
|
||||
|
||||
private readonly Box dim;
|
||||
|
||||
private readonly DrawableFlag countryFlag;
|
||||
private readonly UserCoverBackground profileBackground;
|
||||
private readonly UpdateableAvatar profilePicture;
|
||||
|
||||
public MatchPlayer(RulesetClientInfo clientInfo)
|
||||
{
|
||||
ClientInfo = clientInfo;
|
||||
|
||||
Alpha = 0;
|
||||
Masking = true;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 40f;
|
||||
CornerRadius = 10;
|
||||
|
||||
Country country = new Country
|
||||
{
|
||||
FullName = ClientInfo.UserCountry,
|
||||
FlagName = ClientInfo.CountryFlagName,
|
||||
};
|
||||
|
||||
User user = new User
|
||||
{
|
||||
Username = ClientInfo.Username,
|
||||
Id = ClientInfo.UserID,
|
||||
Country = country,
|
||||
AvatarUrl = ClientInfo.UserPic,
|
||||
CoverUrl = ClientInfo.UserBackground,
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
profileBackground = new UserCoverBackground(user)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
OnLoadComplete = d => d.FadeInFromZero(200),
|
||||
},
|
||||
dim = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.8f
|
||||
},
|
||||
profilePicture = new UpdateableAvatar
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(Height * 0.8f),
|
||||
Position = new Vector2(6, 0),
|
||||
User = user,
|
||||
Masking = true,
|
||||
CornerRadius = 6,
|
||||
},
|
||||
countryFlag = new DrawableFlag(country)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(Height * 0.9f, (Height * 0.9f) * 0.66f),
|
||||
Position = new Vector2(-10, 0)
|
||||
},
|
||||
new SpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Position = new Vector2(Height * 1.1f, 0),
|
||||
TextSize = Height * 0.9f,
|
||||
Text = user.Username
|
||||
}
|
||||
};
|
||||
|
||||
Action = () =>
|
||||
{
|
||||
Process.Start("https://osu.ppy.sh/users/" + user.Id);
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
dim.FadeTo(0.6f, 200);
|
||||
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
base.OnHoverLost(state);
|
||||
|
||||
dim.FadeTo(0.8f, 200);
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("View Profile", MenuItemType.Standard, () => { }),
|
||||
new OsuMenuItem("Promote to Host", MenuItemType.Highlighted, () => { }),
|
||||
new OsuMenuItem("Kick", MenuItemType.Destructive, () => { }),
|
||||
new OsuMenuItem("Ban", MenuItemType.Destructive, () => { }),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using OpenTK;
|
||||
using Symcol.Core.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class MatchPlayerList : Container
|
||||
{
|
||||
private readonly RulesetNetworkingClientHandler rulesetNetworkingClientHandler;
|
||||
|
||||
public readonly List<MatchPlayer> MatchPlayers = new List<MatchPlayer>();
|
||||
|
||||
public readonly FillFlowContainer MatchPlayersContianer;
|
||||
|
||||
public MatchPlayerList(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||
{
|
||||
this.rulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 16;
|
||||
Anchor = Anchor.TopLeft;
|
||||
Origin = Anchor.TopLeft;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Width = 0.49f;
|
||||
Height = 0.45f;
|
||||
Position = new Vector2(10);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.8f)
|
||||
},
|
||||
MatchPlayersContianer = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.98f,
|
||||
Height = 0.96f
|
||||
}
|
||||
};
|
||||
|
||||
rulesetNetworkingClientHandler.OnReceivePlayerList += (players) =>
|
||||
{
|
||||
restart:
|
||||
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||
foreach (ClientInfo clientInfo in players)
|
||||
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||
if (rulesetClientInfo.IP + rulesetClientInfo.Port != matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||
{
|
||||
Add(rulesetClientInfo);
|
||||
players.Remove(clientInfo);
|
||||
goto restart;
|
||||
}
|
||||
};
|
||||
rulesetNetworkingClientHandler.RequestPlayerList();
|
||||
|
||||
rulesetNetworkingClientHandler.OnClientJoin += (clientInfo) =>
|
||||
{
|
||||
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||
if (rulesetClientInfo.IP + rulesetClientInfo.Port != matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||
{
|
||||
Add(rulesetClientInfo);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
rulesetNetworkingClientHandler.OnClientDisconnect += (clientInfo) =>
|
||||
{
|
||||
foreach (MatchPlayer matchPlayer in MatchPlayers)
|
||||
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||
if (rulesetClientInfo.IP + rulesetClientInfo.Port == matchPlayer.ClientInfo.IP + matchPlayer.ClientInfo.Port)
|
||||
{
|
||||
Remove(matchPlayer);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Add(RulesetClientInfo clientInfo)
|
||||
{
|
||||
MatchPlayer matchPlayer = new MatchPlayer(clientInfo);
|
||||
|
||||
Add(matchPlayer);
|
||||
}
|
||||
|
||||
public void Add(MatchPlayer matchPlayer)
|
||||
{
|
||||
MatchPlayers.Add(matchPlayer);
|
||||
MatchPlayersContianer.Add(matchPlayer);
|
||||
matchPlayer.FadeInFromZero(200);
|
||||
}
|
||||
|
||||
public void Remove(MatchPlayer matchPlayer)
|
||||
{
|
||||
MatchPlayers.Remove(matchPlayer);
|
||||
matchPlayer.FadeOutFromOne(200)
|
||||
.Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Symcol.Pieces;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Options;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class MatchTools : Container
|
||||
{
|
||||
public readonly Bindable<MatchScreenMode> Mode = new Bindable<MatchScreenMode>() { Default = MatchScreenMode.MapDetails };
|
||||
|
||||
public readonly Bindable<MatchGamemode> GameMode = new Bindable<MatchGamemode>() { Default = MatchGamemode.HeadToHead };
|
||||
|
||||
public readonly OsuTabControl<MatchScreenMode> TabControl;
|
||||
|
||||
public readonly Container SelectedContent;
|
||||
|
||||
public readonly Container MapDetails;
|
||||
|
||||
public Container RulesetSettings;
|
||||
|
||||
public readonly Container SoundBoard;
|
||||
|
||||
private WorkingBeatmap selectedBeatmap;
|
||||
|
||||
private int selectedBeatmapSetID;
|
||||
|
||||
public MatchTools()
|
||||
{
|
||||
Masking = true;
|
||||
CornerRadius = 16;
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Width = 0.49f;
|
||||
Height = 0.45f;
|
||||
Position = new Vector2(-10, 10);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black.Opacity(0.8f)
|
||||
},
|
||||
TabControl = new OsuTabControl<MatchScreenMode>
|
||||
{
|
||||
Position = new Vector2(72, 0),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.08f,
|
||||
Width = 0.8f
|
||||
},
|
||||
SelectedContent = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.92f
|
||||
}
|
||||
};
|
||||
TabControl.Current.Value = MatchScreenMode.MapDetails;
|
||||
|
||||
Mode.ValueChanged += (value) =>
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case MatchScreenMode.MapDetails:
|
||||
if (selectedBeatmap != null)
|
||||
SelectedContent.Child = new MapDetailsSection(selectedBeatmap);
|
||||
else if (selectedBeatmapSetID != 0)
|
||||
SelectedContent.Child = new MapDetailsSection(selectedBeatmapSetID);
|
||||
else
|
||||
SelectedContent.Child = new MapDetailsSection(true);
|
||||
break;
|
||||
case MatchScreenMode.MatchSettings:
|
||||
SelectedContent.Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MultiplayerDropdownEnumOption<MatchGamemode>(GameMode, "Match Gamemode", 1)
|
||||
}
|
||||
};
|
||||
break;
|
||||
case MatchScreenMode.SoundBoard:
|
||||
SelectedContent.Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new HitSoundBoard
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
ButtonSize = 80
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
};
|
||||
Mode.BindTo(TabControl.Current);
|
||||
}
|
||||
|
||||
public void MapChange(WorkingBeatmap workingBeatmap)
|
||||
{
|
||||
if (workingBeatmap == null)
|
||||
{
|
||||
MapChange(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBeatmap = workingBeatmap;
|
||||
selectedBeatmapSetID = (int)workingBeatmap.BeatmapSetInfo.OnlineBeatmapSetID;
|
||||
|
||||
if (Mode.Value == MatchScreenMode.MapDetails)
|
||||
SelectedContent.Child = new MapDetailsSection(selectedBeatmap);
|
||||
}
|
||||
|
||||
public void MapChange(int onlineBeatmapSetID)
|
||||
{
|
||||
selectedBeatmap = null;
|
||||
selectedBeatmapSetID = onlineBeatmapSetID;
|
||||
|
||||
if (Mode.Value == MatchScreenMode.MapDetails)
|
||||
{
|
||||
if (selectedBeatmapSetID != 0 && selectedBeatmapSetID != -1)
|
||||
SelectedContent.Child = new MapDetailsSection(selectedBeatmapSetID);
|
||||
else
|
||||
SelectedContent.Child = new MapDetailsSection(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MapDetailsSection : ClickableContainer
|
||||
{
|
||||
private Sprite beatmapBG;
|
||||
private SpriteText name;
|
||||
private SpriteText artist;
|
||||
private SpriteText difficulty;
|
||||
private SpriteText time;
|
||||
|
||||
private Box dim;
|
||||
|
||||
public MapDetailsSection(WorkingBeatmap workingBeatmap)
|
||||
{
|
||||
draw();
|
||||
|
||||
HitObject lastObject = workingBeatmap.Beatmap.HitObjects.LastOrDefault();
|
||||
double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
|
||||
|
||||
beatmapBG.Texture = workingBeatmap.Background;
|
||||
name.Text = workingBeatmap.BeatmapSetInfo.Metadata.Title;
|
||||
artist.Text = "By: " + workingBeatmap.BeatmapSetInfo.Metadata.Artist;
|
||||
difficulty.Text = workingBeatmap.BeatmapInfo.Version + " (" + Math.Round(workingBeatmap.BeatmapInfo.StarDifficulty, 2) + " stars) mapped by " + workingBeatmap.BeatmapInfo.Metadata.AuthorString;
|
||||
time.Text = getBPMRange(workingBeatmap.Beatmap) + " bpm for " + TimeSpan.FromMilliseconds(endTime - workingBeatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss");
|
||||
|
||||
BorderColour = getColour(workingBeatmap.BeatmapInfo);
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Radius = 16,
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = getColour(workingBeatmap.BeatmapInfo).Opacity(0.2f)
|
||||
};
|
||||
Action = () => Process.Start("https://osu.ppy.sh/beatmapsets/" + workingBeatmap.BeatmapSetInfo.OnlineBeatmapSetID);
|
||||
}
|
||||
|
||||
public MapDetailsSection(int onlineBeatmapSetID)
|
||||
{
|
||||
draw();
|
||||
name.Text = "Missing Map!";
|
||||
artist.Text = "Click to open in Browser";
|
||||
Action = () => Process.Start("https://osu.ppy.sh/beatmapsets/" + onlineBeatmapSetID);
|
||||
}
|
||||
|
||||
public MapDetailsSection(bool invalid)
|
||||
{
|
||||
draw();
|
||||
name.Text = "Invalid / No Map Selected!";
|
||||
artist.Text = "Don't hit start, weird things might happen";
|
||||
Action = () => Process.Start("https://osu.ppy.sh/home");
|
||||
}
|
||||
|
||||
private void draw()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Width = 0.95f;
|
||||
Height = 0.9f;
|
||||
|
||||
Masking = true;
|
||||
BorderColour = Color4.LightBlue;
|
||||
BorderThickness = 4;
|
||||
CornerRadius = 10;
|
||||
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Radius = 16,
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.LightBlue.Opacity(0.2f)
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
beatmapBG = new Sprite
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
},
|
||||
dim = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.6f
|
||||
},
|
||||
name = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(10, 0),
|
||||
Font = @"Exo2.0-SemiBoldItalic",
|
||||
TextSize = 40
|
||||
},
|
||||
artist = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(10, 38),
|
||||
Font = @"Exo2.0-MediumItalic",
|
||||
TextSize = 24
|
||||
},
|
||||
difficulty = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(10, 64),
|
||||
Font = "Exo2.0-Bold",
|
||||
TextSize = 16
|
||||
},
|
||||
time = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(10, 84),
|
||||
TextSize = 16
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
dim.FadeTo(0.4f, 200);
|
||||
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
base.OnHoverLost(state);
|
||||
|
||||
dim.FadeTo(0.6f, 200);
|
||||
}
|
||||
|
||||
//"Borrowed" stuff
|
||||
private string getBPMRange(Beatmap beatmap)
|
||||
{
|
||||
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
|
||||
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
|
||||
|
||||
if (Precision.AlmostEquals(bpmMin, bpmMax))
|
||||
return $"{bpmMin:0}";
|
||||
|
||||
return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})";
|
||||
}
|
||||
|
||||
private enum DifficultyRating
|
||||
{
|
||||
Easy,
|
||||
Normal,
|
||||
Hard,
|
||||
Insane,
|
||||
Expert,
|
||||
ExpertPlus
|
||||
}
|
||||
|
||||
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
|
||||
{
|
||||
if (beatmap == null)
|
||||
throw new ArgumentNullException(nameof(beatmap));
|
||||
|
||||
var rating = beatmap.StarDifficulty;
|
||||
|
||||
if (rating < 1.5) return DifficultyRating.Easy;
|
||||
if (rating < 2.25) return DifficultyRating.Normal;
|
||||
if (rating < 3.75) return DifficultyRating.Hard;
|
||||
if (rating < 5.25) return DifficultyRating.Insane;
|
||||
if (rating < 6.75) return DifficultyRating.Expert;
|
||||
return DifficultyRating.ExpertPlus;
|
||||
}
|
||||
|
||||
private Color4 getColour(BeatmapInfo beatmap)
|
||||
{
|
||||
OsuColour palette = new OsuColour();
|
||||
switch (getDifficultyRating(beatmap))
|
||||
{
|
||||
case DifficultyRating.Easy:
|
||||
return palette.Green;
|
||||
default:
|
||||
case DifficultyRating.Normal:
|
||||
return palette.Blue;
|
||||
case DifficultyRating.Hard:
|
||||
return palette.Yellow;
|
||||
case DifficultyRating.Insane:
|
||||
return palette.Pink;
|
||||
case DifficultyRating.Expert:
|
||||
return palette.Purple;
|
||||
case DifficultyRating.ExpertPlus:
|
||||
return palette.Gray0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MatchGamemode
|
||||
{
|
||||
[System.ComponentModel.Description("Head to Head")]
|
||||
HeadToHead,
|
||||
[System.ComponentModel.Description("Head to Head with Live Spectator")]
|
||||
HeadToHeadSpectator,
|
||||
[System.ComponentModel.Description("Team Versus")]
|
||||
TeamVS,
|
||||
[System.ComponentModel.Description("TAG4")]
|
||||
TAG4,
|
||||
[System.ComponentModel.Description("Team TAG4")]
|
||||
TeamTAG4,
|
||||
[System.ComponentModel.Description("Tourny Mode")]
|
||||
Tournement,
|
||||
}
|
||||
|
||||
public enum MatchScreenMode
|
||||
{
|
||||
[System.ComponentModel.Description("Map Details")]
|
||||
MapDetails,
|
||||
[System.ComponentModel.Description("Match Settings")]
|
||||
MatchSettings,
|
||||
[System.ComponentModel.Description("Ruleset Settings")]
|
||||
RulesetSettings,
|
||||
[System.ComponentModel.Description("Sound Board")]
|
||||
SoundBoard
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using Symcol.Core.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class MultiplayerScoreboard : Container
|
||||
{
|
||||
public readonly Container<MultiplayerScoreboardItem> ScoreboardItems;
|
||||
|
||||
private readonly RulesetNetworkingClientHandler rulesetNetworkingClientHandler;
|
||||
|
||||
private readonly ScoreProcessor scoreProcessor;
|
||||
|
||||
private double updateScoreTime = 0;
|
||||
|
||||
public MultiplayerScoreboard(RulesetNetworkingClientHandler rulesetNetworkingClientHandler, List<ClientInfo> playerList, ScoreProcessor scoreProcessor)
|
||||
{
|
||||
this.rulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
this.scoreProcessor = scoreProcessor;
|
||||
|
||||
AlwaysPresent = true;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Width = 120;
|
||||
|
||||
Position = new Vector2(0, -200);
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.TopLeft;
|
||||
|
||||
Child = ScoreboardItems = new Container<MultiplayerScoreboardItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
|
||||
int i = 1;
|
||||
foreach (ClientInfo clientInfo in playerList)
|
||||
{
|
||||
if (clientInfo is RulesetClientInfo rulesetClientInfo)
|
||||
{
|
||||
ScoreboardItems.Add(new MultiplayerScoreboardItem(rulesetClientInfo, i) { });
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
rulesetNetworkingClientHandler.OnPacketReceive += (Packet packet) =>
|
||||
{
|
||||
if (packet is ScorePacket scorePacket)
|
||||
{
|
||||
rulesetNetworkingClientHandler.ShareWithOtherPeers(scorePacket);
|
||||
foreach (MultiplayerScoreboardItem item in ScoreboardItems)
|
||||
if (scorePacket.ClientInfo == item.ClientInfo)
|
||||
item.Score = scorePacket.Score;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (Time.Current >= updateScoreTime)
|
||||
{
|
||||
updateScoreTime = Time.Current + 500;
|
||||
foreach (MultiplayerScoreboardItem item in ScoreboardItems)
|
||||
if (rulesetNetworkingClientHandler.ClientInfo == item.ClientInfo)
|
||||
item.Score = (int)scoreProcessor.TotalScore.Value;
|
||||
|
||||
rulesetNetworkingClientHandler.SendToHost(new ScorePacket(rulesetNetworkingClientHandler.ClientInfo, (int)scoreProcessor.TotalScore.Value));
|
||||
rulesetNetworkingClientHandler.SendToInGameClients(new ScorePacket(rulesetNetworkingClientHandler.ClientInfo, (int)scoreProcessor.TotalScore.Value));
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
{
|
||||
if (args.Key == Key.Tab)
|
||||
{
|
||||
if (Alpha > 0)
|
||||
this.FadeOut(100);
|
||||
else
|
||||
this.FadeIn(100);
|
||||
}
|
||||
|
||||
return base.OnKeyDown(state, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Pieces
|
||||
{
|
||||
public class MultiplayerScoreboardItem : Container
|
||||
{
|
||||
public int Score
|
||||
{
|
||||
get { return score; }
|
||||
set
|
||||
{
|
||||
if (value != score)
|
||||
{
|
||||
score = value;
|
||||
scoreText.Text = value.ToString();
|
||||
|
||||
foreach(MultiplayerScoreboardItem item in itemList)
|
||||
if (value > item.Score && Place > item.Place)
|
||||
{
|
||||
Place = item.Place;
|
||||
foreach (MultiplayerScoreboardItem i in itemList)
|
||||
if (i.Place < Place)
|
||||
i.Place -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Place
|
||||
{
|
||||
get { return place; }
|
||||
set
|
||||
{
|
||||
if (Place != place)
|
||||
{
|
||||
place = value;
|
||||
this.MoveTo(new Vector2(0, (-height - 8) * (value - 1)), 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int place = 0;
|
||||
|
||||
private int score = 0;
|
||||
|
||||
private const int height = 60;
|
||||
|
||||
public readonly RulesetClientInfo ClientInfo;
|
||||
|
||||
private readonly SpriteText scoreText;
|
||||
|
||||
private static List<MultiplayerScoreboardItem> itemList = new List<MultiplayerScoreboardItem>();
|
||||
|
||||
public MultiplayerScoreboardItem(RulesetClientInfo clientInfo, int place)
|
||||
{
|
||||
ClientInfo = clientInfo;
|
||||
this.place = place;
|
||||
|
||||
itemList.Add(this);
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = height;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 8;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.8f,
|
||||
},
|
||||
new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Position = new Vector2(4),
|
||||
Text = clientInfo.Username
|
||||
},
|
||||
scoreText = new SpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Position = new Vector2(-4),
|
||||
Text = Score.ToString()
|
||||
}
|
||||
};
|
||||
|
||||
this.MoveTo(new Vector2(0, (-height - 8) * (Place - 1)), 200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
itemList.Remove(this);
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using System;
|
||||
using osu.Game.Screens;
|
||||
using osu.Framework.Screens;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||
{
|
||||
public class MatchSongSelect : SongSelect
|
||||
{
|
||||
public WorkingBeatmap SelectedMap;
|
||||
|
||||
private bool exiting;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => null;
|
||||
|
||||
public Action Action;
|
||||
|
||||
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||
|
||||
public MatchSongSelect(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||
{
|
||||
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
}
|
||||
|
||||
protected override void OnEntering(Screen last)
|
||||
{
|
||||
Add(RulesetNetworkingClientHandler);
|
||||
base.OnEntering(last);
|
||||
}
|
||||
|
||||
protected override bool OnSelectionFinalised()
|
||||
{
|
||||
if (!exiting)
|
||||
{
|
||||
RulesetNetworkingClientHandler.OnMapChange?.Invoke(null);
|
||||
SelectedMap = Beatmap.Value;
|
||||
Action();
|
||||
exiting = true;
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
Exit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using OpenTK;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using OpenTK.Input;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Pieces;
|
||||
using System.Collections.Generic;
|
||||
using Symcol.Core.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||
{
|
||||
public class MultiPlayer : ScreenWithBeatmapBackground, IProvideCursor
|
||||
{
|
||||
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
|
||||
|
||||
protected override float BackgroundParallaxAmount => 0.1f;
|
||||
|
||||
public override bool ShowOverlaysOnEnter => false;
|
||||
|
||||
public Action RestartRequested;
|
||||
|
||||
public override bool AllowBeatmapRulesetChange => false;
|
||||
|
||||
public bool HasFailed { get; private set; }
|
||||
|
||||
public bool AllowPause { get; set; } = false;
|
||||
public bool AllowLeadIn { get; set; } = true;
|
||||
public bool AllowResults { get; set; } = true;
|
||||
|
||||
public int RestartCount;
|
||||
|
||||
public CursorContainer Cursor => RulesetContainer.Cursor;
|
||||
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
|
||||
|
||||
private IAdjustableClock sourceClock;
|
||||
private DecoupleableInterpolatingFramedClock adjustableClock;
|
||||
|
||||
private RulesetInfo ruleset;
|
||||
|
||||
private APIAccess api;
|
||||
|
||||
private ScoreProcessor scoreProcessor;
|
||||
protected RulesetContainer RulesetContainer;
|
||||
|
||||
#region User Settings
|
||||
|
||||
private Bindable<double> dimLevel;
|
||||
private Bindable<double> blurLevel;
|
||||
private Bindable<bool> showStoryboard;
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
private Bindable<double> userAudioOffset;
|
||||
|
||||
private SampleChannel sampleRestart;
|
||||
|
||||
#endregion
|
||||
|
||||
private Container storyboardContainer;
|
||||
private DrawableStoryboard storyboard;
|
||||
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
private MultiplayerScoreboard scoreboard;
|
||||
|
||||
private bool loadedSuccessfully => RulesetContainer?.Objects.Any() == true;
|
||||
|
||||
private readonly List<ClientInfo> playerList;
|
||||
|
||||
public MultiPlayer(RulesetNetworkingClientHandler rulesetNetworkingClientHandler, List<ClientInfo> playerList)//, WorkingBeatmap beatmap = null)
|
||||
{
|
||||
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
RulesetNetworkingClientHandler.OnAbort = () => Exit();
|
||||
RulesetNetworkingClientHandler.StartGame = () => start();
|
||||
this.playerList = playerList;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
|
||||
{
|
||||
this.api = api;
|
||||
|
||||
dimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
|
||||
blurLevel = config.GetBindable<double>(OsuSetting.BlurLevel);
|
||||
showStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||
|
||||
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||
|
||||
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
|
||||
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||
|
||||
WorkingBeatmap working = Beatmap.Value;
|
||||
Beatmap beatmap;
|
||||
|
||||
try
|
||||
{
|
||||
beatmap = working.Beatmap;
|
||||
|
||||
if (beatmap == null)
|
||||
throw new InvalidOperationException("Beatmap was not loaded");
|
||||
|
||||
ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
try
|
||||
{
|
||||
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
|
||||
}
|
||||
catch (BeatmapInvalidForRulesetException)
|
||||
{
|
||||
// we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset
|
||||
// let's try again forcing the beatmap's ruleset.
|
||||
ruleset = beatmap.BeatmapInfo.Ruleset;
|
||||
rulesetInstance = ruleset.CreateInstance();
|
||||
RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true);
|
||||
}
|
||||
|
||||
if (!RulesetContainer.Objects.Any())
|
||||
throw new InvalidOperationException("Beatmap contains no hit objects!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Could not load beatmap sucessfully!");
|
||||
|
||||
//couldn't load, hard abort!
|
||||
Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
||||
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||
|
||||
var firstObjectTime = RulesetContainer.Objects.First().StartTime;
|
||||
adjustableClock.Seek(AllowLeadIn
|
||||
? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))
|
||||
: firstObjectTime);
|
||||
|
||||
adjustableClock.ProcessFrame();
|
||||
|
||||
// the final usable gameplay clock with user-set offsets applied.
|
||||
var offsetClock = new FramedOffsetClock(adjustableClock);
|
||||
|
||||
userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
|
||||
userAudioOffset.TriggerChange();
|
||||
|
||||
scoreProcessor = RulesetContainer.CreateScoreProcessor();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Clock = offsetClock,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
storyboardContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
RulesetContainer,
|
||||
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
|
||||
{
|
||||
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
|
||||
ProcessCustomClock = false,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
ProcessCustomClock = false,
|
||||
Breaks = beatmap.Breaks
|
||||
},
|
||||
scoreboard = new MultiplayerScoreboard(RulesetNetworkingClientHandler, playerList, scoreProcessor)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (showStoryboard)
|
||||
initializeStoryboard(false);
|
||||
|
||||
// Bind ScoreProcessor to ourselves
|
||||
scoreProcessor.AllJudged += onCompletion;
|
||||
scoreProcessor.Failed += onFail;
|
||||
|
||||
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
mod.ApplyToScoreProcessor(scoreProcessor);
|
||||
}
|
||||
|
||||
private void applyRateFromMods()
|
||||
{
|
||||
if (sourceClock == null) return;
|
||||
|
||||
sourceClock.Rate = 1;
|
||||
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
|
||||
mod.ApplyToClock(sourceClock);
|
||||
}
|
||||
|
||||
private void initializeStoryboard(bool asyncLoad)
|
||||
{
|
||||
if (storyboardContainer == null)
|
||||
return;
|
||||
|
||||
var beatmap = Beatmap.Value;
|
||||
|
||||
storyboard = beatmap.Storyboard.CreateDrawable();
|
||||
storyboard.Masking = true;
|
||||
|
||||
if (asyncLoad)
|
||||
LoadComponentAsync(storyboard, storyboardContainer.Add);
|
||||
else
|
||||
storyboardContainer.Add(storyboard);
|
||||
}
|
||||
|
||||
private ScheduledDelegate onCompletionEvent;
|
||||
|
||||
private void onCompletion()
|
||||
{
|
||||
// Only show the completion screen if the player hasn't failed
|
||||
if (scoreProcessor.HasFailed || onCompletionEvent != null)
|
||||
return;
|
||||
|
||||
ValidForResume = false;
|
||||
|
||||
if (!AllowResults) return;
|
||||
|
||||
using (BeginDelayedSequence(1000))
|
||||
{
|
||||
onCompletionEvent = Schedule(delegate
|
||||
{
|
||||
if (!IsCurrentScreen) return;
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Beatmap = Beatmap.Value.BeatmapInfo,
|
||||
Ruleset = ruleset
|
||||
};
|
||||
scoreProcessor.PopulateScore(score);
|
||||
score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value;
|
||||
Push(new Results(score));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool onFail()
|
||||
{
|
||||
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
||||
return false;
|
||||
|
||||
HasFailed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnEntering(Screen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
Add(RulesetNetworkingClientHandler);
|
||||
|
||||
if (!loadedSuccessfully)
|
||||
return;
|
||||
|
||||
Content.Alpha = 0;
|
||||
Content
|
||||
.ScaleTo(0.7f)
|
||||
.ScaleTo(1, 750, Easing.OutQuint)
|
||||
.Delay(250)
|
||||
.FadeIn(250);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
sourceClock.Reset();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
adjustableClock.ChangeSource(sourceClock);
|
||||
applyRateFromMods();
|
||||
|
||||
this.Delay(750).Schedule(() =>
|
||||
{
|
||||
Logger.Log("Client finnished loading", LoggingTarget.Network, LogLevel.Verbose);
|
||||
RulesetNetworkingClientHandler.GameLoaded();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void start()
|
||||
{
|
||||
adjustableClock.Start();
|
||||
}
|
||||
|
||||
protected override void OnSuspending(Screen next)
|
||||
{
|
||||
fadeOut();
|
||||
base.OnSuspending(next);
|
||||
}
|
||||
|
||||
protected override bool OnExiting(Screen next)
|
||||
{
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
fadeOut();
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
||||
protected override void UpdateBackgroundElements()
|
||||
{
|
||||
if (!IsCurrentScreen) return;
|
||||
|
||||
base.UpdateBackgroundElements();
|
||||
|
||||
if (ShowStoryboard && storyboard == null)
|
||||
initializeStoryboard(true);
|
||||
|
||||
var beatmap = Beatmap.Value;
|
||||
var storyboardVisible = ShowStoryboard && beatmap.Storyboard.HasDrawable;
|
||||
|
||||
storyboardContainer?
|
||||
.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint)
|
||||
.FadeTo(storyboardVisible && BackgroundOpacity > 0 ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
|
||||
|
||||
if (storyboardVisible && beatmap.Storyboard.ReplacesBackground)
|
||||
Background?.FadeTo(0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void fadeOut()
|
||||
{
|
||||
const float fade_out_duration = 250;
|
||||
|
||||
RulesetContainer?.FadeOut(fade_out_duration);
|
||||
Content.FadeOut(fade_out_duration);
|
||||
|
||||
hudOverlay?.ScaleTo(0.7f, fade_out_duration * 3, Easing.In);
|
||||
|
||||
Background?.FadeTo(1f, fade_out_duration);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
{
|
||||
if (args.Key == Key.Escape)
|
||||
BackOut();
|
||||
|
||||
return base.OnKeyDown(state, args);
|
||||
}
|
||||
|
||||
public void BackOut()
|
||||
{
|
||||
RulesetNetworkingClientHandler.AbortGame();
|
||||
RulesetNetworkingClientHandler.OnAbort();
|
||||
}
|
||||
|
||||
protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Screens;
|
||||
using System;
|
||||
using osu.Framework.Screens;
|
||||
using System.Collections.Generic;
|
||||
using Symcol.Core.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||
{
|
||||
public abstract class RulesetLobbyScreen : OsuScreen
|
||||
{
|
||||
public abstract string RulesetName { get; }
|
||||
|
||||
public abstract RulesetMatchScreen MatchScreen { get; }
|
||||
|
||||
public RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||
|
||||
public readonly SettingsButton HostGameButton;
|
||||
public readonly SettingsButton DirectConnectButton;
|
||||
public readonly SettingsButton JoinGameButton;
|
||||
|
||||
public readonly Container NewGame;
|
||||
protected readonly TextBox HostIP;
|
||||
protected readonly TextBox HostPort;
|
||||
//protected readonly TextBox PublicIp;
|
||||
protected readonly TextBox LocalIp;
|
||||
|
||||
public readonly Container JoinIP;
|
||||
|
||||
public RulesetLobbyScreen()
|
||||
{
|
||||
AlwaysPresent = true;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
HostGameButton = new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.3f,
|
||||
Text = "Host Game",
|
||||
Action = HostGame
|
||||
},
|
||||
DirectConnectButton = new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.3f,
|
||||
Text = "Direct Connect",
|
||||
Action = DirectConnect
|
||||
},
|
||||
JoinGameButton = new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.3f,
|
||||
Text = "Join Game"
|
||||
},
|
||||
NewGame = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
Size = new Vector2(400, 300),
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Blue,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.9f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
},
|
||||
HostIP = new TextBox
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
Text = "Host IP Address"
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.9f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
},
|
||||
HostPort = new TextBox
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
Text = "25570"
|
||||
},
|
||||
/*
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.9f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Position = new Vector2(0, 22),
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
},
|
||||
PublicIp = new TextBox
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Position = new Vector2(0, 22),
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
Text = "You're Public IP Address"
|
||||
},
|
||||
*/
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.9f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Position = new Vector2(0, 44),
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
},
|
||||
LocalIp = new TextBox
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Position = new Vector2(0, 44),
|
||||
Width = 0.48f,
|
||||
Height = 20,
|
||||
Text = "You're Local IP Address"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnEntering(Screen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
MakeCurrent();
|
||||
}
|
||||
|
||||
protected override void OnResuming(Screen last)
|
||||
{
|
||||
base.OnResuming(last);
|
||||
MakeCurrent();
|
||||
}
|
||||
|
||||
protected override bool OnExiting(Screen next)
|
||||
{
|
||||
if (RulesetNetworkingClientHandler != null)
|
||||
{
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
RulesetNetworkingClientHandler.Dispose();
|
||||
}
|
||||
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
||||
protected virtual void HostGame()
|
||||
{
|
||||
if (RulesetNetworkingClientHandler != null)
|
||||
{
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
RulesetNetworkingClientHandler.Dispose();
|
||||
}
|
||||
Add(RulesetNetworkingClientHandler = new RulesetNetworkingClientHandler(ClientType.Host, LocalIp.Text, Int32.Parse(HostPort.Text)));
|
||||
|
||||
List<ClientInfo> list = new List<ClientInfo>();
|
||||
list.Add(RulesetNetworkingClientHandler.RulesetClientInfo);
|
||||
|
||||
JoinMatch(list);
|
||||
}
|
||||
|
||||
protected virtual void DirectConnect()
|
||||
{
|
||||
if (RulesetNetworkingClientHandler != null)
|
||||
{
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
RulesetNetworkingClientHandler.Dispose();
|
||||
}
|
||||
Add(RulesetNetworkingClientHandler = new RulesetNetworkingClientHandler(ClientType.Peer, HostIP.Text, Int32.Parse(HostPort.Text), LocalIp.Text));
|
||||
|
||||
RulesetNetworkingClientHandler.OnConnectedToHost += (p) => JoinMatch(p);
|
||||
}
|
||||
|
||||
protected virtual void JoinMatch(List<ClientInfo> clientInfos)
|
||||
{
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
MakeCurrent();
|
||||
Push(MatchScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Screens;
|
||||
using Symcol.Core.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Networking;
|
||||
using Symcol.Rulesets.Core.Multiplayer.Pieces;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||
{
|
||||
public abstract class RulesetMatchScreen : OsuScreen
|
||||
{
|
||||
public readonly RulesetNetworkingClientHandler RulesetNetworkingClientHandler;
|
||||
|
||||
private readonly MatchPlayerList playerList;
|
||||
|
||||
private BeatmapManager beatmaps;
|
||||
|
||||
protected MatchTools MatchTools;
|
||||
|
||||
private readonly Chat chat;
|
||||
|
||||
public RulesetMatchScreen(RulesetNetworkingClientHandler rulesetNetworkingClientHandler)
|
||||
{
|
||||
RulesetNetworkingClientHandler = rulesetNetworkingClientHandler;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.35f,
|
||||
Text = "Leave",
|
||||
Action = () => Exit()
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.3f,
|
||||
Text = "Open Song Select",
|
||||
Action = () => openSongSelect()
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.35f,
|
||||
Text = "Start Match",
|
||||
Action = () => RulesetNetworkingClientHandler.StartLoadingGame()
|
||||
},
|
||||
playerList = new MatchPlayerList(RulesetNetworkingClientHandler),
|
||||
MatchTools = new MatchTools(),
|
||||
chat = new Chat(RulesetNetworkingClientHandler)
|
||||
};
|
||||
|
||||
RulesetNetworkingClientHandler.OnPacketReceive += (Packet packet) =>
|
||||
{
|
||||
if (packet is RulesetPacket rulesetPacket && rulesetPacket.OnlineBeatmapID != -1)
|
||||
foreach (BeatmapSetInfo beatmapSet in beatmaps.GetAllUsableBeatmapSets())
|
||||
if (beatmapSet.OnlineBeatmapSetID == rulesetPacket.OnlineBeatmapSetID)
|
||||
{
|
||||
foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps)
|
||||
if (beatmap.OnlineBeatmapID == rulesetPacket.OnlineBeatmapID)
|
||||
{
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value);
|
||||
Beatmap.Value.Track.Start();
|
||||
MatchTools.MapChange(Beatmap);
|
||||
RulesetNetworkingClientHandler.OnMapChange?.Invoke(Beatmap);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
MatchTools.MapChange(rulesetPacket.OnlineBeatmapSetID);
|
||||
};
|
||||
|
||||
RulesetNetworkingClientHandler.OnMapChange += (beatmap) => MatchTools.MapChange(beatmap);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
playerList.Add(RulesetNetworkingClientHandler.RulesetClientInfo);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapManager beatmaps)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
}
|
||||
|
||||
protected override void OnEntering(Screen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
MakeCurrent();
|
||||
Add(RulesetNetworkingClientHandler);
|
||||
RulesetNetworkingClientHandler.OnLoadGame = (i) => Load(i);
|
||||
}
|
||||
|
||||
protected override void OnResuming(Screen last)
|
||||
{
|
||||
base.OnResuming(last);
|
||||
MakeCurrent();
|
||||
if (RulesetNetworkingClientHandler != null)
|
||||
Add(RulesetNetworkingClientHandler);
|
||||
}
|
||||
|
||||
protected override void OnSuspending(Screen next)
|
||||
{
|
||||
base.OnSuspending(next);
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
}
|
||||
|
||||
protected override bool OnExiting(Screen next)
|
||||
{
|
||||
RulesetNetworkingClientHandler.Disconnect();
|
||||
Remove(RulesetNetworkingClientHandler);
|
||||
RulesetNetworkingClientHandler.Dispose();
|
||||
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
||||
protected virtual void Load(List<ClientInfo> playerList)
|
||||
{
|
||||
MakeCurrent();
|
||||
Push(new MultiPlayer(RulesetNetworkingClientHandler, playerList));
|
||||
}
|
||||
|
||||
private void openSongSelect()
|
||||
{
|
||||
MatchSongSelect songSelect = new MatchSongSelect(RulesetNetworkingClientHandler);
|
||||
MakeCurrent();
|
||||
Push(songSelect);
|
||||
songSelect.Action = () => RulesetNetworkingClientHandler.SetMap(songSelect.SelectedMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//osu.Game.Screens.Symcol.SymcolMenu
|
||||
//Symcol.Rulesets.Core.SymcolSettingsSubsection
|
||||
#define SymcolMods
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Symcol;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Multiplayer.Screens
|
||||
{
|
||||
public class RulesetMultiplayerSelection : OsuScreen
|
||||
{
|
||||
public static readonly FillFlowContainer<RulesetLobbyItem> LobbyItems = new FillFlowContainer<RulesetLobbyItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.85f,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
};
|
||||
|
||||
public RulesetMultiplayerSelection()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Add(LobbyItems);
|
||||
}
|
||||
|
||||
protected override void OnEntering(Screen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
foreach (RulesetLobbyItem item in LobbyItems)
|
||||
item.Action = () => Push(item.RulesetLobbyScreen);
|
||||
}
|
||||
|
||||
protected override bool OnExiting(Screen next)
|
||||
{
|
||||
Remove(LobbyItems);
|
||||
|
||||
#if SymcolMods
|
||||
SymcolSettingsSubsection.RulesetMultiplayerSelection = new RulesetMultiplayerSelection();
|
||||
|
||||
SymcolMenu.RulesetMultiplayerScreen = SymcolSettingsSubsection.RulesetMultiplayerSelection;
|
||||
#endif
|
||||
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RulesetLobbyItem : ClickableContainer
|
||||
{
|
||||
public abstract Texture Icon { get; }
|
||||
|
||||
public abstract string RulesetName { get; }
|
||||
|
||||
public virtual Texture Background { get; }
|
||||
|
||||
public abstract RulesetLobbyScreen RulesetLobbyScreen { get; }
|
||||
|
||||
public RulesetLobbyItem()
|
||||
{
|
||||
CornerRadius = 20;
|
||||
Masking = true;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 100;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fill,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = Background
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f,
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
Size = new Vector2(Height),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Texture = Icon
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Text = RulesetName,
|
||||
TextSize = 60,
|
||||
Position = new Vector2(-20, 0)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Symcol.Rulesets.Core")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Symcol.Rulesets.Core")]
|
||||
[assembly: AssemblyCopyright("Copyright © Shawdooow 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("552b5940-c647-4060-aa4d-61baac415c72")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,97 @@
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Skinning
|
||||
{
|
||||
public abstract class SkinElement : Container
|
||||
{
|
||||
private static string loadedSkin;
|
||||
|
||||
private static ResourceStore<byte[]> skinResources;
|
||||
private static TextureStore skinTextures;
|
||||
|
||||
/// <summary>
|
||||
/// Will attempt to get a skin element fron the skin, if no element is found return the default element
|
||||
/// </summary>
|
||||
/// <param name="stockTextures"></param>
|
||||
/// <param name="skin"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="storage"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture GetSkinElement(TextureStore stockTextures, Bindable<string> skin, string fileName, Storage storage)
|
||||
{
|
||||
Texture texture = null;
|
||||
|
||||
string fileNameHd = fileName + "@2x";
|
||||
|
||||
Storage skinStorage = storage.GetStorageForDirectory("Skins\\" + skin);
|
||||
|
||||
if (skin.Value == "default")
|
||||
{
|
||||
texture = stockTextures.Get(fileName + ".png");
|
||||
|
||||
if (texture == null)
|
||||
texture = stockTextures.Get(fileNameHd + ".png");
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
if (loadedSkin != skin.ToString())
|
||||
{
|
||||
loadedSkin = skin.ToString();
|
||||
skinResources = new ResourceStore<byte[]>(new StorageBackedResourceStore(skinStorage));
|
||||
skinTextures = new TextureStore(new RawTextureLoaderStore(skinResources));
|
||||
}
|
||||
|
||||
if (skinStorage.Exists(fileNameHd + ".png"))
|
||||
texture = skinTextures.Get(fileNameHd + ".png");
|
||||
else if (skinStorage.Exists(fileName + ".png"))
|
||||
{
|
||||
texture = skinTextures.Get(fileName + ".png");
|
||||
texture.ScaleAdjust = 1f;
|
||||
}
|
||||
else
|
||||
texture = stockTextures.Get(fileNameHd + ".png");
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will attempt to get a skin element from the skin, if no element is found return null
|
||||
/// </summary>
|
||||
/// <param name="skin"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="storage"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture GetElement(Bindable<string> skin, string fileName, Storage storage)
|
||||
{
|
||||
Texture texture = null;
|
||||
|
||||
string fileNameHd = fileName + "@2x";
|
||||
|
||||
Storage skinStorage = storage.GetStorageForDirectory("Skins\\" + skin);
|
||||
|
||||
if (loadedSkin != skin.ToString())
|
||||
{
|
||||
loadedSkin = skin.ToString();
|
||||
skinResources = new ResourceStore<byte[]>(new StorageBackedResourceStore(skinStorage));
|
||||
skinTextures = new TextureStore(new RawTextureLoaderStore(skinResources));
|
||||
}
|
||||
|
||||
if (skinStorage.Exists(fileNameHd + ".png"))
|
||||
texture = skinTextures.Get(fileNameHd + ".png");
|
||||
else if (skinStorage.Exists(fileName + ".png"))
|
||||
{
|
||||
texture = skinTextures.Get(fileName + ".png");
|
||||
texture.ScaleAdjust = 1f;
|
||||
}
|
||||
else
|
||||
texture = null;
|
||||
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
namespace Symcol.Rulesets.Core.Skinning
|
||||
{
|
||||
public class SkinConfigReader<T> : IniConfigManager<T>
|
||||
where T : struct
|
||||
{
|
||||
protected override string Filename => @"skin.ini";
|
||||
|
||||
public SkinConfigReader(Storage storage) : base(storage) { }
|
||||
|
||||
protected override bool PerformSave() { return false; }
|
||||
}
|
||||
|
||||
//wildly incomplete
|
||||
public enum ClassicIniParameters
|
||||
{
|
||||
Name,
|
||||
Author,
|
||||
CursorRotate,
|
||||
CursorExpand,
|
||||
CursorCentre
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user