1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-14 05:24:07 +08:00

Compare commits

..

32 Commits

6274 changed files with 119646 additions and 621022 deletions
-27
View File
@@ -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
View File
@@ -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
-10
View File
@@ -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
View File
@@ -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
-2
View File
@@ -1,2 +0,0 @@
github: ppy
custom: https://osu.ppy.sh/home/support
-75
View File
@@ -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
![export logs button](https://github.com/ppy/osu/assets/191335/cbfa5550-b7ed-4c5c-8dd0-8b87cc90ad9b)
### 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
-12
View File
@@ -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.
-46
View File
@@ -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
-228
View File
@@ -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 }}"
-187
View File
@@ -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
-88
View File
@@ -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
-196
View File
@@ -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.
-29
View File
@@ -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
View File
@@ -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/
+6
View File
@@ -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
View File
-1
View File
@@ -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
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
-6
View File
@@ -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>
-6
View File
@@ -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
View File
@@ -1 +0,0 @@
osu.Desktop
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
-8
View File
@@ -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>
-20
View File
@@ -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>
-16
View File
@@ -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>
-1
View File
@@ -1 +0,0 @@
osu.iOS
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
-6
View File
@@ -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>
-6
View File
@@ -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>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
-6
View File
@@ -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>
-6
View File
@@ -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>
-7
View File
@@ -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>
-20
View File
@@ -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>
+2
View File
@@ -0,0 +1,2 @@
language: csharp
solution: osu.sln
-6
View File
@@ -1,6 +0,0 @@
{
"recommendations": [
"editorconfig.editorconfig",
"ms-dotnettools.csdevkit"
]
}
+44 -109
View File
@@ -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"
}
]
}
}
+3
View File
@@ -0,0 +1,3 @@
// Place your settings in this file to overwrite default and user settings.
{
}
+43 -81
View File
@@ -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"
}
]
+36
View File
@@ -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.
-94
View File
@@ -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!
-27
View File
@@ -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.
-112
View File
@@ -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
-56
View File
@@ -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>
-3
View File
@@ -1,3 +0,0 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Realm DisableAnalytics="true" />
</Weavers>
+11
View File
@@ -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.
-11
View File
@@ -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
-6
View File
@@ -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 -1
View File
@@ -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
+16 -132
View File
@@ -1,148 +1,32 @@
<p align="center">
<img width="500" alt="osu! logo" src="assets/lazer.png">
</p>
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](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!
[![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osu-web/localized.svg)](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.
+80
View File
@@ -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;
}
}
}
+63
View File
@@ -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)
{
}
}
}
+23
View File
@@ -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;
}
}
+119
View File
@@ -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
}
}
+23
View File
@@ -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;
}
}
}
+35
View File
@@ -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")]
+95
View File
@@ -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