Compare commits

..

6 Commits

Author SHA1 Message Date
Paul Chote
f1b548c258 Add setup-dotnet action. 2020-12-23 13:33:25 +00:00
Paul Chote
565828efe4 Mono packaging fixes. 2020-12-22 21:48:14 +00:00
teinarss
600ab46302 and back again 2020-12-22 19:21:44 +01:00
teinarss
d1c5c112d5 blah 2020-12-22 19:16:15 +01:00
teinarss
0f0b9afc97 ee 2020-12-22 18:33:04 +01:00
teinarss
3774ba0928 Add support for dotnet core for Windows 2020-12-22 16:56:24 +01:00
2552 changed files with 30114 additions and 75998 deletions

View File

@@ -25,189 +25,82 @@ csharp_using_directive_placement = outside_namespace:suggestion
csharp_new_line_before_open_brace = all csharp_new_line_before_open_brace = all
csharp_space_around_binary_operators = before_and_after csharp_space_around_binary_operators = before_and_after
## Naming styles: #### Naming styles ####
dotnet_naming_style.camel_case.capitalization = camel_case dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.i_prefix_pascal_case.capitalization = pascal_case # Symbol specifications
dotnet_naming_style.i_prefix_pascal_case.required_prefix = I
## Symbol specifications: dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.const_locals.applicable_kinds = local dotnet_naming_symbols.const_private_field.applicable_kinds = field
dotnet_naming_symbols.const_locals.applicable_accessibilities = * dotnet_naming_symbols.const_private_field.required_modifiers = const
dotnet_naming_symbols.const_locals.required_modifiers = const dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
dotnet_naming_symbols.const_fields.applicable_kinds = field dotnet_naming_symbols.internal_field.applicable_kinds = field
dotnet_naming_symbols.const_fields.applicable_accessibilities = * dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
dotnet_naming_symbols.const_fields.required_modifiers = const
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = * dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
dotnet_naming_symbols.private_or_protected_fields.applicable_kinds = field dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.private_or_protected_fields.applicable_accessibilities = private, protected, private_protected dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.interfaces.applicable_accessibilities = * dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local # Naming rules
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = *
dotnet_naming_symbols.most_symbols.applicable_kinds = namespace, class, struct, enum, field, property, method, local_function, event, delegate, type_parameter dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_symbols.most_symbols.applicable_accessibilities = * dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
## Naming rules:
dotnet_naming_rule.const_locals_should_be_pascal_case.symbols = const_locals dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
dotnet_naming_rule.const_locals_should_be_pascal_case.style = pascal_case dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
dotnet_naming_rule.const_locals_should_be_pascal_case.severity = warning dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static_readonly_fields dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = pascal_case dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.symbols = private_or_protected_fields # Naming rules
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.style = camel_case
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.severity = warning
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.symbols = interfaces #require a space before the colon for bases or interfaces in a type declaration
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.style = i_prefix_pascal_case
dotnet_naming_rule.interfaces_should_be_i_prefix_pascal_case.severity = warning
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.symbols = parameters_and_locals
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.style = camel_case
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.severity = warning
dotnet_naming_rule.most_symbols_should_be_pascal_case.symbols = most_symbols
dotnet_naming_rule.most_symbols_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.most_symbols_should_be_pascal_case.severity = warning
## Formatting:
# Also handled by StyleCopAnalyzers - SA1024: ColonsMustBeSpacedCorrectly.
csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
# Also handled by StyleCopAnalyzers - SA1024: ColonsMustBeSpacedCorrectly. csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_colon_in_inheritance_clause = true
# Also handled by StyleCopAnalyzers - SA1000: KeywordsMustBeSpacedCorrectly. #Formatting - wrapping options
csharp_space_after_keywords_in_control_flow_statements = true
# Leave code block on single line. #leave code block on single line
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
#leave statements and member declarations on the same line
# Leave statements and member declarations on the same line.
csharp_preserve_single_line_statements = true csharp_preserve_single_line_statements = true
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias. #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true dotnet_style_predefined_type_for_member_access = true:suggestion
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias. #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
## Others:
# Show an IDE warning when default access modifiers are explicitly specified.
dotnet_style_require_accessibility_modifiers = omit_if_default:warning
# use 'var' instead of explicit type
dotnet_diagnostic.IDE0007.severity = warning
# Don't prefer braces (for one liners).
dotnet_diagnostic.IDE0011.severity = silent
# Object initialization can be simplified / Use object initializer.
dotnet_diagnostic.IDE0017.severity = warning
# Collection initialization can be simplified
dotnet_diagnostic.IDE0028.severity = warning
# Simplify 'default' expression
dotnet_diagnostic.IDE0034.severity = warning
# Modifiers are not ordered.
dotnet_diagnostic.IDE0036.severity = warning
# Raise a warning on build when default access modifiers are explicitly specified.
dotnet_diagnostic.IDE0040.severity = warning
# Make field readonly.
dotnet_diagnostic.IDE0044.severity = warning
# Unused private member.
dotnet_diagnostic.IDE0052.severity = warning
# Unnecessary value assignment.
dotnet_diagnostic.IDE0059.severity = warning
# Unused parameter.
dotnet_diagnostic.IDE0060.severity = warning
# Naming rule violation.
dotnet_diagnostic.IDE1006.severity = warning
# Avoid unnecessary zero-length array allocations.
dotnet_diagnostic.CA1825.severity = warning
# Do not use Enumerable methods on indexable collections. Instead use the collection directly.
dotnet_diagnostic.CA1826.severity = warning
# Count() is used where Any() could be used instead to improve performance.
dotnet_diagnostic.CA1827.severity = warning
# Use Length/Count property instead of Enumerable.Count method.
dotnet_diagnostic.CA1829.severity = warning
# Use string.Contains(char) instead of string.Contains(string) with single characters.
dotnet_diagnostic.CA1847.severity = warning
; 4-column tab indentation ; 4-column tab indentation
[*.yaml] [*.yaml]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4
# Use 'Count' property instead of 'Any' method.
dotnet_diagnostic.RCS1080.severity = warning
# Use read-only auto-implemented property.
dotnet_diagnostic.RCS1170.severity = warning
# Unnecessary interpolated string.
dotnet_diagnostic.RCS1214.severity = warning
# Unnecessary usage of verbatim string literal.
dotnet_diagnostic.RCS1192.severity = warning
# Use pattern matching instead of combination of 'as' operator and null check.
dotnet_diagnostic.RCS1221.severity = warning
# Expression is always equal to 'true'.
dotnet_diagnostic.RCS1215.severity = warning
# Use StringComparison when comparing strings.
dotnet_diagnostic.RCS1155.severity = warning
# Abstract type should not have public constructors.
dotnet_diagnostic.RCS1160.severity = warning
# Optimize 'Dictionary<TKey, TValue>.ContainsKey' call.
dotnet_diagnostic.RCS1235.severity = warning
# Call extension method as instance method.
dotnet_diagnostic.RCS1196.severity = warning

View File

@@ -10,5 +10,5 @@ contact_links:
url: https://discord.openra.net/ url: https://discord.openra.net/
about: Join the community Discord server for community discussion and support. about: Join the community Discord server for community discussion and support.
- name: OpenRA IRC Channel - name: OpenRA IRC Channel
url: https://web.libera.chat/#openra url: https://webchat.freenode.net/#openra
about: Join our development IRC channel on Libera for discussion of development topics. about: Join our development IRC channel on freenode for discussion of development topics.

View File

@@ -13,4 +13,4 @@ You can help speed up the review process by following a few steps:
* Respond to review comments as soon as you reasonably can. Reviewers will usually prioritize Pull Requests that are still fresh in their minds. Make sure to leave a comment when you push new changes, otherwise GitHub does not automatically notify reviewers! * Respond to review comments as soon as you reasonably can. Reviewers will usually prioritize Pull Requests that are still fresh in their minds. Make sure to leave a comment when you push new changes, otherwise GitHub does not automatically notify reviewers!
* Leave a polite comment asking for reviews if a week or more has passed without feedback. * Leave a polite comment asking for reviews if a week or more has passed without feedback.
If you need any help you can ask on Discord (https://discord.openra.net) or in the #openra IRC channel on Libera (not as active as Discord). If you need any help you can ask in the #openra IRC channel on freenode (most active during European evenings).

View File

@@ -3,32 +3,9 @@ name: Continuous Integration
on: on:
push: push:
pull_request: pull_request:
branches: [ bleed, 'prep-*' ] branches: [ bleed ]
jobs: jobs:
linux:
name: Linux (.NET 6.0)
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Check Code
run: |
make check
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make test
linux-mono: linux-mono:
name: Linux (mono) name: Linux (mono)
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -40,31 +17,32 @@ jobs:
- name: Check Code - name: Check Code
run: | run: |
mono --version mono --version
make RUNTIME=mono check make check
- name: Check Mods - name: Check Mods
run: | run: |
# check-scripts does not depend on .net/mono, so is not needed here sudo apt-get install lua5.1
make RUNTIME=mono test make check-scripts
make test
windows: windows:
name: Windows (.NET 6.0) name: Windows (Net 5.0)
runs-on: windows-2019 runs-on: windows-2019
steps: steps:
- name: Clone Repository - name: Clone Repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install .NET 6.0 - name: Install .NET 5
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '6.0.x' dotnet-version: '5.0.x'
- name: Check Code - name: Check Code
shell: powershell shell: powershell
run: | run: |
# Work around runtime failures on the GH Actions runner # Work around runtime failures on the GH Actions runner
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org dotnet nuget locals all --clear
.\make.ps1 check .\make.ps1 check
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64 dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
dotnet test bin\OpenRA.Test.dll --test-adapter-path:. dotnet test bin\OpenRA.Test.dll --test-adapter-path:.

View File

@@ -19,11 +19,6 @@ jobs:
with: with:
ref: ${{ github.event.inputs.tag }} ref: ${{ github.event.inputs.tag }}
- name: Install .NET 6
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Prepare Environment - name: Prepare Environment
run: | run: |
make all make all
@@ -70,71 +65,43 @@ jobs:
with: with:
ref: ${{ github.event.inputs.tag }} ref: ${{ github.event.inputs.tag }}
- name: Install .NET 6
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Prepare Environment - name: Prepare Environment
run: | run: |
make all make all
- name: Clone docs.openra.net (Playtest) - name: Clone docs.openra.net
if: startsWith(github.event.inputs.tag, 'playtest-')
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: openra/docs repository: openra/docs
token: ${{ secrets.DOCS_TOKEN }} token: ${{ secrets.DOCS_TOKEN }}
path: docs path: docs
ref: playtest
- name: Clone docs.openra.net (Release)
if: startsWith(github.event.inputs.tag, 'release-')
uses: actions/checkout@v2
with:
repository: openra/docs
token: ${{ secrets.DOCS_TOKEN }}
path: docs
ref: release
- name: Update docs.openra.net (Playtest) - name: Update docs.openra.net (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-') if: startsWith(github.event.inputs.tag, 'playtest-')
env: env:
GIT_TAG: ${{ github.event.inputs.tag }} GIT_TAG: ${{ github.event.inputs.tag }}
run: | run: |
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md" ./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md" ./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md" ./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
- name: Update docs.openra.net (Release) - name: Update docs.openra.net (Release)
if: startsWith(github.event.inputs.tag, 'release-') if: startsWith(github.event.inputs.tag, 'release-')
env: env:
GIT_TAG: ${{ github.event.inputs.tag }} GIT_TAG: ${{ github.event.inputs.tag }}
run: | run: |
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md" ./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md" ./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
./utility.sh all --sprite-sequence-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md" ./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/lua.md"
- name: Commit docs.openra.net - name: Push docs.openra.net
env: env:
GIT_TAG: ${{ github.event.inputs.tag }} GIT_TAG: ${{ github.event.inputs.tag }}
run: | run: |
cd docs cd docs
git config --local user.email "actions@github.com" git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions" git config --local user.name "GitHub Actions"
git add api/*.md git add --all
git commit -m "Update auto-generated documentation for ${GIT_TAG}" git commit -m "Update auto-generated documentation for ${GIT_TAG}"
git push origin master
- name: Push docs.openra.net (Release)
if: startsWith(github.event.inputs.tag, 'release-')
run: |
cd docs
git push origin release
- name: Push docs.openra.net (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-')
run: |
cd docs
git push origin playtest

View File

@@ -15,15 +15,17 @@ jobs:
if: github.repository == 'openra/openra' if: github.repository == 'openra/openra'
steps: steps:
- name: Download Packages - name: Download Packages
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: | run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe" wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64.exe"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64-winportable.zip" -O "OpenRA-${GIT_TAG}-x64-win-itch.zip"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg" wget -q "https://github.com${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}.dmg"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage" wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Dune-2000-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage" wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Red-Alert-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage" wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml" wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${GIT_TAG}/packaging/.itch.toml"
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml zip -u "OpenRA-${GIT_TAG}-x64-win-itch.zip" .itch.toml
- name: Publish Windows Installer - name: Publish Windows Installer
uses: josephbmanley/butler-publish-itchio-action@master uses: josephbmanley/butler-publish-itchio-action@master
@@ -31,9 +33,9 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: win CHANNEL: win
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe"
- name: Publish Windows Itch Bundle - name: Publish Windows Itch Bundle
uses: josephbmanley/butler-publish-itchio-action@master uses: josephbmanley/butler-publish-itchio-action@master
@@ -41,7 +43,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: itch CHANNEL: itch
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
@@ -51,9 +53,9 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: macos CHANNEL: macos
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg"
- name: Publish RA AppImage - name: Publish RA AppImage
uses: josephbmanley/butler-publish-itchio-action@master uses: josephbmanley/butler-publish-itchio-action@master
@@ -61,7 +63,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-ra CHANNEL: linux-ra
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
@@ -71,7 +73,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-cnc CHANNEL: linux-cnc
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
@@ -81,6 +83,6 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-d2k CHANNEL: linux-d2k
ITCH_GAME: openra ITCH_GAME: openra
ITCH_USER: openra ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }} VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage PACKAGE: OpenRA-Dune-2000-x86_64.AppImage

View File

@@ -8,30 +8,6 @@ on:
- 'devtest-*' - 'devtest-*'
jobs: jobs:
source:
name: Source Tarball
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
- name: Package Source
run: |
mkdir -p build/source
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
- name: Upload Packages
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/source/*
linux: linux:
name: Linux AppImages name: Linux AppImages
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@@ -39,11 +15,6 @@ jobs:
- name: Clone Repository - name: Clone Repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Prepare Environment - name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV} run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
@@ -62,21 +33,16 @@ jobs:
file: build/linux/* file: build/linux/*
macos: macos:
name: macOS Disk Image name: macOS Disk Images
runs-on: macos-11 runs-on: macos-10.15
steps: steps:
- name: Clone Repository - name: Clone Repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Prepare Environment - name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV} run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
- name: Package Disk Image - name: Package Disk Images
env: env:
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }} MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }} MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
@@ -87,7 +53,7 @@ jobs:
mkdir -p build/macos mkdir -p build/macos
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos" ./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
- name: Upload Package - name: Upload Packages
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
@@ -103,16 +69,15 @@ jobs:
- name: Clone Repository - name: Clone Repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install .NET 6.0 - name: Install .NET 5
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '6.0.x' dotnet-version: '5.0.x'
- name: Prepare Environment - name: Prepare Environment
run: | run: |
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV} echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
sudo apt-get update sudo apt install nsis wine64
sudo apt-get install nsis wine64
- name: Package Installers - name: Package Installers
run: | run: |

19
.gitignore vendored
View File

@@ -13,10 +13,19 @@ obj
_ReSharper.*/ _ReSharper.*/
/.vs /.vs
# Visual Studio Code
/.vscode/settings.json
# binaries # binaries
mods/*/*.dll
mods/*/*.mdb
mods/*/*.pdb
/*.dll
/*.dll.config
/*.so
/*.dylib
/*.pdb
/*.mdb
/*.exe
/*.exe.config
thirdparty/download/*
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
# backup files by various editors # backup files by various editors
@@ -41,6 +50,10 @@ Settings.md
openra.6 openra.6
update.log update.log
# StyleCop
*.Cache
StyleCopViolations.xml
# SublimeText # SublimeText
*.sublime-project *.sublime-project
*.sublime-workspace *.sublime-workspace

View File

@@ -1,9 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"ms-dotnettools.csharp", "ms-dotnettools.csharp",
"openra.oraide-vscode",
"openra.vscode-openra-lua",
"EditorConfig.EditorConfig", "EditorConfig.EditorConfig",
"macabeus.vscode-fluent", "ms-vscode.mono-debug"
] ]
} }

78
.vscode/launch.json vendored
View File

@@ -3,60 +3,62 @@
"configurations": [ "configurations": [
{ {
"name": "Launch (TD)", "name": "Launch (TD)",
"type": "coreclr", "type": "clr",
"request": "launch", "linux": {
"program": "${workspaceRoot}/bin/OpenRA.dll", "type": "mono"
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
}, },
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."], "osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=cnc"],
"preLaunchTask": "build", "preLaunchTask": "build",
}, },
{ {
"name": "Launch (RA)", "name": "Launch (RA)",
"type": "coreclr", "type": "clr",
"request": "launch", "linux": {
"program": "${workspaceRoot}/bin/OpenRA.dll", "type": "mono"
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
}, },
"args": ["Game.Mod=ra", "Engine.EngineDir=.."], "osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ra"],
"preLaunchTask": "build", "preLaunchTask": "build",
}, },
{ {
"name": "Launch (D2k)", "name": "Launch (D2k)",
"type": "coreclr", "type": "clr",
"request": "launch", "linux": {
"program": "${workspaceRoot}/bin/OpenRA.dll", "type": "mono"
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
}, },
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."], "osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=d2k"],
"preLaunchTask": "build", "preLaunchTask": "build",
}, },
{ {
"name": "Launch (TS)", "name": "Launch (TS)",
"type": "coreclr", "type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"request": "launch", "request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.dll", "program": "${workspaceRoot}/OpenRA.Game.exe",
"windows": { "cwd": "${workspaceRoot}",
"program": "${workspaceRoot}/bin/OpenRA.exe", "args": ["Game.Mod=ts"],
},
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch Utility",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.Utility.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.Utility.exe",
},
"args": ["all", "--docs", "{DEV_VERSION}"],
"env": {
"ENGINE_DIR": ".."
},
"preLaunchTask": "build", "preLaunchTask": "build",
}, },
] ]

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"omnisharp.enableRoslynAnalyzers": true
}

25
.vscode/tasks.json vendored
View File

@@ -1,36 +1,13 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"options": {
"env": {
"ENGINE_DIR": ".."
}
},
"tasks": [ "tasks": [
{ {
"label": "build", "label": "build",
"command": "make", "command": "make",
"args": ["all", "CONFIGURATION=Debug"], "args": ["all"],
"windows": { "windows": {
"command": "make.cmd" "command": "make.cmd"
} }
},
{
"label": "Run Utility",
"command": "dotnet ${workspaceRoot}/bin/OpenRA.Utility.dll ${input:modId} ${input:command}",
"type": "shell",
} }
],
"inputs": [
{
"id": "modId",
"description": "ID of the mod to run",
"default": "all",
"type": "promptString"
}, {
"id": "command",
"description": "Name of the command + parameters",
"default": "",
"type": "promptString"
},
] ]
} }

36
AUTHORS
View File

@@ -2,30 +2,28 @@ OpenRA wouldn't be where it is today without the
hard work of many contributors. hard work of many contributors.
The OpenRA developers are: The OpenRA developers are:
* Gustas Kažukauskas (PunkPun) * Chris Forbes (chrisf)
* Lukas Franke (abcdefg30) * Lukas Franke (abcdefg30)
* Matthias Mailänder (Mailaender) * Paul Chote (pchote)
* Reaperrr
Previous developers included: Previous developers included:
* Alli Witheford (alzeih) * Alli Witheford (alzeih)
* Caleb Anderson (RobotCaleb) * Caleb Anderson (RobotCaleb)
* Chris Forbes (chrisf)
* Curtis Shmyr (hamb) * Curtis Shmyr (hamb)
* Daniel Hernandez (Mancano) * Daniel Hernandez (Mancano)
* Igor Popov (ihptru) * Igor Popov (ihptru)
* Matthias Mailänder (Mailaender)
* Megan Bowra-Dean (beedee) * Megan Bowra-Dean (beedee)
* Mike Bundy (kehaar) * Mike Bundy (kehaar)
* Oliver Brakmann (obrakmann) * Oliver Brakmann (obrakmann)
* Paul Chote (pchote)
* Pavel Penev (penev92) * Pavel Penev (penev92)
* Reaperrr
* Robert Pepperell (ytinasni) * Robert Pepperell (ytinasni)
* ScottNZ * ScottNZ
* Tom Roostan (RoosterDragon) * Tom Roostan (RoosterDragon)
Also thanks to: Also thanks to:
* abmyii * abmyii
* anvilvapre (anvilvapre)
* Adam Valy (Tschokky) * Adam Valy (Tschokky)
* Akseli Virtanen (RAGEQUIT) * Akseli Virtanen (RAGEQUIT)
* Alexander Fast (mizipzor) * Alexander Fast (mizipzor)
@@ -39,7 +37,6 @@ Also thanks to:
* Arik Lirette (Angusm3) * Arik Lirette (Angusm3)
* Barnaby Smith (mvi) * Barnaby Smith (mvi)
* Bellator * Bellator
* Bernd Stellwag (burned42)
* Biofreak * Biofreak
* Braxton Williams (Buddytex) * Braxton Williams (Buddytex)
* Brendan Gluth (Mechanical_Man) * Brendan Gluth (Mechanical_Man)
@@ -49,7 +46,6 @@ Also thanks to:
* Chris Cameron (Vesuvian) * Chris Cameron (Vesuvian)
* Chris Grant (Unit158) * Chris Grant (Unit158)
* Christer Ulfsparre (Holloweye) * Christer Ulfsparre (Holloweye)
* Christoph Lahner (chlah)
* clem * clem
* Cody Brittain (Generalcamo) * Cody Brittain (Generalcamo)
* Constantin Helmig (CH4Code) * Constantin Helmig (CH4Code)
@@ -62,7 +58,6 @@ Also thanks to:
* DeadlySurprise * DeadlySurprise
* Dmitri Suvorov (suvjunmd) * Dmitri Suvorov (suvjunmd)
* dtluna * dtluna
* Eduardo Cáceres (eduherminio)
* Erasmus Schroder (rasco) * Erasmus Schroder (rasco)
* Eric Bajumpaa (SteelPhase) * Eric Bajumpaa (SteelPhase)
* Evgeniy Sergeev (evgeniysergeev) * Evgeniy Sergeev (evgeniysergeev)
@@ -80,7 +75,6 @@ Also thanks to:
* Imago * Imago
* Iran * Iran
* Ishan Bhargava (ishantheperson) * Ishan Bhargava (ishantheperson)
* Ivaylo Draganov (dragunoff)
* Jacob Dufault (jacobdufault) * Jacob Dufault (jacobdufault)
* James Dunne (jsd) * James Dunne (jsd)
* James Gilbert (DSUK) * James Gilbert (DSUK)
@@ -141,12 +135,12 @@ Also thanks to:
* Rikhardur Bjarni Einarsson (WolfGaming) * Rikhardur Bjarni Einarsson (WolfGaming)
* Sascha Biedermann (bidifx) * Sascha Biedermann (bidifx)
* Sean Hunt (coppro) * Sean Hunt (coppro)
* Sebastien Kerguen (xanax)
* Shawn Collins (UberWaffe) * Shawn Collins (UberWaffe)
* Simon Verbeke (Saticmotion) * Simon Verbeke (Saticmotion)
* Stuart McHattie (SDJMcHattie) * Stuart McHattie (SDJMcHattie)
* Taryn Hill (Phrohdoh) * Taryn Hill (Phrohdoh)
* Teemu Nieminen (Temeez) * Teemu Nieminen (Temeez)
* Thomas Christlieb (ThomasChr)
* Tim Mylemans (gecko) * Tim Mylemans (gecko)
* Tirili * Tirili
* Tomas Einarsson (Mesacer) * Tomas Einarsson (Mesacer)
@@ -179,17 +173,9 @@ under the MIT license.
Using FuzzyLogicLibrary (fuzzynet) by Dmitry Using FuzzyLogicLibrary (fuzzynet) by Dmitry
Kaluzhny and released under the GNU GPL terms. Kaluzhny and released under the GNU GPL terms.
Using Mono.Nat by Alan McGovern, Ben Motmans, Using Open.Nat by Lucas Ontivero, based on the work
Nicholas Terry distributed under the MIT license. of Alan McGovern and Ben Motmans and distributed
under the MIT license.
Using MP3Sharp by Robert Bruke and Zane Wagner
licensed under the GNU LGPL Version 3.
Using TagLib# by Stephen Shaw licensed under the
GNU LGPL Version 2.1.
Using NVorbis by Andrew Ward distributed under
the MIT license.
Using ICSharpCode.SharpZipLib initially by Mike Using ICSharpCode.SharpZipLib initially by Mike
Krueger and distributed under the GNU GPL terms. Krueger and distributed under the GNU GPL terms.
@@ -205,12 +191,6 @@ distributed under MIT License.
Using ANGLE distributed under the BS3 3-Clause license. Using ANGLE distributed under the BS3 3-Clause license.
Using Pfim developed by Nick Babcock
distributed under the MIT license.
Using Linguini by the Space Station 14 team
licensed under Apache and MIT terms.
This site or product includes IP2Location LITE data This site or product includes IP2Location LITE data
available from http://www.ip2location.com. available from http://www.ip2location.com.

View File

@@ -56,8 +56,8 @@ further defined and clarified by project maintainers.
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by private-messaging a project team member (users with a + in front reported by private-messaging a project team member (users with a + in front
of their name) via our IRC channel (#openra on Libera of their name) via our IRC channel (#openra on freenode
[webchat](https://web.libera.chat/#openra)). All [webchat](http://webchat.freenode.net/?channels=openra)). All
complaints will be reviewed and investigated and will result in a response that complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident. obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@@ -1,52 +0,0 @@
<Project>
<PropertyGroup>
<OutputType>Library</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<LangVersion>7.3</LangVersion>
<DebugSymbols>true</DebugSymbols>
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
<OutputPath>$(EngineRootPath)/bin</OutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>$(EngineRootPath)/OpenRA.ruleset</CodeAnalysisRuleSet>
<Nullable>disable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework Condition="'$(MSBuildRuntimeType)'!='Mono'">net6.0</TargetFramework>
<TargetFramework Condition="'$(MSBuildRuntimeType)'=='Mono'">netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
<Compile Include="**/*.cs" Exclude="$(DefaultItemExcludes)" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
<ItemGroup Condition="'$(Configuration)'=='Release'">
<Analyzer Remove="@(Analyzer)" />
</ItemGroup>
</Target>
<!-- StyleCop -->
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@ Windows
Compiling OpenRA requires the following dependencies: Compiling OpenRA requires the following dependencies:
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions) * [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio) * [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (or via Visual Studio)
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax. To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
@@ -17,67 +17,93 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
Linux Linux
===== =====
.NET 6 or Mono (version 6.4 or later) is required to compile OpenRA. We recommend using .NET 6 when possible, as Mono is poorly packaged by most Linux distributions (e.g. missing the required `msbuild` toolchain), and has been deprecated as a standalone project. Mono, version 6.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
The [.NET 6 download page](https://dotnet.microsoft.com/download/dotnet/6.0) provides repositories for various package managers and binary releases for several architectures. If you prefer to use Mono, we suggest adding the [upstream repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version and the `msbuild` toolchain. To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](http://luabinaries.sourceforge.net/download.html) before compiling OpenRA. If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
These can be installed using your package manager on various distros: Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
<details><summary>Arch Linux</summary> Arch Linux
----------
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
``` ```
sudo pacman -S openal libgl freetype2 sdl2 lua51 sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
``` ```
</details>
<details><summary>Debian/Ubuntu</summary> Debian/Ubuntu
-------------
:warning: The `mono` packages in the Ubuntu < 19.04 and Debian < 10 repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases if needed.
``` ```
sudo apt install libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
``` ```
</details>
<details><summary>Fedora</summary> Fedora
------
:warning: The `mono` packages in the Fedora repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases.
``` ```
sudo dnf install SDL2 freetype "lua = 5.1" openal-soft sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
``` ```
</details>
<details><summary>Gentoo</summary> Gentoo
------
``` ```
sudo emerge -av media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/opengl '=dev-lang/lua-5.1.5*' sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/jpeg virtual/opengl '=dev-lang/lua-5.1.5*' x11-misc/xdg-utils gnome-extra/zenity
``` ```
</details>
<details><summary>Mageia</summary> Mageia
------
``` ```
sudo dnf install SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
``` ```
</details>
<details><summary>openSUSE</summary> openSUSE
--------
``` ```
sudo zypper in openal-soft freetype2 SDL2 lua51 sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
``` ```
</details>
<details><summary>Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)</summary> Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
----------------------------------------------------
The EPEL repository is required in order for the following command to run properly. The EPEL repository is required in order for the following command to run properly.
``` ```
sudo yum install SDL2 freetype "lua = 5.1" openal-soft sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
``` ```
</details>
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
macOS macOS
===== =====
[.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0) or [Mono](https://www.mono-project.com/download/stable/#download-mac) (version 6.4 or later) is required to compile OpenRA. We recommend using .NET 6 unless you are running a very old version of macOS (10.9 through 10.14). Before compiling OpenRA you must install the following dependencies:
* [Mono >= 6.4](https://www.mono-project.com/download/stable/#download-mac)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.

150
Makefile
View File

@@ -1,40 +1,42 @@
############################# INSTRUCTIONS ############################# ############################# INSTRUCTIONS #############################
# #
# to compile, run: # to compile, run:
# make # make [DEBUG=true]
#
# to compile using Mono (version 6.4 or greater) instead of .NET 6, run:
# make RUNTIME=mono
# #
# to compile using system libraries for native dependencies, run: # to compile using system libraries for native dependencies, run:
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic # make [DEBUG=true] TARGETPLATFORM=unix-generic
# #
# to check the official mods for erroneous yaml files, run: # to check the official mods for erroneous yaml files, run:
# make [RUNTIME=net6] test # make test
# #
# to check the engine and official mod dlls for code style violations, run: # to check the engine and official mod dlls for code style violations, run:
# make [RUNTIME=net6] check # make check
# #
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run: # to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install # make [prefix=/foo] [bindir=/bar/bin] install
# #
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000 # to install Linux startup scripts, desktop files, icons, and MIME metadata
# using system libraries for native dependencies, run:
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
#
# to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata
# make install-linux-shortcuts # make install-linux-shortcuts
# #
# to install FreeDesktop AppStream metadata # to install Linux AppStream metadata
# make install-linux-appdata # make install-linux-appdata
# #
# to install the Unix man page
# make install-man
#
# for help, run: # for help, run:
# make help # make help
# #
############################## TOOLCHAIN ###############################
#
# List of .NET assemblies that we can guarantee exist
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.dll OpenRA.Utility.dll OpenRA.Server.dll OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll Microsoft.Win32.Registry.dll System.Security.AccessControl.dll System.Security.Principal.Windows.dll System.Xml.Linq.dll System.Runtime.Serialization.dll
######################### UTILITIES/SETTINGS ########################### ######################### UTILITIES/SETTINGS ###########################
# #
# Install locations for local installs and downstream packaging # Install locations for local installs and downstream packaging
@@ -46,161 +48,125 @@ bindir ?= $(prefix)/bin
libdir ?= $(prefix)/lib libdir ?= $(prefix)/lib
gameinstalldir ?= $(libdir)/openra gameinstalldir ?= $(libdir)/openra
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
DATA_INSTALL_DIR = $(DESTDIR)$(datadir)
OPENRA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
# Toolchain # Toolchain
CWD = $(shell pwd) CWD = $(shell pwd)
MSBUILD = msbuild -verbosity:m -nologo MSBUILD = msbuild -verbosity:m -nologo
DOTNET = dotnet
MONO = mono MONO = mono
RM = rm RM = rm
RM_R = $(RM) -r RM_R = $(RM) -r
RM_F = $(RM) -f RM_F = $(RM) -f
RM_RF = $(RM) -rf RM_RF = $(RM) -rf
RUNTIME ?= net6 VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
CONFIGURATION ?= Release
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
ARCH_X64 = $(shell echo ${DOTNET_RID} | grep x64)
# Only for use in target version:
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
# Detect target platform for dependencies if not given by the user # Detect target platform for dependencies if not given by the user
ifndef TARGETPLATFORM ifndef TARGETPLATFORM
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
ifeq ($(ARCH_X64),)
TARGETPLATFORM = osx-arm64
else
TARGETPLATFORM = osx-x64 TARGETPLATFORM = osx-x64
endif
else else
ifeq ($(UNAME_M),x86_64) ifeq ($(UNAME_M),x86_64)
TARGETPLATFORM = linux-x64 TARGETPLATFORM = linux-x64
else else
ifeq ($(UNAME_M),aarch64)
TARGETPLATFORM = linux-arm64
else
TARGETPLATFORM = unix-generic TARGETPLATFORM = unix-generic
endif endif
endif endif
endif endif
endif
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.dll
##################### DEVELOPMENT BUILDS AND TESTS ##################### ##################### DEVELOPMENT BUILDS AND TESTS #####################
# #
all: all:
@echo "Compiling in ${CONFIGURATION} mode..." @command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
ifeq ($(RUNTIME), mono) @$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 6.4."; exit 1)
@$(MSBUILD) -t:Build -restore -p:Configuration=${CONFIGURATION} -p:TargetPlatform=$(TARGETPLATFORM)
else
@$(DOTNET) build -c ${CONFIGURATION} -nologo -p:TargetPlatform=$(TARGETPLATFORM)
endif
ifeq ($(TARGETPLATFORM), unix-generic) ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh @./configure-system-libraries.sh
endif endif
@./fetch-geoip.sh @./fetch-geoip.sh
# dotnet clean and msbuild -t:Clean leave files that cause problems when switching between mono/dotnet
# Deleting the intermediate / output directories ensures the build directory is actually clean
clean: clean:
@-$(RM_RF) ./bin ./*/obj @-$(RM_RF) ./bin ./*/bin ./*/obj
@$(MSBUILD) -t:Clean -p:Mono=true
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP @-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
check: check:
@echo @echo
@echo "Compiling in Debug mode..." @echo "Compiling in debug mode..."
ifeq ($(RUNTIME), mono) @$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
# Enabling EnforceCodeStyleInBuild and GenerateDocumentationFile as a workaround for some code style rules (in particular IDE0005) being bugged and not reporting warnings/errors otherwise. @echo
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM) -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true @echo "Checking runtime assemblies..."
else @$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
# Enabling EnforceCodeStyleInBuild and GenerateDocumentationFile as a workaround for some code style rules (in particular IDE0005) being bugged and not reporting warnings/errors otherwise.
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM) -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
endif
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@echo @echo
@echo "Checking for explicit interface violations..." @echo "Checking for explicit interface violations..."
@./utility.sh all --check-explicit-interfaces @$(OPENRA_UTILITY) all --check-explicit-interfaces
@echo @echo
@echo "Checking for incorrect conditional trait interface overrides..." @echo "Checking for incorrect conditional trait interface overrides..."
@./utility.sh all --check-conditional-trait-interface-overrides @$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
check-scripts: check-scripts:
@echo @echo
@echo "Checking for Lua syntax errors..." @echo "Checking for Lua syntax errors..."
@find lua/ mods/*/{maps,scripts}/ -iname "*.lua" -print0 | xargs -0n1 luac -p @luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
test: all test: all
@echo @echo
@echo "Testing Tiberian Sun mod MiniYAML..." @echo "Testing Tiberian Sun mod MiniYAML..."
@./utility.sh ts --check-yaml @$(OPENRA_UTILITY) ts --check-yaml
@echo @echo
@echo "Testing Dune 2000 mod MiniYAML..." @echo "Testing Dune 2000 mod MiniYAML..."
@./utility.sh d2k --check-yaml @$(OPENRA_UTILITY) d2k --check-yaml
@echo @echo
@echo "Testing Tiberian Dawn mod MiniYAML..." @echo "Testing Tiberian Dawn mod MiniYAML..."
@./utility.sh cnc --check-yaml @$(OPENRA_UTILITY) cnc --check-yaml
@echo @echo
@echo "Testing Red Alert mod MiniYAML..." @echo "Testing Red Alert mod MiniYAML..."
@./utility.sh ra --check-yaml @$(OPENRA_UTILITY) ra --check-yaml
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ############## ############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
# #
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
ifeq ($(VERSION),) @sh -c '. ./packaging/functions.sh; set_engine_version $(VERSION) .'
$(error Unable to determine new version (requires git or override of variable VERSION)) @sh -c '. ./packaging/functions.sh; set_mod_version $(VERSION) mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
endif
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
install: install:
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True' @sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(OPENRA_INSTALL_DIR) $(TARGETPLATFORM) True True True'
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra' @sh -c '. ./packaging/functions.sh; install_data $(CWD) $(OPENRA_INSTALL_DIR) cnc d2k ra'
install-linux-shortcuts: install-linux-shortcuts:
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra' @sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) $(OPENRA_INSTALL_DIR) $(BIN_INSTALL_DIR) $(DATA_INSTALL_DIR) $(VERSION) cnc d2k ra'
install-linux-appdata: install-linux-appdata:
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra' @sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) $(DATA_INSTALL_DIR) cnc d2k ra'
install-man: all
@mkdir -p $(DESTDIR)$(mandir)/man6/
@./utility.sh all --man-page > $(DESTDIR)$(mandir)/man6/openra.6
help: help:
@echo 'to compile, run:' @echo 'to compile, run:'
@echo ' make' @echo ' make [DEBUG=true]'
@echo
@echo 'to compile using Mono (version 6.4 or greater) instead of .NET 6, run:'
@echo ' make RUNTIME=mono'
@echo @echo
@echo 'to compile using system libraries for native dependencies, run:' @echo 'to compile using system libraries for native dependencies, run:'
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic' @echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
@echo @echo
@echo 'to check the official mods for erroneous yaml files, run:' @echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make [RUNTIME=net6] test' @echo ' make test'
@echo @echo
@echo 'to check the engine and official mod dlls for code style violations, run:' @echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make [RUNTIME=net6] check' @echo ' make test'
@echo @echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:' @echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
@echo ' make [RUNTIME=net6] [prefix=/foo] [TARGETPLATFORM=unix-generic] install' @echo ' make [prefix=/foo] install'
@echo @echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000' @echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
@echo 'using system libraries for native dependencies, run:'
@echo ' make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
@echo
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
@echo ' make install-linux-shortcuts' @echo ' make install-linux-shortcuts'
@echo @echo
@echo 'to install FreeDesktop AppStream metadata' @echo 'to install Linux AppStream metadata'
@echo ' make install-linux-appdata' @echo ' make install-linux-appdata'
@echo
@echo 'to install a Unix man page'
@echo ' make install-man'
########################### MAKEFILE SETTINGS ########################## ########################### MAKEFILE SETTINGS ##########################
# #
@@ -208,4 +174,4 @@ help:
.SUFFIXES: .SUFFIXES:
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata install-man help .PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -53,15 +53,15 @@ namespace OpenRA.Activities
Activity childActivity; Activity childActivity;
protected Activity ChildActivity protected Activity ChildActivity
{ {
get => SkipDoneActivities(childActivity); get { return SkipDoneActivities(childActivity); }
private set => childActivity = value; private set { childActivity = value; }
} }
Activity nextActivity; Activity nextActivity;
public Activity NextActivity public Activity NextActivity
{ {
get => SkipDoneActivities(nextActivity); get { return SkipDoneActivities(nextActivity); }
private set => nextActivity = value; private set { nextActivity = value; }
} }
internal static Activity SkipDoneActivities(Activity first) internal static Activity SkipDoneActivities(Activity first)
@@ -74,14 +74,14 @@ namespace OpenRA.Activities
// drop valid activities queued after it. Walk the queue until we find a valid activity or // drop valid activities queued after it. Walk the queue until we find a valid activity or
// (more likely) run out of activities. // (more likely) run out of activities.
while (first != null && first.State == ActivityState.Done) while (first != null && first.State == ActivityState.Done)
first = first.nextActivity; first = first.NextActivity;
return first; return first;
} }
public bool IsInterruptible { get; protected set; } public bool IsInterruptible { get; protected set; }
public bool ChildHasPriority { get; protected set; } public bool ChildHasPriority { get; protected set; }
public bool IsCanceling => State == ActivityState.Canceling; public bool IsCanceling { get { return State == ActivityState.Canceling; } }
bool finishing; bool finishing;
bool firstRunCompleted; bool firstRunCompleted;
bool lastRun; bool lastRun;
@@ -95,7 +95,7 @@ namespace OpenRA.Activities
public Activity TickOuter(Actor self) public Activity TickOuter(Actor self)
{ {
if (State == ActivityState.Done) if (State == ActivityState.Done)
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} after it had already completed."); throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
if (State == ActivityState.Queued) if (State == ActivityState.Queued)
{ {
@@ -105,7 +105,7 @@ namespace OpenRA.Activities
} }
if (!firstRunCompleted) if (!firstRunCompleted)
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method."); throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
// Only run the parent tick when the child is done. // Only run the parent tick when the child is done.
// We must always let the child finish on its own before continuing. // We must always let the child finish on its own before continuing.
@@ -120,8 +120,7 @@ namespace OpenRA.Activities
lastRun = Tick(self); lastRun = Tick(self);
// Avoid a single tick delay if the childactivity was just queued. // Avoid a single tick delay if the childactivity was just queued.
var ca = ChildActivity; if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
if (ca != null && ca.State == ActivityState.Queued)
{ {
if (ChildHasPriority) if (ChildHasPriority)
lastRun = TickChild(self) && finishing; lastRun = TickChild(self) && finishing;
@@ -207,18 +206,18 @@ namespace OpenRA.Activities
public void Queue(Activity activity) public void Queue(Activity activity)
{ {
var it = this; if (NextActivity != null)
while (it.nextActivity != null) NextActivity.Queue(activity);
it = it.nextActivity; else
it.nextActivity = activity; NextActivity = activity;
} }
public void QueueChild(Activity activity) public void QueueChild(Activity activity)
{ {
if (childActivity != null) if (ChildActivity != null)
childActivity.Queue(activity); ChildActivity.Queue(activity);
else else
childActivity = activity; ChildActivity = activity;
} }
/// <summary> /// <summary>
@@ -270,21 +269,15 @@ namespace OpenRA.Activities
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
{ {
// Skips Done child and next activities if (includeChildren && ChildActivity != null)
if (includeChildren) foreach (var a in ChildActivity.ActivitiesImplementing<T>())
{ yield return a;
var ca = ChildActivity;
if (ca != null)
foreach (var a in ca.ActivitiesImplementing<T>())
yield return a;
}
if (this is T) if (this is T)
yield return (T)(object)this; yield return (T)(object)this;
var na = NextActivity; if (NextActivity != null)
if (na != null) foreach (var a in NextActivity.ActivitiesImplementing<T>())
foreach (var a in na.ActivitiesImplementing<T>())
yield return a; yield return a;
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -22,11 +22,11 @@ namespace OpenRA.Activities
IsInterruptible = interruptible; IsInterruptible = interruptible;
} }
readonly Action a; Action a;
public override bool Tick(Actor self) public override bool Tick(Actor self)
{ {
a.Invoke(); a?.Invoke();
return true; return true;
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -11,7 +11,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Eluant; using Eluant;
@@ -24,18 +23,9 @@ using OpenRA.Traits;
namespace OpenRA namespace OpenRA
{ {
[Flags]
public enum SystemActors
{
Player = 0,
EditorPlayer = 1,
World = 2,
EditorWorld = 4
}
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
{ {
internal readonly struct SyncHash internal struct SyncHash
{ {
public readonly ISync Trait; public readonly ISync Trait;
readonly Func<object, int> hashFunction; readonly Func<object, int> hashFunction;
@@ -58,25 +48,30 @@ namespace OpenRA
Activity currentActivity; Activity currentActivity;
public Activity CurrentActivity public Activity CurrentActivity
{ {
get => Activity.SkipDoneActivities(currentActivity); get { return Activity.SkipDoneActivities(currentActivity); }
private set => currentActivity = value; private set { currentActivity = value; }
} }
public int Generation; public int Generation;
public Actor ReplacedByActor; public Actor ReplacedByActor;
public IEffectiveOwner EffectiveOwner { get; } public IEffectiveOwner EffectiveOwner { get; private set; }
public IOccupySpace OccupiesSpace { get; } public IOccupySpace OccupiesSpace { get; private set; }
public ITargetable[] Targetables { get; } public ITargetable[] Targetables { get; private set; }
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
public bool IsIdle => CurrentActivity == null; public bool IsIdle { get { return CurrentActivity == null; } }
public bool IsDead => Disposed || (health != null && health.IsDead); public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
public CPos Location => OccupiesSpace.TopLeft; public CPos Location { get { return OccupiesSpace.TopLeft; } }
public WPos CenterPosition => OccupiesSpace.CenterPosition; public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
public WRot Orientation => facing?.Orientation ?? WRot.None; public WRot Orientation
{
get
{
return facing != null ? facing.Orientation : WRot.None;
}
}
/// <summary>Value used to represent an invalid token.</summary> /// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1; public static readonly int InvalidConditionToken = -1;
@@ -103,7 +98,7 @@ namespace OpenRA
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary> /// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache; readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
internal SyncHash[] SyncHashes { get; } internal SyncHash[] SyncHashes { get; private set; }
readonly IFacing facing; readonly IFacing facing;
readonly IHealth health; readonly IHealth health;
@@ -115,8 +110,10 @@ namespace OpenRA
readonly IDefaultVisibility defaultVisibility; readonly IDefaultVisibility defaultVisibility;
readonly INotifyBecomingIdle[] becomingIdles; readonly INotifyBecomingIdle[] becomingIdles;
readonly INotifyIdle[] tickIdles; readonly INotifyIdle[] tickIdles;
readonly IEnumerable<WPos> enabledTargetableWorldPositions; readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
WPos[] staticTargetablePositions;
bool created; bool created;
bool setStaticTargetablePositions;
internal Actor(World world, string name, TypeDictionary initDict) internal Actor(World world, string name, TypeDictionary initDict)
{ {
@@ -124,7 +121,7 @@ namespace OpenRA
.FirstOrDefault(i => i.Count() > 1); .FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null) if (duplicateInit != null)
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'"); throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
var init = new ActorInitializer(this, initDict); var init = new ActorInitializer(this, initDict);
@@ -145,6 +142,7 @@ namespace OpenRA
Info = world.Map.Rules.Actors[name]; Info = world.Map.Rules.Actors[name];
IPositionable positionable = null;
var resolveOrdersList = new List<IResolveOrder>(); var resolveOrdersList = new List<IResolveOrder>();
var renderModifiersList = new List<IRenderModifier>(); var renderModifiersList = new List<IRenderModifier>();
var rendersList = new List<IRender>(); var rendersList = new List<IRender>();
@@ -166,6 +164,7 @@ namespace OpenRA
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering. // performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style // Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
// rules for spacing in order to keep these assignments compact and readable. // rules for spacing in order to keep these assignments compact and readable.
{ if (trait is IPositionable t) positionable = t; }
{ if (trait is IOccupySpace t) OccupiesSpace = t; } { if (trait is IOccupySpace t) OccupiesSpace = t; }
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; } { if (trait is IEffectiveOwner t) EffectiveOwner = t; }
{ if (trait is IFacing t) facing = t; } { if (trait is IFacing t) facing = t; }
@@ -192,9 +191,10 @@ namespace OpenRA
tickIdles = tickIdlesList.ToArray(); tickIdles = tickIdlesList.ToArray();
Targetables = targetablesList.ToArray(); Targetables = targetablesList.ToArray();
var targetablePositions = targetablePositionsList.ToArray(); var targetablePositions = targetablePositionsList.ToArray();
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled); enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
SyncHashes = syncHashesList.ToArray(); SyncHashes = syncHashesList.ToArray();
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
} }
} }
@@ -229,6 +229,11 @@ namespace OpenRA
foreach (var notify in allObserverNotifiers) foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache); notify(this, readOnlyConditionCache);
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (setStaticTargetablePositions)
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
// TODO: Other traits may need initialization after being notified of initial condition state. // TODO: Other traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead. // TODO: A post condition initialization notification phase may allow queueing activities instead.
@@ -241,7 +246,7 @@ namespace OpenRA
continue; continue;
if (creationActivity != null) if (creationActivity != null)
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}"); throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
var activity = ica.GetCreationActivity(); var activity = ica.GetCreationActivity();
if (activity == null) if (activity == null)
@@ -360,7 +365,8 @@ namespace OpenRA
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is Actor o && Equals(o); var o = obj as Actor;
return o != null && Equals(o);
} }
public bool Equals(Actor other) public bool Equals(Actor other)
@@ -481,7 +487,7 @@ namespace OpenRA
health.InflictDamage(this, attacker, damage, false); health.InflictDamage(this, attacker, damage, false);
} }
public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default) public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))
{ {
if (Disposed || health == null) if (Disposed || health == null)
return; return;
@@ -522,7 +528,7 @@ namespace OpenRA
{ {
// PERF: Avoid LINQ. // PERF: Avoid LINQ.
foreach (var targetable in Targetables) foreach (var targetable in Targetables)
if (targetable.TargetableBy(this, byActor)) if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
return true; return true;
return false; return false;
@@ -530,8 +536,11 @@ namespace OpenRA
public IEnumerable<WPos> GetTargetablePositions() public IEnumerable<WPos> GetTargetablePositions()
{ {
if (EnabledTargetablePositions.Any()) if (staticTargetablePositions != null)
return enabledTargetableWorldPositions; return staticTargetablePositions;
if (enabledTargetablePositions.Any())
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
return new[] { CenterPosition }; return new[] { CenterPosition };
} }
@@ -540,7 +549,7 @@ namespace OpenRA
void UpdateConditionState(string condition, int token, bool isRevoke) void UpdateConditionState(string condition, int token, bool isRevoke)
{ {
var conditionState = conditionStates.GetOrAdd(condition); ConditionState conditionState = conditionStates.GetOrAdd(condition);
if (isRevoke) if (isRevoke)
conditionState.Tokens.Remove(token); conditionState.Tokens.Remove(token);
@@ -580,7 +589,7 @@ namespace OpenRA
public int RevokeCondition(int token) public int RevokeCondition(int token)
{ {
if (!conditionTokens.TryGetValue(token, out var condition)) if (!conditionTokens.TryGetValue(token, out var condition))
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}."); throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
conditionTokens.Remove(token); conditionTokens.Remove(token);
UpdateConditionState(condition, token, true); UpdateConditionState(condition, token, true);
@@ -606,8 +615,8 @@ namespace OpenRA
public LuaValue this[LuaRuntime runtime, LuaValue keyValue] public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
{ {
get => luaInterface.Value[runtime, keyValue]; get { return luaInterface.Value[runtime, keyValue]; }
set => luaInterface.Value[runtime, keyValue] = value; set { luaInterface.Value[runtime, keyValue] = value; }
} }
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right) public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
@@ -620,7 +629,7 @@ namespace OpenRA
public LuaValue ToString(LuaRuntime runtime) public LuaValue ToString(LuaRuntime runtime)
{ {
return $"Actor ({this})"; return "Actor ({0})".F(this);
} }
public bool HasScriptProperty(string name) public bool HasScriptProperty(string name)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -16,7 +16,7 @@ using OpenRA.Scripting;
namespace OpenRA namespace OpenRA
{ {
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos> public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
{ {
// Coordinates are packed in a 32 bit signed int // Coordinates are packed in a 32 bit signed int
// X and Y are 12 bits (signed): -2048...2047 // X and Y are 12 bits (signed): -2048...2047
@@ -25,13 +25,13 @@ namespace OpenRA
public readonly int Bits; public readonly int Bits;
// X is padded to MSB, so bit shift does the correct sign extension // X is padded to MSB, so bit shift does the correct sign extension
public int X => Bits >> 20; public int X { get { return Bits >> 20; } }
// Align Y with a short, cast, then shift the rest of the way // Align Y with a short, cast, then shift the rest of the way
// The signed short bit shift does the correct sign extension // The signed short bit shift does the correct sign extension
public int Y => ((short)(Bits >> 4)) >> 4; public int Y { get { return ((short)(Bits >> 4)) >> 4; } }
public byte Layer => (byte)Bits; public byte Layer { get { return (byte)Bits; } }
public CPos(int bits) { Bits = bits; } public CPos(int bits) { Bits = bits; }
public CPos(int x, int y) public CPos(int x, int y)
@@ -58,13 +58,7 @@ namespace OpenRA
public bool Equals(CPos other) { return Bits == other.Bits; } public bool Equals(CPos other) { return Bits == other.Bits; }
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); } public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
public override string ToString() public override string ToString() { return X + "," + Y; }
{
if (Layer == 0)
return X + "," + Y;
return X + "," + Y + "," + Layer;
}
public MPos ToMPos(Map map) public MPos ToMPos(Map map)
{ {
@@ -96,7 +90,7 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right) public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{ {
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b)) if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})"); throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b); return new LuaCustomClrObject(a + b);
} }
@@ -105,7 +99,7 @@ namespace OpenRA
{ {
var rightType = right.WrappedClrType(); var rightType = right.WrappedClrType();
if (!left.TryGetClrValue(out CPos a)) if (!left.TryGetClrValue(out CPos a))
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})"); throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
if (rightType == typeof(CPos)) if (rightType == typeof(CPos))
{ {
@@ -118,7 +112,7 @@ namespace OpenRA
return new LuaCustomClrObject(a - b); return new LuaCustomClrObject(a - b);
} }
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})"); throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
} }
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right) public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
@@ -138,11 +132,14 @@ namespace OpenRA
case "X": return X; case "X": return X;
case "Y": return Y; case "Y": return Y;
case "Layer": return Layer; case "Layer": return Layer;
default: throw new LuaException($"CPos does not define a member '{key}'"); default: throw new LuaException("CPos does not define a member '{0}'".F(key));
} }
} }
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value"); set
{
throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
}
} }
#endregion #endregion

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,7 +17,7 @@ using OpenRA.Scripting;
namespace OpenRA namespace OpenRA
{ {
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec> public struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
{ {
public readonly int X, Y; public readonly int X, Y;
@@ -42,8 +42,8 @@ namespace OpenRA
public CVec Sign() { return new CVec(Math.Sign(X), Math.Sign(Y)); } public CVec Sign() { return new CVec(Math.Sign(X), Math.Sign(Y)); }
public CVec Abs() { return new CVec(Math.Abs(X), Math.Abs(Y)); } public CVec Abs() { return new CVec(Math.Abs(X), Math.Abs(Y)); }
public int LengthSquared => X * X + Y * Y; public int LengthSquared { get { return X * X + Y * Y; } }
public int Length => Exts.ISqrt(LengthSquared); public int Length { get { return Exts.ISqrt(LengthSquared); } }
public CVec Clamp(Rectangle r) public CVec Clamp(Rectangle r)
{ {
@@ -76,7 +76,7 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right) public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{ {
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b)) if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})"); throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b); return new LuaCustomClrObject(a + b);
} }
@@ -84,7 +84,7 @@ namespace OpenRA
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right) public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{ {
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b)) if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})"); throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a - b); return new LuaCustomClrObject(a - b);
} }
@@ -110,11 +110,14 @@ namespace OpenRA
{ {
case "X": return X; case "X": return X;
case "Y": return Y; case "Y": return Y;
default: throw new LuaException($"CVec does not define a member '{key}'"); default: throw new LuaException("CVec does not define a member '{0}'".F(key));
} }
} }
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value"); set
{
throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
}
} }
#endregion #endregion

View File

@@ -0,0 +1,20 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
namespace OpenRA
{
public interface ICacheStorage<T>
{
void Remove(string key);
void Store(string key, T data);
T Retrieve(string key);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -15,6 +15,6 @@ namespace OpenRA
{ {
public class DefaultPlayer : IGlobalModData public class DefaultPlayer : IGlobalModData
{ {
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE); public readonly Color Color = Color.FromAhsl(0, 0, 238);
} }
} }

96
OpenRA.Game/Download.cs Normal file
View File

@@ -0,0 +1,96 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.ComponentModel;
using System.Net;
namespace OpenRA
{
public class Download
{
readonly object syncObject = new object();
WebClient wc;
public static string FormatErrorMessage(Exception e)
{
var ex = e as WebException;
if (ex == null)
return e.Message;
switch (ex.Status)
{
case WebExceptionStatus.RequestCanceled:
return "Cancelled";
case WebExceptionStatus.NameResolutionFailure:
return "DNS lookup failed";
case WebExceptionStatus.Timeout:
return "Connection timeout";
case WebExceptionStatus.ConnectFailure:
return "Cannot connect to remote server";
case WebExceptionStatus.ProtocolError:
return "File not found on remote server";
default:
return ex.Message;
}
}
void EnableTLS12OnWindows()
{
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
// so we must use the enum's constant value directly
if (Platform.CurrentPlatform == PlatformType.Windows)
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
}
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadFileAsync(new Uri(url), path);
}
}
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadDataAsync(new Uri(url));
}
}
void DisposeWebClient()
{
lock (syncObject)
{
wc.Dispose();
wc = null;
}
}
public void CancelAsync()
{
lock (syncObject)
wc?.CancelAsync();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,8 +17,8 @@ namespace OpenRA.Effects
{ {
public class AsyncAction : IEffect public class AsyncAction : IEffect
{ {
readonly Action a; Action a;
readonly IAsyncResult ar; IAsyncResult ar;
public AsyncAction(IAsyncResult ar, Action a) public AsyncAction(IAsyncResult ar, Action a)
{ {

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,7 +17,7 @@ namespace OpenRA.Effects
{ {
public class DelayedAction : IEffect public class DelayedAction : IEffect
{ {
readonly Action a; Action a;
int delay; int delay;
public DelayedAction(int delay, Action a) public DelayedAction(int delay, Action a)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -66,7 +66,12 @@ namespace OpenRA
// Several types of support directory types are available, depending on // Several types of support directory types are available, depending on
// how the player has installed and launched the game. // how the player has installed and launched the game.
// Read registration metadata from all of them // Read registration metadata from all of them
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System)) var sources = Enum.GetValues(typeof(SupportDirType))
.Cast<SupportDirType>()
.Select(t => Platform.GetSupportDir(t))
.Distinct();
foreach (var source in sources)
{ {
var metadataPath = Path.Combine(source, "ModMetadata"); var metadataPath = Path.Combine(source, "ModMetadata");
if (!Directory.Exists(metadataPath)) if (!Directory.Exists(metadataPath))
@@ -143,7 +148,7 @@ namespace OpenRA
if (stream != null) if (stream != null)
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes()))); yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
var sources = new HashSet<string>(); var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System)) if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.GetSupportDir(SupportDirType.System)); sources.Add(Platform.GetSupportDir(SupportDirType.System));
@@ -162,7 +167,7 @@ namespace OpenRA
LoadMod(yaml.Value, forceRegistration: true); LoadMod(yaml.Value, forceRegistration: true);
var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray(); var lines = new List<MiniYamlNode> { yaml }.ToLines().ToArray();
foreach (var source in sources) foreach (var source in sources.Distinct())
{ {
var metadataPath = Path.Combine(source, "ModMetadata"); var metadataPath = Path.Combine(source, "ModMetadata");
@@ -186,9 +191,23 @@ namespace OpenRA
/// * Filename doesn't match internal key /// * Filename doesn't match internal key
/// * Fails to parse as a mod registration /// * Fails to parse as a mod registration
/// </summary> /// </summary>
internal void ClearInvalidRegistrations(ModRegistration registration) internal void ClearInvalidRegistrations(ExternalMod activeMod, ModRegistration registration)
{ {
foreach (var source in GetSupportDirs(registration)) var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
{
// User support dir may be using the modern or legacy value, or overridden by the user
// Add all the possibilities and let the .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var activeModKey = ExternalMod.MakeKey(activeMod);
foreach (var source in sources.Distinct())
{ {
var metadataPath = Path.Combine(source, "ModMetadata"); var metadataPath = Path.Combine(source, "ModMetadata");
if (!Directory.Exists(metadataPath)) if (!Directory.Exists(metadataPath))
@@ -203,10 +222,13 @@ namespace OpenRA
var m = FieldLoader.Load<ExternalMod>(yaml); var m = FieldLoader.Load<ExternalMod>(yaml);
modKey = ExternalMod.MakeKey(m); modKey = ExternalMod.MakeKey(m);
// Continue to the next entry if it is the active mod (even if the LaunchPath is bogus)
if (modKey == activeModKey)
continue;
// Continue to the next entry if this one is valid // Continue to the next entry if this one is valid
// HACK: Explicitly invalidate paths to OpenRA.dll to clean up bogus metadata files if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
// that were created after the initial migration from .NET Framework to Core/5. !(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey && Path.GetExtension(m.LaunchPath) != ".dll")
continue; continue;
} }
catch (Exception e) catch (Exception e)
@@ -236,10 +258,23 @@ namespace OpenRA
internal void Unregister(Manifest mod, ModRegistration registration) internal void Unregister(Manifest mod, ModRegistration registration)
{ {
var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
{
// User support dir may be using the modern or legacy value, or overridden by the user
// Add all the possibilities and let the .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var key = ExternalMod.MakeKey(mod); var key = ExternalMod.MakeKey(mod);
mods.Remove(key); mods.Remove(key);
foreach (var source in GetSupportDirs(registration)) foreach (var source in sources.Distinct())
{ {
var path = Path.Combine(source, "ModMetadata", key + ".yaml"); var path = Path.Combine(source, "ModMetadata", key + ".yaml");
try try
@@ -255,33 +290,10 @@ namespace OpenRA
} }
} }
IEnumerable<string> GetSupportDirs(ModRegistration registration) public ExternalMod this[string key] { get { return mods[key]; } }
{ public int Count { get { return mods.Count; } }
var sources = new HashSet<string>(4); public ICollection<string> Keys { get { return mods.Keys; } }
if (registration.HasFlag(ModRegistration.System)) public ICollection<ExternalMod> Values { get { return mods.Values; } }
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
{
// User support dir may be using the modern or legacy value, or overridden by the user
// Add all the possibilities and let the HashSet ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
return sources;
}
public ExternalMod this[string key] => mods[key];
public int Count => mods.Count;
public ICollection<string> Keys => mods.Keys;
public ICollection<ExternalMod> Values => mods.Values;
IEnumerable<string> IReadOnlyDictionary<string, ExternalMod>.Keys => ((IReadOnlyDictionary<string, ExternalMod>)mods).Keys;
IEnumerable<ExternalMod> IReadOnlyDictionary<string, ExternalMod>.Values => ((IReadOnlyDictionary<string, ExternalMod>)mods).Values;
public bool ContainsKey(string key) { return mods.ContainsKey(key); } public bool ContainsKey(string key) { return mods.ContainsKey(key); }
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); } public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); } public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -38,6 +38,12 @@ namespace OpenRA
catch { return def; } catch { return def; }
} }
public static void Do<T>(this IEnumerable<T> e, Action<T> fn)
{
foreach (var ee in e)
fn(ee);
}
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); } public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
public static IEnumerable<string> GetNamespaces(this Assembly a) public static IEnumerable<string> GetNamespaces(this Assembly a)
@@ -47,7 +53,7 @@ namespace OpenRA
public static bool HasAttribute<T>(this MemberInfo mi) public static bool HasAttribute<T>(this MemberInfo mi)
{ {
return Attribute.IsDefined(mi, typeof(T)); return mi.GetCustomAttributes(typeof(T), true).Length != 0;
} }
public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit) public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit)
@@ -101,7 +107,7 @@ namespace OpenRA
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise) // - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
// - the triangles CAB and DAB must have opposite sense // - the triangles CAB and DAB must have opposite sense
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other // Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
// Assumes that lines are not collinear // Assumes that lines are not colinear
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d); return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
} }
@@ -153,9 +159,9 @@ namespace OpenRA
if (xs.Count == 0) if (xs.Count == 0)
{ {
if (throws) if (throws)
throw new ArgumentException("Collection must not be empty.", nameof(ts)); throw new ArgumentException("Collection must not be empty.", "ts");
else else
return default; return default(T);
} }
else else
return xs.ElementAt(r.Next(xs.Count)); return xs.ElementAt(r.Next(xs.Count));
@@ -230,9 +236,9 @@ namespace OpenRA
{ {
if (!e.MoveNext()) if (!e.MoveNext())
if (throws) if (throws)
throw new ArgumentException("Collection must not be empty.", nameof(ts)); throw new ArgumentException("Collection must not be empty.", "ts");
else else
return default; return default(T);
t = e.Current; t = e.Current;
u = selector(t); u = selector(t);
while (e.MoveNext()) while (e.MoveNext())
@@ -272,7 +278,7 @@ namespace OpenRA
public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor) public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
{ {
if (number < 0) if (number < 0)
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}"); throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
return (int)ISqrt((uint)number, round); return (int)ISqrt((uint)number, round);
} }
@@ -313,7 +319,7 @@ namespace OpenRA
public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor) public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
{ {
if (number < 0) if (number < 0)
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}"); throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
return (long)ISqrt((ulong)number, round); return (long)ISqrt((ulong)number, round);
} }
@@ -351,11 +357,6 @@ namespace OpenRA
return root; return root;
} }
public static int MultiplyBySqrtTwo(short number)
{
return number * 46341 / 32768;
}
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor) public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
{ {
var quotient = Math.DivRem(dividend, divisor, out var remainder); var quotient = Math.DivRem(dividend, divisor, out var remainder);
@@ -374,11 +375,6 @@ namespace OpenRA
return ts.Concat(moreTs); return ts.Concat(moreTs);
} }
public static IEnumerable<T> Exclude<T>(this IEnumerable<T> ts, params T[] exclusions)
{
return ts.Except(exclusions);
}
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source) public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{ {
return new HashSet<T>(source); return new HashSet<T>(source);
@@ -401,8 +397,7 @@ namespace OpenRA
// Try to build a dictionary and log all duplicates found (if any): // Try to build a dictionary and log all duplicates found (if any):
var dupKeys = new Dictionary<TKey, List<string>>(); var dupKeys = new Dictionary<TKey, List<string>>();
var capacity = source is ICollection<TSource> collection ? collection.Count : 0; var d = new Dictionary<TKey, TElement>();
var d = new Dictionary<TKey, TElement>(capacity);
foreach (var item in source) foreach (var item in source)
{ {
var key = keySelector(item); var key = keySelector(item);
@@ -413,28 +408,29 @@ namespace OpenRA
continue; continue;
// Check for a key conflict: // Check for a key conflict:
if (!d.TryAdd(key, element)) if (d.ContainsKey(key))
{ {
if (!dupKeys.TryGetValue(key, out var dupKeyMessages)) if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
{ {
// Log the initial conflicting value already inserted: // Log the initial conflicting value already inserted:
dupKeyMessages = new List<string> dupKeyMessages = new List<string>();
{ dupKeyMessages.Add(logValue(d[key]));
logValue(d[key])
};
dupKeys.Add(key, dupKeyMessages); dupKeys.Add(key, dupKeyMessages);
} }
// Log this conflicting value: // Log this conflicting value:
dupKeyMessages.Add(logValue(element)); dupKeyMessages.Add(logValue(element));
continue;
} }
d.Add(key, element);
} }
// If any duplicates were found, throw a descriptive error // If any duplicates were found, throw a descriptive error
if (dupKeys.Count > 0) if (dupKeys.Count > 0)
{ {
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]")); var badKeysFormatted = string.Join(", ", dupKeys.Select(p => "{0}: [{1}]".F(logKey(p.Key), string.Join(",", p.Value))));
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}"; var msg = "{0}, duplicate values found for the following keys: {1}".F(debugName, badKeysFormatted);
throw new ArgumentException(msg); throw new ArgumentException(msg);
} }
@@ -515,7 +511,8 @@ namespace OpenRA
public static bool IsTraitEnabled<T>(this T trait) public static bool IsTraitEnabled<T>(this T trait)
{ {
return !(trait is IDisabledTrait disabledTrait) || !disabledTrait.IsTraitDisabled; var disabledTrait = trait as IDisabledTrait;
return disabledTrait == null || !disabledTrait.IsTraitDisabled;
} }
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts) public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
@@ -525,7 +522,7 @@ namespace OpenRA
if (t.IsTraitEnabled()) if (t.IsTraitEnabled())
return t; return t;
return default; return default(T);
} }
public static T FirstEnabledTraitOrDefault<T>(this T[] ts) public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
@@ -535,72 +532,8 @@ namespace OpenRA
if (t.IsTraitEnabled()) if (t.IsTraitEnabled())
return t; return t;
return default; return default(T);
} }
public static T FirstEnabledConditionalTraitOrDefault<T>(this IEnumerable<T> ts) where T : IDisabledTrait
{
// PERF: Avoid LINQ.
foreach (var t in ts)
if (!t.IsTraitDisabled)
return t;
return default;
}
public static T FirstEnabledConditionalTraitOrDefault<T>(this T[] ts) where T : IDisabledTrait
{
// PERF: Avoid LINQ.
foreach (var t in ts)
if (!t.IsTraitDisabled)
return t;
return default;
}
public static LineSplitEnumerator SplitLines(this string str, char separator)
{
return new LineSplitEnumerator(str.AsSpan(), separator);
}
}
public ref struct LineSplitEnumerator
{
ReadOnlySpan<char> str;
readonly char separator;
public LineSplitEnumerator(ReadOnlySpan<char> str, char separator)
{
this.str = str;
this.separator = separator;
Current = default;
}
public LineSplitEnumerator GetEnumerator() => this;
public bool MoveNext()
{
var span = str;
// Reach the end of the string
if (span.Length == 0)
return false;
var index = span.IndexOf(separator);
if (index == -1)
{
// The remaining string is an empty string
str = ReadOnlySpan<char>.Empty;
Current = span;
return true;
}
Current = span.Slice(0, index);
str = span.Slice(index + 1);
return true;
}
public ReadOnlySpan<char> Current { get; private set; }
} }
public static class Enum<T> public static class Enum<T>
@@ -616,7 +549,7 @@ namespace OpenRA
if (values.Any(x => !names.Contains(x))) if (values.Any(x => !names.Contains(x)))
{ {
value = default; value = default(T);
return false; return false;
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,6 +17,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Support; using OpenRA.Support;
@@ -24,8 +25,6 @@ namespace OpenRA
{ {
public static class FieldLoader public static class FieldLoader
{ {
const char SplitComma = ',';
[Serializable] [Serializable]
public class MissingFieldsException : YamlException public class MissingFieldsException : YamlException
{ {
@@ -57,476 +56,26 @@ namespace OpenRA
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) => public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
{ {
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` "); throw new YamlException("FieldLoader: Cannot parse `{0}` into `{1}.{2}` ".F(s, f, t));
}; };
public static Action<string, Type> UnknownFieldAction = (s, f) => public static Action<string, Type> UnknownFieldAction = (s, f) =>
{ {
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`"); throw new NotImplementedException("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name));
}; };
static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo = static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo); new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache = static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression)); new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache = static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression)); new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers = static readonly object TranslationsLock = new object();
new Dictionary<Type, Func<string, Type, string, MemberInfo, object>>() static Dictionary<string, string> translations;
{
{ typeof(int), ParseInt },
{ typeof(ushort), ParseUShort },
{ typeof(long), ParseLong },
{ typeof(float), ParseFloat },
{ typeof(decimal), ParseDecimal },
{ typeof(string), ParseString },
{ typeof(Color), ParseColor },
{ typeof(Hotkey), ParseHotkey },
{ typeof(HotkeyReference), ParseHotkeyReference },
{ typeof(WDist), ParseWDist },
{ typeof(WVec), ParseWVec },
{ typeof(WVec[]), ParseWVecArray },
{ typeof(WPos), ParseWPos },
{ typeof(WAngle), ParseWAngle },
{ typeof(WRot), ParseWRot },
{ typeof(CPos), ParseCPos },
{ typeof(CVec), ParseCVec },
{ typeof(CVec[]), ParseCVecArray },
{ typeof(BooleanExpression), ParseBooleanExpression },
{ typeof(IntegerExpression), ParseIntegerExpression },
{ typeof(Enum), ParseEnum },
{ typeof(bool), ParseBool },
{ typeof(int2[]), ParseInt2Array },
{ typeof(Size), ParseSize },
{ typeof(int2), ParseInt2 },
{ typeof(float2), ParseFloat2 },
{ typeof(float3), ParseFloat3 },
{ typeof(Rectangle), ParseRectangle },
{ typeof(DateTime), ParseDateTime }
};
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
new Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>>()
{
{ typeof(HashSet<>), ParseHashSetOrList },
{ typeof(List<>), ParseHashSetOrList },
{ typeof(Dictionary<,>), ParseDictionary },
{ typeof(BitSet<>), ParseBitSet },
{ typeof(Nullable<>), ParseNullable },
};
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseUShort(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseLong(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01f : 1f);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseDecimal(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01m : 1m);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseString(string fieldName, Type fieldType, string value, MemberInfo field)
{
return value;
}
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && Color.TryParse(value, out var color))
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHotkey(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Hotkey.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHotkeyReference(string fieldName, Type fieldType, string value, MemberInfo field)
{
return Game.ModData.Hotkeys[value];
}
static object ParseWDist(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (WDist.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWVec(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WVec(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length % 3 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new WVec[parts.Length / 3];
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx)
&& WDist.TryParse(parts[3 * i + 1], out var ry)
&& WDist.TryParse(parts[3 * i + 2], out var rz))
vecs[i] = new WVec(rx, ry, rz);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWPos(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx)
&& WDist.TryParse(parts[1], out var ry)
&& WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWRot(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCPos(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
return new CPos(
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseByte(parts[2]));
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCVec(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new CVec[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx)
&& int.TryParse(parts[2 * i + 1], out var ry))
vecs[i] = new CVec(rx, ry);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseBooleanExpression(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
try
{
return BooleanExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseIntegerExpression(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
try
{
return IntegerExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseEnum(string fieldName, Type fieldType, string value, MemberInfo field)
{
try
{
return Enum.Parse(fieldType, value, true);
}
catch (ArgumentException)
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
return result;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseInt2Array(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var ints = new int2[parts.Length / 2];
for (var i = 0; i < ints.Length; i++)
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
return ints;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseSize(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseInt2(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return InvalidValueAction(value, fieldType, fieldName);
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat2(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
float xx = 0;
float yy = 0;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
return new float2(xx, yy);
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat3(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
// z component is optional for compatibility with older float2 definitions
float z = 0;
if (parts.Length > 2)
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
return new float3(x, y, z);
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseRectangle(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Rectangle(
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseIntegerInvariant(parts[2]),
Exts.ParseIntegerInvariant(parts[3]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseDateTime(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
return dt;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
return set;
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
var addArgs = new object[1];
for (var i = 0; i < parts.Length; i++)
{
addArgs[0] = GetValue(fieldName, arguments[0], parts[i].Trim(), field);
addMethod.Invoke(set, addArgs);
}
return set;
}
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
var dict = Activator.CreateInstance(fieldType);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
var addArgs = new object[2];
foreach (var node in yaml.Nodes)
{
addArgs[0] = GetValue(fieldName, arguments[0], node.Key, field);
addArgs[1] = GetValue(fieldName, arguments[1], node.Value, field);
addMethod.Invoke(dict, addArgs);
}
return dict;
}
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
public static void Load(object self, MiniYaml my) public static void Load(object self, MiniYaml my)
{ {
@@ -564,7 +113,7 @@ namespace OpenRA
fli.Field.SetValue(self, val); fli.Field.SetValue(self, val);
} }
if (missing.Count > 0) if (missing.Any())
throw new MissingFieldsException(missing.ToArray()); throw new MissingFieldsException(missing.ToArray());
} }
@@ -631,46 +180,402 @@ namespace OpenRA
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field) public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
{ {
var value = yaml.Value?.Trim(); var value = yaml.Value?.Trim();
if (fieldType.IsGenericType)
{
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
return parseFuncGeneric(fieldName, fieldType, value, yaml, field);
}
else
{
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
return parseFunc(fieldName, fieldType, value, field);
if (fieldType.IsArray && fieldType.GetArrayRank() == 1) if (fieldType == typeof(int))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(ushort))
{
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
if (fieldType == typeof(long))
{
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float))
{
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01f : 1f);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(decimal))
{
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01m : 1m);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(string))
{
if (field != null && MemberHasTranslateAttribute[field] && value != null)
return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
return value;
}
else if (fieldType == typeof(Color))
{
if (value != null && Color.TryParse(value, out var color))
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(Hotkey))
{
if (Hotkey.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(HotkeyReference))
{
return Game.ModData.Hotkeys[value];
}
else if (fieldType == typeof(WDist))
{
if (WDist.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WVec))
{
if (value != null)
{ {
if (value == null) var parts = value.Split(',');
return Array.CreateInstance(fieldType.GetElementType(), 0); if (parts.Length == 3)
{
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ? if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries; return new WVec(rx, ry, rz);
var parts = value.Split(SplitComma, options); }
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
for (var i = 0; i < parts.Length; i++)
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
} }
}
var conv = TypeDescriptor.GetConverter(fieldType); return InvalidValueAction(value, fieldType, fieldName);
if (conv.CanConvertFrom(typeof(string))) }
else if (fieldType == typeof(WVec[]))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length % 3 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new WVec[parts.Length / 3];
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
vecs[i] = new WVec(rx, ry, rz);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WPos))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WAngle))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WRot))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CPos))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CVec))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CVec[]))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new CVec[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
vecs[i] = new CVec(rx, ry);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(BooleanExpression))
{
if (value != null)
{
try
{
return BooleanExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(IntegerExpression))
{
if (value != null)
{
try
{
return IntegerExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsEnum)
{ {
try try
{ {
return conv.ConvertFromInvariantString(value); return Enum.Parse(fieldType, value, true);
} }
catch catch (ArgumentException)
{ {
return InvalidValueAction(value, fieldType, fieldName); return InvalidValueAction(value, fieldType, fieldName);
} }
} }
else if (fieldType == typeof(bool))
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
return result;
UnknownFieldAction($"[Type] {value}", fieldType); return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(int2[]))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var ints = new int2[parts.Length / 2];
for (var i = 0; i < ints.Length; i++)
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
return ints;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
{
if (value == null)
return Array.CreateInstance(fieldType.GetElementType(), 0);
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
var parts = value.Split(new char[] { ',' }, options);
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
for (var i = 0; i < parts.Length; i++)
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
}
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
return set;
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var addMethod = fieldType.GetMethod("Add", fieldType.GetGenericArguments());
for (var i = 0; i < parts.Length; i++)
addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) });
return set;
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
var dict = Activator.CreateInstance(fieldType);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod("Add", arguments);
foreach (var node in yaml.Nodes)
{
var key = GetValue(fieldName, arguments[0], node.Key, field);
var val = GetValue(fieldName, arguments[1], node.Value, field);
addMethod.Invoke(dict, new[] { key, val });
}
return dict;
}
else if (fieldType == typeof(Size))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(int2))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return InvalidValueAction(value, fieldType, fieldName);
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float2))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float xx = 0;
float yy = 0;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
return new float2(xx, yy);
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float3))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
// z component is optional for compatibility with older float2 definitions
float z = 0;
if (parts.Length > 2)
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
return new float3(x, y, z);
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(Rectangle))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new Rectangle(
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseIntegerInvariant(parts[2]),
Exts.ParseIntegerInvariant(parts[3]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
else if (fieldType == typeof(DateTime))
{
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
return dt;
return InvalidValueAction(value, fieldType, fieldName);
}
else
{
var conv = TypeDescriptor.GetConverter(fieldType);
if (conv.CanConvertFrom(typeof(string)))
{
try
{
return conv.ConvertFromInvariantString(value);
}
catch
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
}
UnknownFieldAction("[Type] {0}".F(value), fieldType);
return null; return null;
} }
@@ -756,7 +661,7 @@ namespace OpenRA
{ {
public static readonly SerializeAttribute Default = new SerializeAttribute(true); public static readonly SerializeAttribute Default = new SerializeAttribute(true);
public bool IsDefault => this == Default; public bool IsDefault { get { return this == Default; } }
public readonly bool Serialize; public readonly bool Serialize;
public string YamlName; public string YamlName;
@@ -781,7 +686,7 @@ namespace OpenRA
{ {
var method = type.GetMethod(Loader, Flags); var method = type.GetMethod(Loader, Flags);
if (method == null) if (method == null)
throw new InvalidOperationException($"{type.Name} does not specify a loader function '{Loader}'"); throw new InvalidOperationException("{0} does not specify a loader function '{1}'".F(type.Name, Loader));
return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), method); return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), method);
} }
@@ -789,8 +694,34 @@ namespace OpenRA
return null; return null;
} }
} }
public static string Translate(string key)
{
if (string.IsNullOrEmpty(key))
return key;
lock (TranslationsLock)
{
if (translations == null)
return key;
if (!translations.TryGetValue(key, out var value))
return key;
return value;
}
}
public static void SetTranslations(IDictionary<string, string> translations)
{
lock (TranslationsLock)
FieldLoader.translations = new Dictionary<string, string>(translations);
}
} }
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class TranslateAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
{ {

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -72,14 +72,32 @@ namespace OpenRA
return ""; return "";
var t = v.GetType(); var t = v.GetType();
if (t == typeof(Color))
{
return ((Color)v).ToString();
}
if (t == typeof(Rectangle))
{
var r = (Rectangle)v;
return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height);
}
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>)) if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
{
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", "); return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
}
if (t.IsArray && t.GetArrayRank() == 1) if (t.IsArray && t.GetArrayRank() == 1)
{
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", "); return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>))) if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
{
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", "); return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
// This is only for documentation generation // This is only for documentation generation
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
@@ -94,14 +112,17 @@ namespace OpenRA
var formattedKey = FormatValue(key); var formattedKey = FormatValue(key);
var formattedValue = FormatValue(value); var formattedValue = FormatValue(value);
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}"; result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine);
} }
return result; return result;
} }
if (v is DateTime d) if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
return d.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture); return ""; // TODO
if (t == typeof(DateTime))
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
// Try the TypeConverter // Try the TypeConverter
var conv = TypeDescriptor.GetConverter(t); var conv = TypeDescriptor.GetConverter(t);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,7 +17,6 @@ using System.Net;
using System.Text; using System.Text;
using ICSharpCode.SharpZipLib.Checksum; using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using OpenRA.Graphics;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.FileFormats namespace OpenRA.FileFormats
@@ -26,15 +25,12 @@ namespace OpenRA.FileFormats
{ {
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
public int Width { get; } public int Width { get; set; }
public int Height { get; } public int Height { get; set; }
public Color[] Palette { get; } public Color[] Palette { get; set; }
public byte[] Data { get; } public byte[] Data { get; set; }
public SpriteFrameType Type { get; }
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>(); public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
public Png(Stream s) public Png(Stream s)
{ {
if (!Verify(s)) if (!Verify(s))
@@ -42,8 +38,9 @@ namespace OpenRA.FileFormats
s.Position += 8; s.Position += 8;
var headerParsed = false; var headerParsed = false;
var isPaletted = false;
var is24Bit = false;
var data = new List<byte>(); var data = new List<byte>();
Type = SpriteFrameType.Rgba32;
while (true) while (true)
{ {
@@ -68,12 +65,14 @@ namespace OpenRA.FileFormats
var bitDepth = ms.ReadUInt8(); var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadByte(); var colorType = (PngColorType)ms.ReadByte();
if (IsPaletted(bitDepth, colorType)) isPaletted = IsPaletted(bitDepth, colorType);
Type = SpriteFrameType.Indexed8; is24Bit = colorType == PngColorType.Color;
else if (colorType == PngColorType.Color)
Type = SpriteFrameType.Rgb24;
Data = new byte[Width * Height * PixelStride]; var dataLength = Width * Height;
if (!isPaletted)
dataLength *= 4;
Data = new byte[dataLength];
var compression = ms.ReadByte(); var compression = ms.ReadByte();
/*var filter = */ms.ReadByte(); /*var filter = */ms.ReadByte();
@@ -134,28 +133,39 @@ namespace OpenRA.FileFormats
{ {
using (var ds = new InflaterInputStream(ns)) using (var ds = new InflaterInputStream(ns))
{ {
var pxStride = PixelStride; var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
var rowStride = Width * pxStride; var srcStride = Width * pxStride;
var destStride = Width * (isPaletted ? 1 : 4);
var prevLine = new byte[rowStride]; var prevLine = new byte[srcStride];
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
var filter = (PngFilter)ds.ReadByte(); var filter = (PngFilter)ds.ReadByte();
var line = ds.ReadBytes(rowStride); var line = ds.ReadBytes(srcStride);
for (var i = 0; i < rowStride; i++) for (var i = 0; i < srcStride; i++)
line[i] = i < pxStride line[i] = i < pxStride
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0) ? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]); : UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
Array.Copy(line, 0, Data, y * rowStride, rowStride); if (is24Bit)
{
// Fold alpha channel into RGB data
for (var i = 0; i < line.Length / 3; i++)
{
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
Data[y * destStride + 4 * i + 3] = 255;
}
}
else
Array.Copy(line, 0, Data, y * destStride, line.Length);
prevLine = line; prevLine = line;
} }
} }
} }
if (Type == SpriteFrameType.Indexed8 && Palette == null) if (isPaletted && Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported."); throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
return; return;
@@ -165,7 +175,7 @@ namespace OpenRA.FileFormats
} }
} }
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null, public Png(byte[] data, int width, int height, Color[] palette = null,
Dictionary<string, string> embeddedData = null) Dictionary<string, string> embeddedData = null)
{ {
var expectLength = width * height; var expectLength = width * height;
@@ -175,46 +185,11 @@ namespace OpenRA.FileFormats
if (data.Length != expectLength) if (data.Length != expectLength)
throw new InvalidDataException("Input data does not match expected length"); throw new InvalidDataException("Input data does not match expected length");
Type = type;
Width = width; Width = width;
Height = height; Height = height;
switch (type) Palette = palette;
{ Data = data;
case SpriteFrameType.Indexed8:
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
// Data is already in a compatible format
Data = data;
if (type == SpriteFrameType.Indexed8)
Palette = palette;
break;
}
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
{
// Convert to big endian
Data = new byte[data.Length];
var stride = PixelStride;
for (var i = 0; i < width * height; i++)
{
Data[stride * i] = data[stride * i + 2];
Data[stride * i + 1] = data[stride * i + 1];
Data[stride * i + 2] = data[stride * i + 0];
if (type == SpriteFrameType.Bgra32)
Data[stride * i + 3] = data[stride * i + 3];
}
break;
}
default:
throw new InvalidDataException($"Unhandled SpriteFrameType {type}");
}
if (embeddedData != null) if (embeddedData != null)
EmbeddedData = embeddedData; EmbeddedData = embeddedData;
@@ -299,8 +274,9 @@ namespace OpenRA.FileFormats
header.Write(IPAddress.HostToNetworkOrder(Height)); header.Write(IPAddress.HostToNetworkOrder(Height));
header.WriteByte(8); // Bit depth header.WriteByte(8); // Bit depth
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color : var colorType = Palette != null
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha; ? PngColorType.Indexed | PngColorType.Color
: PngColorType.Color | PngColorType.Alpha;
header.WriteByte((byte)colorType); header.WriteByte((byte)colorType);
header.WriteByte(0); // Compression header.WriteByte(0); // Compression
@@ -310,7 +286,7 @@ namespace OpenRA.FileFormats
WritePngChunk(output, "IHDR", header); WritePngChunk(output, "IHDR", header);
} }
var alphaPalette = false; bool alphaPalette = false;
if (Palette != null) if (Palette != null)
{ {
using (var palette = new MemoryStream()) using (var palette = new MemoryStream())
@@ -342,12 +318,12 @@ namespace OpenRA.FileFormats
{ {
using (var compressed = new DeflaterOutputStream(data)) using (var compressed = new DeflaterOutputStream(data))
{ {
var rowStride = Width * PixelStride; var stride = Width * (Palette != null ? 1 : 4);
for (var y = 0; y < Height; y++) for (var y = 0; y < Height; y++)
{ {
// Write uncompressed scanlines for simplicity // Write uncompressed scanlines for simplicity
compressed.WriteByte(0); compressed.WriteByte(0);
compressed.Write(Data, y * rowStride, rowStride); compressed.Write(Data, y * stride, stride);
} }
compressed.Flush(); compressed.Flush();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -28,7 +28,7 @@ namespace OpenRA.FileFormats
public ReplayMetadata(GameInformation info) public ReplayMetadata(GameInformation info)
{ {
if (info == null) if (info == null)
throw new ArgumentNullException(nameof(info)); throw new ArgumentNullException("info");
GameInfo = info; GameInfo = info;
} }
@@ -44,7 +44,7 @@ namespace OpenRA.FileFormats
// Read version // Read version
var version = fs.ReadInt32(); var version = fs.ReadInt32();
if (version != MetaVersion) if (version != MetaVersion)
throw new NotSupportedException($"Metadata version {version} is not supported"); throw new NotSupportedException("Metadata version {0} is not supported".F(version));
// Read game info (max 100K limit as a safeguard against corrupted files) // Read game info (max 100K limit as a safeguard against corrupted files)
var data = fs.ReadString(Encoding.UTF8, 1024 * 100); var data = fs.ReadString(Encoding.UTF8, 1024 * 100);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -28,7 +28,7 @@ namespace OpenRA.FileSystem
public class FileSystem : IReadOnlyFileSystem public class FileSystem : IReadOnlyFileSystem
{ {
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys; public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>(); readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>(); readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
readonly string modID; readonly string modID;
@@ -63,7 +63,7 @@ namespace OpenRA.FileSystem
{ {
// Raw directories are the easiest and one of the most common cases, so try these first // Raw directories are the easiest and one of the most common cases, so try these first
var resolvedPath = Platform.ResolvePath(filename); var resolvedPath = Platform.ResolvePath(filename);
if (!resolvedPath.Contains('|') && Directory.Exists(resolvedPath)) if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
return new Folder(resolvedPath); return new Folder(resolvedPath);
// Children of another package require special handling // Children of another package require special handling
@@ -95,7 +95,7 @@ namespace OpenRA.FileSystem
name = name.Substring(1); name = name.Substring(1);
if (!installedMods.TryGetValue(name, out var mod)) if (!installedMods.TryGetValue(name, out var mod))
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}"); throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
package = mod.Package; package = mod.Package;
modPackages.Add(package); modPackages.Add(package);
@@ -104,7 +104,7 @@ namespace OpenRA.FileSystem
{ {
package = OpenPackage(name); package = OpenPackage(name);
if (package == null) if (package == null)
throw new InvalidOperationException($"Could not open package '{name}', file not found or its format is not supported."); throw new InvalidOperationException("Could not open package '{0}', file not found or its format is not supported.".F(name));
} }
Mount(package, explicitName); Mount(package, explicitName);
@@ -203,7 +203,7 @@ namespace OpenRA.FileSystem
public Stream Open(string filename) public Stream Open(string filename)
{ {
if (!TryOpen(filename, out var s)) if (!TryOpen(filename, out var s))
throw new FileNotFoundException($"File not found: {filename}", filename); throw new FileNotFoundException("File not found: {0}".F(filename), filename);
return s; return s;
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -27,7 +27,7 @@ namespace OpenRA.FileSystem
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
public string Name => path; public string Name { get { return path; } }
public IEnumerable<string> Contents public IEnumerable<string> Contents
{ {

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -55,8 +55,7 @@ namespace OpenRA.FileSystem
get get
{ {
foreach (ZipEntry entry in pkg) foreach (ZipEntry entry in pkg)
if (entry.IsFile) yield return entry.Name;
yield return entry.Name;
} }
} }
@@ -142,8 +141,8 @@ namespace OpenRA.FileSystem
sealed class ZipFolder : IReadOnlyPackage sealed class ZipFolder : IReadOnlyPackage
{ {
public string Name => path; public string Name { get { return path; } }
public ReadOnlyZipFile Parent { get; } public ReadOnlyZipFile Parent { get; private set; }
readonly string path; readonly string path;
public ZipFolder(ReadOnlyZipFile parent, string path) public ZipFolder(ReadOnlyZipFile parent, string path)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -22,7 +22,7 @@ namespace OpenRA
public class Fonts : IGlobalModData public class Fonts : IGlobalModData
{ {
[FieldLoader.LoadUsing(nameof(LoadFonts))] [FieldLoader.LoadUsing("LoadFonts")]
public readonly Dictionary<string, FontData> FontList; public readonly Dictionary<string, FontData> FontList;
static object LoadFonts(MiniYaml y) static object LoadFonts(MiniYaml y)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -16,8 +16,10 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Reflection;
using System.Runtime; using System.Runtime;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Primitives; using OpenRA.Primitives;
@@ -29,6 +31,8 @@ namespace OpenRA
{ {
public static class Game public static class Game
{ {
public const int NetTickScale = 3; // 120 ms net tick for 40 ms local tick
public const int Timestep = 40;
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
public static InstalledMods Mods { get; private set; } public static InstalledMods Mods { get; private set; }
@@ -38,7 +42,6 @@ namespace OpenRA
public static Settings Settings; public static Settings Settings;
public static CursorManager Cursor; public static CursorManager Cursor;
public static bool HideCursor; public static bool HideCursor;
static WorldRenderer worldRenderer; static WorldRenderer worldRenderer;
static string modLaunchWrapper; static string modLaunchWrapper;
@@ -53,6 +56,7 @@ namespace OpenRA
public static string EngineVersion { get; private set; } public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile; public static LocalPlayerProfile LocalPlayerProfile;
static Task discoverNat;
static bool takeScreenshot = false; static bool takeScreenshot = false;
static Benchmark benchmark = null; static Benchmark benchmark = null;
@@ -60,18 +64,12 @@ namespace OpenRA
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true) public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
{ {
var newConnection = new NetworkConnection(endpoint); var connection = new NetworkConnection(endpoint);
if (recordReplay) if (recordReplay)
newConnection.StartRecording(() => { return TimestampedFilename(); }); connection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(newConnection); var om = new OrderManager(endpoint, password, connection);
JoinInner(om); JoinInner(om);
CurrentServerSettings.Password = password;
CurrentServerSettings.Target = endpoint;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager, password, newConnection);
return om; return om;
} }
@@ -83,56 +81,34 @@ namespace OpenRA
static void JoinInner(OrderManager om) static void JoinInner(OrderManager om)
{ {
// Refresh TextNotificationsManager before the game starts. OrderManager?.Dispose();
TextNotificationsManager.Clear();
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
// a lobby, while keeping the OrderManager that runs the shellmap intact.
// A matching check in World.Dispose (which is called by WorldRenderer.Dispose) makes sure that we dispose
// the shellmap's OM when a lobby game actually starts.
if (OrderManager?.World == null || OrderManager.World.Type != WorldType.Shellmap)
OrderManager?.Dispose();
OrderManager = om; OrderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager);
} }
public static void JoinReplay(string replayFile) public static void JoinReplay(string replayFile)
{ {
JoinInner(new OrderManager(new ReplayConnection(replayFile))); JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
} }
static void JoinLocal() static void JoinLocal()
{ {
JoinInner(new OrderManager(new EchoConnection())); JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
// Add a spectator client for the local player
// On the shellmap this player is controlling the map via scripted orders
OrderManager.LobbyInfo.Clients.Add(new Session.Client
{
Index = OrderManager.Connection.LocalClientId,
Name = Settings.Player.Name,
PreferredColor = Settings.Player.Color,
Color = Settings.Player.Color,
Faction = "Random",
SpawnPoint = 0,
Team = 0,
State = Session.ClientState.Ready
});
} }
// More accurate replacement for Environment.TickCount // More accurate replacement for Environment.TickCount
static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); static Stopwatch stopwatch = Stopwatch.StartNew();
public static long RunTime => Stopwatch.ElapsedMilliseconds; public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
public static int RenderFrame = 0; public static int RenderFrame = 0;
public static int NetFrameNumber => OrderManager.NetFrameNumber; public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick => OrderManager.LocalFrameNumber; public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { }; public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<OrderManager, string, NetworkConnection> ConnectionStateChanged = (om, pass, conn) => { }; public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting; static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId => OrderManager.Connection.LocalClientId; public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static void RemoteDirectConnect(ConnectionTarget endpoint) public static void RemoteDirectConnect(ConnectionTarget endpoint)
{ {
@@ -188,7 +164,6 @@ namespace OpenRA
using (new PerfTimer("PrepareMap")) using (new PerfTimer("PrepareMap"))
map = ModData.PrepareMap(mapUID); map = ModData.PrepareMap(mapUID);
using (new PerfTimer("NewWorld")) using (new PerfTimer("NewWorld"))
OrderManager.World = new World(ModData, map, OrderManager, type); OrderManager.World = new World(ModData, map, OrderManager, type);
@@ -211,9 +186,11 @@ namespace OpenRA
Ui.MouseFocusWidget = null; Ui.MouseFocusWidget = null;
Ui.KeyboardFocusWidget = null; Ui.KeyboardFocusWidget = null;
OrderManager.LocalFrameNumber = 0;
OrderManager.LastTickTime = RunTime;
OrderManager.StartGame(); OrderManager.StartGame();
worldRenderer.RefreshPalette(); worldRenderer.RefreshPalette();
Cursor.SetCursor(ChromeMetrics.Get<string>("DefaultCursor")); Cursor.SetCursor("default");
// Now loading is completed, now is the ideal time to run a GC and compact the LOH. // Now loading is completed, now is the ideal time to run a GC and compact the LOH.
// - All the temporary garbage created during loading can be collected. // - All the temporary garbage created during loading can be collected.
@@ -229,26 +206,15 @@ namespace OpenRA
public static void RestartGame() public static void RestartGame()
{ {
var replay = OrderManager.Connection as ReplayConnection; var replay = OrderManager.Connection as ReplayConnection;
var replayName = replay?.Filename; var replayName = replay != null ? replay.Filename : null;
var lobbyInfo = OrderManager.LobbyInfo; var lobbyInfo = OrderManager.LobbyInfo;
// Reseed the RNG so this isn't an exact repeat of the last game // Reseed the RNG so this isn't an exact repeat of the last game
lobbyInfo.GlobalSettings.RandomSeed = CosmeticRandom.Next(); lobbyInfo.GlobalSettings.RandomSeed = CosmeticRandom.Next();
// Note: the map may have been changed on disk outside the game, changing its UID.
// Use the updated UID if we have tracked the update instead of failing.
lobbyInfo.GlobalSettings.Map = ModData.MapCache.GetUpdatedMap(lobbyInfo.GlobalSettings.Map);
if (lobbyInfo.GlobalSettings.Map == null)
{
Disconnect();
Ui.ResetAll();
LoadShellMap();
return;
}
var orders = new[] var orders = new[]
{ {
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"), Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
Order.Command("startgame") Order.Command("startgame")
}; };
@@ -318,7 +284,7 @@ namespace OpenRA
if (!string.IsNullOrEmpty(supportDirArg)) if (!string.IsNullOrEmpty(supportDirArg))
Platform.OverrideSupportDir(supportDirArg); Platform.OverrideSupportDir(supportDirArg);
Console.WriteLine($"Platform is {Platform.CurrentPlatform} ({Platform.CurrentArchitecture})"); Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
// Load the engine version as early as possible so it can be written to exception logs // Load the engine version as early as possible so it can be written to exception logs
try try
@@ -330,13 +296,13 @@ namespace OpenRA
if (string.IsNullOrEmpty(EngineVersion)) if (string.IsNullOrEmpty(EngineVersion))
EngineVersion = "Unknown"; EngineVersion = "Unknown";
Console.WriteLine($"Engine version is {EngineVersion}"); Console.WriteLine("Engine version is {0}", EngineVersion);
Console.WriteLine($"Runtime: {Platform.RuntimeVersion}"); Console.WriteLine("Runtime: {0}", Platform.RuntimeVersion);
// Special case handling of Game.Mod argument: if it matches a real filesystem path // Special case handling of Game.Mod argument: if it matches a real filesystem path
// then we use this to override the mod search path, and replace it with the mod id // then we use this to override the mod search path, and replace it with the mod id
var modID = args.GetValue("Game.Mod", null); var modID = args.GetValue("Game.Mod", null);
var explicitModPaths = Array.Empty<string>(); var explicitModPaths = new string[0];
if (modID != null && (File.Exists(modID) || Directory.Exists(modID))) if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
{ {
explicitModPaths = new[] { modID }; explicitModPaths = new[] { modID };
@@ -365,13 +331,12 @@ namespace OpenRA
{ {
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll"); var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
#if NET5_0_OR_GREATER #if !MONO
var loader = new AssemblyLoader(rendererPath); var loader = new AssemblyLoader(rendererPath);
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t)); var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#else #else
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above var assembly = Assembly.LoadFile(rendererPath);
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t)); var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#endif #endif
@@ -386,7 +351,7 @@ namespace OpenRA
} }
catch (Exception e) catch (Exception e)
{ {
Log.Write("graphics", $"{e}"); Log.Write("graphics", "{0}", e);
Console.WriteLine("Renderer initialization failed. Check graphics.log for details."); Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
Renderer?.Dispose(); Renderer?.Dispose();
@@ -395,7 +360,8 @@ namespace OpenRA
} }
} }
Nat.Initialize(); if (Settings.Server.DiscoverNatDevices)
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null); var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ? var modSearchPaths = modSearchArg != null ?
@@ -405,7 +371,7 @@ namespace OpenRA
Mods = new InstalledMods(modSearchPaths, explicitModPaths); Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:"); Console.WriteLine("Internal mods:");
foreach (var mod in Mods) foreach (var mod in Mods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})"); Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null); modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
@@ -421,16 +387,24 @@ namespace OpenRA
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"') if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath.Substring(1, launchPath.Length - 2); launchPath = launchPath.Substring(1, launchPath.Length - 2);
// Metadata registration requires an explicit launch path if (launchPath == null)
if (launchPath != null) {
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User); // When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
// Platform-specific launchers are expected to manage this internally.
launchPath = Assembly.GetEntryAssembly().Location;
if (!string.IsNullOrEmpty(engineDirArg))
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
}
ExternalMods.ClearInvalidRegistrations(ModRegistration.User); ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
} }
Console.WriteLine("External mods:"); Console.WriteLine("External mods:");
foreach (var mod in ExternalMods) foreach (var mod in ExternalMods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})"); Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
InitializeMod(modID, args); InitializeMod(modID, args);
} }
@@ -439,7 +413,7 @@ namespace OpenRA
{ {
// Clear static state if we have switched mods // Clear static state if we have switched mods
LobbyInfoChanged = () => { }; LobbyInfoChanged = () => { };
ConnectionStateChanged = (om, p, conn) => { }; ConnectionStateChanged = om => { };
BeforeGameStart = () => { }; BeforeGameStart = () => { };
OnRemoteDirectConnect = endpoint => { }; OnRemoteDirectConnect = endpoint => { };
delayedActions = new ActionQueue(); delayedActions = new ActionQueue();
@@ -463,9 +437,9 @@ namespace OpenRA
throw new InvalidOperationException("Game.Mod argument missing."); throw new InvalidOperationException("Game.Mod argument missing.");
if (!Mods.ContainsKey(mod)) if (!Mods.ContainsKey(mod))
throw new InvalidOperationException($"Unknown or invalid mod '{mod}'."); throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
Console.WriteLine($"Loading mod: {mod}"); Console.WriteLine("Loading mod: {0}", mod);
Sound.StopVideo(); Sound.StopVideo();
@@ -476,21 +450,18 @@ namespace OpenRA
if (!ModData.LoadScreen.BeforeLoad()) if (!ModData.LoadScreen.BeforeLoad())
return; return;
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);
using (new PerfTimer("LoadMaps")) using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps(); ModData.MapCache.LoadMaps();
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null; var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
Renderer.InitializeDepthBuffer(grid); Renderer.InitializeDepthBuffer(grid);
Cursor?.Dispose(); Cursor?.Dispose();
Cursor = new CursorManager(ModData.CursorProvider);
var metadata = ModData.Manifest.Metadata; Cursor = new CursorManager(ModData.CursorProvider);
if (!string.IsNullOrEmpty(metadata.WindowTitle))
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
PerfHistory.Items["render"].HasNormalTick = false; PerfHistory.Items["render"].HasNormalTick = false;
PerfHistory.Items["batches"].HasNormalTick = false; PerfHistory.Items["batches"].HasNormalTick = false;
@@ -501,18 +472,31 @@ namespace OpenRA
JoinLocal(); JoinLocal();
try
{
discoverNat?.Wait();
}
catch (Exception e)
{
Console.WriteLine("NAT discovery failed: {0}", e.Message);
Log.Write("nat", e.ToString());
}
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
ModData.LoadScreen.StartGame(args); ModData.LoadScreen.StartGame(args);
} }
public static void LoadEditor(string mapUid) public static void LoadEditor(string mapUid)
{ {
JoinLocal();
StartGame(mapUid, WorldType.Editor); StartGame(mapUid, WorldType.Editor);
} }
public static void LoadShellMap() public static void LoadShellMap()
{ {
var shellmap = ChooseShellmap(); var shellmap = ChooseShellmap();
using (new PerfTimer("StartGame")) using (new PerfTimer("StartGame"))
{ {
StartGame(shellmap, WorldType.Shellmap); StartGame(shellmap, WorldType.Shellmap);
@@ -567,13 +551,11 @@ namespace OpenRA
// Note: These delayed actions should only be used by widgets or disposing objects // Note: These delayed actions should only be used by widgets or disposing objects
// - things that depend on a particular world should be queuing them on the world actor. // - things that depend on a particular world should be queuing them on the world actor.
static volatile ActionQueue delayedActions = new ActionQueue(); static volatile ActionQueue delayedActions = new ActionQueue();
static Color systemMessageColor = Color.White;
static Color chatMessageColor = Color.White;
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); } public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); } public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
[TranslationReference("filename")]
static readonly string SavedScreenshot = "saved-screenshot";
static void TakeScreenshotInner() static void TakeScreenshotInner()
{ {
using (new PerfTimer("Renderer.SaveScreenshot")) using (new PerfTimer("Renderer.SaveScreenshot"))
@@ -587,7 +569,7 @@ namespace OpenRA
Log.Write("debug", "Taking screenshot " + path); Log.Write("debug", "Taking screenshot " + path);
Renderer.SaveScreenshot(path); Renderer.SaveScreenshot(path);
TextNotificationsManager.Debug(ModData.Translation.GetString(SavedScreenshot, Translation.Arguments("filename", filename))); Debug("Saved screenshot " + filename);
} }
} }
@@ -597,29 +579,48 @@ namespace OpenRA
var world = orderManager.World; var world = orderManager.World;
if (Ui.LastTickTime.ShouldAdvance(tick)) var uiTickDelta = tick - Ui.LastTickTime;
if (uiTickDelta >= Timestep)
{ {
Ui.LastTickTime.AdvanceTickTime(tick); // Explained below for the world tick calculation
Sync.RunUnsynced(world, Ui.Tick); var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
Cursor.Tick(); Cursor.Tick();
} }
if (orderManager.LastTickTime.ShouldAdvance(tick)) var worldTimestep = world == null ? Timestep : world.IsLoadingGameSave ? 1 : world.Timestep;
var worldTickDelta = tick - orderManager.LastTickTime;
if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
{ {
using (new PerfSample("tick_time")) using (new PerfSample("tick_time"))
{ {
orderManager.LastTickTime.AdvanceTickTime(tick); // Tick the world to advance the world time to match real time:
// If dt < TickJankThreshold then we should try and catch up by repeatedly ticking
// If dt >= TickJankThreshold then we should accept the jank and progress at the normal rate
// dt is rounded down to an integer tick count in order to preserve fractional tick components.
var integralTickTimestep = (worldTickDelta / worldTimestep) * worldTimestep;
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
Sound.Tick(); Sound.Tick();
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
Sync.RunUnsynced(world, orderManager.TickImmediate);
if (world == null) if (world == null)
return; return;
if (orderManager.TryTick()) var isNetTick = LocalTick % NetTickScale == 0;
if (!isNetTick || orderManager.IsReadyForNextFrame)
{ {
Sync.RunUnsynced(world, () => ++orderManager.LocalFrameNumber;
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
if (isNetTick)
orderManager.Tick();
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
{ {
world.OrderGenerator.Tick(world); world.OrderGenerator.Tick(world);
}); });
@@ -628,10 +629,12 @@ namespace OpenRA
PerfHistory.Tick(); PerfHistory.Tick();
} }
else if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = RunTime;
// Wait until we have done our first world Tick before TickRendering // Wait until we have done our first world Tick before TickRendering
if (orderManager.LocalFrameNumber > 0) if (orderManager.LocalFrameNumber > 0)
Sync.RunUnsynced(world, () => world.TickRender(worldRenderer)); Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
} }
benchmark?.Tick(LocalTick); benchmark?.Tick(LocalTick);
@@ -642,10 +645,10 @@ namespace OpenRA
{ {
PerformDelayedActions(); PerformDelayedActions();
if (OrderManager.Connection is NetworkConnection nc && nc.ConnectionState != lastConnectionState) if (OrderManager.Connection.ConnectionState != lastConnectionState)
{ {
lastConnectionState = nc.ConnectionState; lastConnectionState = OrderManager.Connection.ConnectionState;
ConnectionStateChanged(OrderManager, null, nc); ConnectionStateChanged(OrderManager);
} }
InnerLogicTick(OrderManager); InnerLogicTick(OrderManager);
@@ -782,20 +785,13 @@ namespace OpenRA
while (state == RunStatus.Running) while (state == RunStatus.Running)
{ {
var logicInterval = Ui.Timestep; // Ideal time between logic updates. Timestep = 0 means the game is paused
var logicWorld = worldRenderer?.World; // but we still call LogicTick() because it handles pausing internally.
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Timestep;
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
// Ideal time between screen updates // Ideal time between screen updates
var renderInterval = logicInterval; var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
if (!Settings.Graphics.CapFramerateToGameFps) var renderInterval = 1000 / maxFramerate;
{
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
renderInterval = 1000 / maxFramerate;
}
// Tick as fast as possible while restoring game saves, capping rendering at 5 FPS // Tick as fast as possible while restoring game saves, capping rendering at 5 FPS
if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave) if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave)
@@ -829,7 +825,8 @@ namespace OpenRA
var haveSomeTimeUntilNextLogic = now < nextLogic; var haveSomeTimeUntilNextLogic = now < nextLogic;
var isTimeToRender = now >= nextRender; var isTimeToRender = now >= nextRender;
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
{ {
nextRender = now + renderInterval; nextRender = now + renderInterval;
@@ -844,19 +841,6 @@ namespace OpenRA
RenderTick(); RenderTick();
renderBeforeNextTick = false; renderBeforeNextTick = false;
} }
// Simulate a render tick if it was time to render but we skip actually rendering
if (Renderer.WindowIsSuspended && isTimeToRender)
{
// Make sure that nextUpdate is set to a proper minimum interval
nextRender = now + renderInterval;
// Still process SDL events to allow a restore to come through
Renderer.Window.PumpInput(new NullInputHandler());
// Ensure that we still logic tick despite not rendering
renderBeforeNextTick = false;
}
} }
else else
Thread.Sleep((int)(nextUpdate - now)); Thread.Sleep((int)(nextUpdate - now));
@@ -898,6 +882,26 @@ namespace OpenRA
state = RunStatus.Success; state = RunStatus.Success;
} }
public static void AddSystemLine(string text)
{
AddSystemLine("Battlefield Control", text);
}
public static void AddSystemLine(string name, string text)
{
OrderManager.AddChatLine(name, systemMessageColor, text, systemMessageColor);
}
public static void AddChatLine(string name, Color nameColor, string text)
{
OrderManager.AddChatLine(name, nameColor, text, chatMessageColor);
}
public static void Debug(string s, params object[] args)
{
AddSystemLine("Debug", string.Format(s, args));
}
public static void Disconnect() public static void Disconnect()
{ {
OrderManager.World?.TraitDict.PrintReport(); OrderManager.World?.TraitDict.PrintReport();
@@ -969,13 +973,16 @@ namespace OpenRA
{ {
var orders = new List<Order> var orders = new List<Order>
{ {
Order.Command("option gamespeed default"), Order.Command("option gamespeed {0}".F("default")),
Order.Command($"state {Session.ClientState.Ready}") Order.Command("state {0}".F(Session.ClientState.Ready))
}; };
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Package.Name) == launchMap); var path = Platform.ResolvePath(launchMap);
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap) ??
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
if (map == null) if (map == null)
throw new ArgumentException($"Could not find map '{launchMap}'."); throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
CreateAndStartLocalServer(map.Uid, orders); CreateAndStartLocalServer(map.Uid, orders);
} }
@@ -989,11 +996,4 @@ namespace OpenRA
} }
} }
} }
public static class CurrentServerSettings
{
public static string Password;
public static ConnectionTarget Target;
public static ExternalMod ServerExternalMod;
}
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -33,13 +33,12 @@ namespace OpenRA
public DateTime EndTimeUtc; public DateTime EndTimeUtc;
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary> /// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
public IList<Player> Players { get; private set; }
public IList<Player> Players { get; }
public HashSet<int> DisabledSpawnPoints = new HashSet<int>(); public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public MapPreview MapPreview => Game.ModData.MapCache[MapUid]; public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } } public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer => HumanPlayers.Count() == 1; public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
readonly Dictionary<OpenRA.Player, Player> playersByRuntime; readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
@@ -76,7 +75,7 @@ namespace OpenRA
} }
catch (YamlException) catch (YamlException)
{ {
Log.Write("debug", $"GameInformation deserialized invalid MiniYaml:\n{data}"); Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
throw; throw;
} }
} }
@@ -89,7 +88,7 @@ namespace OpenRA
}; };
for (var i = 0; i < Players.Count; i++) for (var i = 0; i < Players.Count; i++)
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i]))); nodes.Add(new MiniYamlNode("Player@{0}".F(i), FieldSaver.Save(Players[i])));
return nodes.WriteToString(); return nodes.WriteToString();
} }
@@ -98,10 +97,10 @@ namespace OpenRA
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo) public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
{ {
if (runtimePlayer == null) if (runtimePlayer == null)
throw new ArgumentNullException(nameof(runtimePlayer)); throw new ArgumentNullException("runtimePlayer");
if (lobbyInfo == null) if (lobbyInfo == null)
throw new ArgumentNullException(nameof(lobbyInfo)); throw new ArgumentNullException("lobbyInfo");
// We don't care about spectators and map players // We don't care about spectators and map players
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable) if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
@@ -124,7 +123,6 @@ namespace OpenRA
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName, DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
Color = runtimePlayer.Color, Color = runtimePlayer.Color,
Team = client.Team, Team = client.Team,
Handicap = client.Handicap,
SpawnPoint = runtimePlayer.SpawnPoint, SpawnPoint = runtimePlayer.SpawnPoint,
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction, IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0, IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
@@ -168,7 +166,6 @@ namespace OpenRA
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary> /// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
public int Team; public int Team;
public int SpawnPoint; public int SpawnPoint;
public int Handicap;
/// <summary>True if the faction was chosen at random; otherwise, false.</summary> /// <summary>True if the faction was chosen at random; otherwise, false.</summary>
public bool IsRandomFaction; public bool IsRandomFaction;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -61,7 +61,7 @@ namespace OpenRA
} }
catch (YamlException e) catch (YamlException e)
{ {
throw new YamlException($"Actor type {name}: {e.Message}"); throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
} }
} }
@@ -76,7 +76,8 @@ namespace OpenRA
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my) static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
{ {
if (!string.IsNullOrEmpty(my.Value)) if (!string.IsNullOrEmpty(my.Value))
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}"); throw new YamlException("Junk value `{0}` on trait node {1}"
.F(my.Value, traitName));
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead // HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
// ObjectCreator will only return null to signal us to abort here if the linter is running // ObjectCreator will only return null to signal us to abort here if the linter is running
@@ -88,7 +89,7 @@ namespace OpenRA
try try
{ {
if (traitInstance.Length > 1) if (traitInstance.Length > 1)
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]); info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
FieldLoader.Load(info, my); FieldLoader.Load(info, my);
} }
@@ -110,11 +111,10 @@ namespace OpenRA
{ {
Trait = i, Trait = i,
Type = i.GetType(), Type = i.GetType(),
Dependencies = PrerequisitesOf(i).ToList(), Dependencies = PrerequisitesOf(i).ToList()
OptionalDependencies = OptionalPrerequisitesOf(i).ToList()
}).ToList(); }).ToList();
var resolved = source.Where(s => s.Dependencies.Count == 0 && s.OptionalDependencies.Count == 0).ToList(); var resolved = source.Where(s => !s.Dependencies.Any()).ToList();
var unresolved = source.Except(resolved); var unresolved = source.Except(resolved);
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b)); var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
@@ -123,9 +123,7 @@ namespace OpenRA
var more = unresolved.Where(u => var more = unresolved.Where(u =>
u.Dependencies.All(d => // To be resolvable, all dependencies must be satisfied according to the following conditions: u.Dependencies.All(d => // To be resolvable, all dependencies must be satisfied according to the following conditions:
resolved.Exists(r => testResolve(d, r.Type)) && // There must exist a resolved trait that meets the dependency. resolved.Exists(r => testResolve(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
!unresolved.Any(u1 => testResolve(d, u1.Type))) && // All matching traits that meet this dependency must be resolved first. !unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this dependency must be resolved first.
u.OptionalDependencies.All(d => // To be resolvable, all optional dependencies must be satisfied according to the following condition:
!unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this optional dependencies must be resolved first.
// Continue resolving traits as long as possible. // Continue resolving traits as long as possible.
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass. // Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
@@ -145,9 +143,7 @@ namespace OpenRA
foreach (var u in unresolved) foreach (var u in unresolved)
{ {
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d)); var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d)); exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
exceptionString += $"{u.Type}: {{ {allDeps} }}\r\n";
} }
throw new YamlException(exceptionString); throw new YamlException(exceptionString);
@@ -166,15 +162,6 @@ namespace OpenRA
.Select(t => t.GetGenericArguments()[0]); .Select(t => t.GetGenericArguments()[0]);
} }
public static IEnumerable<Type> OptionalPrerequisitesOf(TraitInfo info)
{
return info
.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(NotBefore<>))
.Select(t => t.GetGenericArguments()[0]);
}
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); } public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); } public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); } public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -9,6 +9,7 @@
*/ */
#endregion #endregion
using System.IO;
using OpenRA.FileSystem; using OpenRA.FileSystem;
namespace OpenRA.GameRules namespace OpenRA.GameRules

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -22,12 +22,12 @@ namespace OpenRA
{ {
public class Ruleset public class Ruleset
{ {
public readonly ActorInfoDictionary Actors; public readonly IReadOnlyDictionary<string, ActorInfo> Actors;
public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons; public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons;
public readonly IReadOnlyDictionary<string, SoundInfo> Voices; public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications; public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
public readonly IReadOnlyDictionary<string, MusicInfo> Music; public readonly IReadOnlyDictionary<string, MusicInfo> Music;
public readonly ITerrainInfo TerrainInfo; public readonly TileSet TileSet;
public readonly SequenceProvider Sequences; public readonly SequenceProvider Sequences;
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences; public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
@@ -37,16 +37,16 @@ namespace OpenRA
IReadOnlyDictionary<string, SoundInfo> voices, IReadOnlyDictionary<string, SoundInfo> voices,
IReadOnlyDictionary<string, SoundInfo> notifications, IReadOnlyDictionary<string, SoundInfo> notifications,
IReadOnlyDictionary<string, MusicInfo> music, IReadOnlyDictionary<string, MusicInfo> music,
ITerrainInfo terrainInfo, TileSet tileSet,
SequenceProvider sequences, SequenceProvider sequences,
IReadOnlyDictionary<string, MiniYamlNode> modelSequences) IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
{ {
Actors = new ActorInfoDictionary(actors); Actors = actors;
Weapons = weapons; Weapons = weapons;
Voices = voices; Voices = voices;
Notifications = notifications; Notifications = notifications;
Music = music; Music = music;
TerrainInfo = terrainInfo; TileSet = tileSet;
Sequences = sequences; Sequences = sequences;
ModelSequences = modelSequences; ModelSequences = modelSequences;
@@ -60,14 +60,15 @@ namespace OpenRA
} }
catch (YamlException e) catch (YamlException e)
{ {
throw new YamlException($"Actor type {a.Name}: {e.Message}"); throw new YamlException("Actor type {0}: {1}".F(a.Name, e.Message));
} }
} }
} }
foreach (var weapon in Weapons) foreach (var weapon in Weapons)
{ {
if (weapon.Value.Projectile is IRulesetLoaded<WeaponInfo> projectileLoaded) var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
if (projectileLoaded != null)
{ {
try try
{ {
@@ -75,13 +76,14 @@ namespace OpenRA
} }
catch (YamlException e) catch (YamlException e)
{ {
throw new YamlException($"Projectile type {weapon.Key}: {e.Message}"); throw new YamlException("Projectile type {0}: {1}".F(weapon.Key, e.Message));
} }
} }
foreach (var warhead in weapon.Value.Warheads) foreach (var warhead in weapon.Value.Warheads)
{ {
if (warhead is IRulesetLoaded<WeaponInfo> cacher) var cacher = warhead as IRulesetLoaded<WeaponInfo>;
if (cacher != null)
{ {
try try
{ {
@@ -89,7 +91,7 @@ namespace OpenRA
} }
catch (YamlException e) catch (YamlException e)
{ {
throw new YamlException($"Weapon type {weapon.Key}: {e.Message}"); throw new YamlException("Weapon type {0}: {1}".F(weapon.Key, e.Message));
} }
} }
} }
@@ -115,7 +117,7 @@ namespace OpenRA
if (filterNode != null) if (filterNode != null)
yamlNodes = yamlNodes.Where(k => !filterNode(k)); yamlNodes = yamlNodes.Where(k => !filterNode(k));
return yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"); return new ReadOnlyDictionary<string, T>(yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"));
} }
public static Ruleset LoadDefaults(ModData modData) public static Ruleset LoadDefaults(ModData modData)
@@ -131,7 +133,7 @@ namespace OpenRA
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal)); filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null, var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
k => new WeaponInfo(k.Value)); k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null, var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null,
k => new SoundInfo(k.Value)); k => new SoundInfo(k.Value));
@@ -169,10 +171,10 @@ namespace OpenRA
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet) public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
{ {
var dr = modData.DefaultRules; var dr = modData.DefaultRules;
var terrainInfo = modData.DefaultTerrainInfo[tileSet]; var ts = modData.DefaultTileSets[tileSet];
var sequences = modData.DefaultSequences[tileSet]; var sequences = modData.DefaultSequences[tileSet];
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, sequences, dr.ModelSequences); return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
} }
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet, public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
@@ -190,7 +192,7 @@ namespace OpenRA
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal)); filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons, var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
k => new WeaponInfo(k.Value)); k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices, var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices,
k => new SoundInfo(k.Value)); k => new SoundInfo(k.Value));
@@ -201,8 +203,8 @@ namespace OpenRA
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music, var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
k => new MusicInfo(k.Key, k.Value)); k => new MusicInfo(k.Key, k.Value));
// TODO: Add support for merging custom terrain modifications // TODO: Add support for merging custom tileset modifications
var terrainInfo = modData.DefaultTerrainInfo[tileSet]; var ts = modData.DefaultTileSets[tileSet];
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object // TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] : var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
@@ -213,7 +215,7 @@ namespace OpenRA
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences, modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
k => k); k => k);
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, sequences, modelSequences); ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
}; };
if (modData.IsOnMainThread) if (modData.IsOnMainThread)
@@ -235,7 +237,7 @@ namespace OpenRA
static bool AnyCustomYaml(MiniYaml yaml) static bool AnyCustomYaml(MiniYaml yaml)
{ {
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0); return yaml != null && (yaml.Value != null || yaml.Nodes.Any());
} }
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors) static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
@@ -248,7 +250,7 @@ namespace OpenRA
{ {
var traitName = traitNode.Key.Split('@')[0]; var traitName = traitNode.Key.Split('@')[0];
var traitType = modData.ObjectCreator.FindType(traitName + "Info"); var traitType = modData.ObjectCreator.FindType(traitName + "Info");
if (traitType != null && traitType.GetInterface(nameof(ILobbyCustomRulesIgnore)) == null) if (traitType != null && traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -33,28 +33,23 @@ namespace OpenRA.GameRules
{ {
FieldLoader.Load(this, y); FieldLoader.Load(this, y);
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, SoundPool.DefaultInterruptType, a.Value))); VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, a.Value)));
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications")); NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
} }
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key) Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
{ {
var ret = new Dictionary<string, SoundPool>(); var ret = new Dictionary<string, SoundPool>();
var classifiction = y.Nodes.First(x => x.Key == key); var classifiction = y.Nodes.First(x => x.Key == key);
foreach (var t in classifiction.Value.Nodes) foreach (var t in classifiction.Value.Nodes)
{ {
var volumeModifier = 1f; var volumeModifier = 1f;
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier)); var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "VolumeModifier");
if (volumeModifierNode != null) if (volumeModifierNode != null)
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value); volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
var interruptType = SoundPool.DefaultInterruptType;
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
if (interruptTypeNode != null)
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value); var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
var sp = new SoundPool(volumeModifier, interruptType, names); var sp = new SoundPool(volumeModifier, names);
ret.Add(t.Key, sp); ret.Add(t.Key, sp);
} }
@@ -64,17 +59,13 @@ namespace OpenRA.GameRules
public class SoundPool public class SoundPool
{ {
public enum InterruptType { DoNotPlay, Interrupt, Overlap }
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
public readonly float VolumeModifier; public readonly float VolumeModifier;
public readonly InterruptType Type;
readonly string[] clips; readonly string[] clips;
readonly List<string> liveclips = new List<string>(); readonly List<string> liveclips = new List<string>();
public SoundPool(float volumeModifier, InterruptType interruptType, params string[] clips) public SoundPool(float volumeModifier, params string[] clips)
{ {
VolumeModifier = volumeModifier; VolumeModifier = volumeModifier;
Type = interruptType;
this.clips = clips; this.clips = clips;
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -36,7 +36,7 @@ namespace OpenRA.GameRules
public class WarheadArgs public class WarheadArgs
{ {
public WeaponInfo Weapon; public WeaponInfo Weapon;
public int[] DamageModifiers = Array.Empty<int>(); public int[] DamageModifiers = { };
public WPos? Source; public WPos? Source;
public WRot ImpactOrientation; public WRot ImpactOrientation;
public WPos ImpactPosition; public WPos ImpactPosition;
@@ -121,18 +121,13 @@ namespace OpenRA.GameRules
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")] [Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
public readonly bool TargetActorCenter = false; public readonly bool TargetActorCenter = false;
[FieldLoader.LoadUsing(nameof(LoadProjectile))] [FieldLoader.LoadUsing("LoadProjectile")]
public readonly IProjectileInfo Projectile; public readonly IProjectileInfo Projectile;
[FieldLoader.LoadUsing(nameof(LoadWarheads))] [FieldLoader.LoadUsing("LoadWarheads")]
public readonly List<IWarhead> Warheads = new List<IWarhead>(); public readonly List<IWarhead> Warheads = new List<IWarhead>();
/// <summary> public WeaponInfo(string name, MiniYaml content)
/// This constructor is used solely for documentation generation!
/// </summary>
public WeaponInfo() { }
public WeaponInfo(MiniYaml content)
{ {
// Resolve any weapon-level yaml inheritance or removals // Resolve any weapon-level yaml inheritance or removals
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing // HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
@@ -144,11 +139,7 @@ namespace OpenRA.GameRules
{ {
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj)) if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
return null; return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info"); var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
if (ret == null)
return null;
FieldLoader.Load(ret, proj); FieldLoader.Load(ret, proj);
return ret; return ret;
} }
@@ -159,9 +150,6 @@ namespace OpenRA.GameRules
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead"))) foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
{ {
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead"); var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
if (ret == null)
continue;
FieldLoader.Load(ret, node.Value); FieldLoader.Load(ret, node.Value);
retList.Add(ret); retList.Add(ret);
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -10,49 +10,27 @@
#endregion #endregion
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace OpenRA namespace OpenRA
{ {
public class GameSpeed public class GameSpeed
{ {
[FieldLoader.Require] [Translate]
public readonly string Name; public readonly string Name = "Default";
public readonly int Timestep = 40;
[FieldLoader.Require] public readonly int OrderLatency = 3;
public readonly int Timestep;
[FieldLoader.Require]
public readonly int OrderLatency;
} }
public class GameSpeeds : IGlobalModData public class GameSpeeds : IGlobalModData
{ {
[FieldLoader.Require] [FieldLoader.LoadUsing("LoadSpeeds")]
public readonly string DefaultSpeed;
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
public readonly Dictionary<string, GameSpeed> Speeds; public readonly Dictionary<string, GameSpeed> Speeds;
static object LoadSpeeds(MiniYaml y) static object LoadSpeeds(MiniYaml y)
{ {
var ret = new Dictionary<string, GameSpeed>(); var ret = new Dictionary<string, GameSpeed>();
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds"); foreach (var node in y.Nodes)
if (speedsNode == null) ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
foreach (var node in speedsNode.Value.Nodes)
{
try
{
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
}
catch (FieldLoader.MissingFieldsException e)
{
var label = e.Missing.Length > 1 ? "Required properties missing" : "Required property missing";
throw new YamlException($"Error parsing GameSpeed {node.Key}: {label}: {e.Missing.JoinWith(", ")}");
}
}
return ret; return ret;
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -49,51 +49,42 @@ namespace OpenRA.Graphics
this.paused = paused; this.paused = paused;
} }
public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame; public int CurrentFrame { get { return backwards ? CurrentSequence.Length - frame - 1 : frame; } }
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc()); public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette)
{ {
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None; var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
rotation);
if (CurrentSequence.ShadowStart >= 0) if (CurrentSequence.ShadowStart >= 0)
{ {
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers, var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
true, rotation);
return new IRenderable[] { shadowRenderable, imageRenderable }; return new IRenderable[] { shadowRenderable, imageRenderable };
} }
return new IRenderable[] { imageRenderable }; return new IRenderable[] { imageRenderable };
} }
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f, float rotation = 0f) public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale)
{ {
scale *= CurrentSequence.Scale;
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2(); var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2)); var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
var alpha = CurrentSequence.GetAlpha(CurrentFrame); var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale);
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
if (CurrentSequence.ShadowStart >= 0) if (CurrentSequence.ShadowStart >= 0)
{ {
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2)); var shadowPos = pos - new int2((int)(scale * shadow.Size.X / 2), (int)(scale * shadow.Size.Y / 2));
var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale, 1f, rotation); var shadowRenderable = new UISpriteRenderable(shadow, WPos.Zero + offset, shadowPos, CurrentSequence.ShadowZOffset + zOffset, palette, scale);
return new IRenderable[] { shadowRenderable, imageRenderable }; return new IRenderable[] { shadowRenderable, imageRenderable };
} }
return new IRenderable[] { imageRenderable }; return new IRenderable[] { imageRenderable };
} }
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset) public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
{ {
var scale = CurrentSequence.Scale;
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset); var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
var cb = CurrentSequence.Bounds; var cb = CurrentSequence.Bounds;
return Rectangle.FromLTRB( return Rectangle.FromLTRB(
@@ -105,7 +96,7 @@ namespace OpenRA.Graphics
public IRenderable[] Render(WPos pos, PaletteReference palette) public IRenderable[] Render(WPos pos, PaletteReference palette)
{ {
return Render(pos, WVec.Zero, 0, palette); return Render(pos, WVec.Zero, 0, palette, 1f);
} }
public void Play(string sequenceName) public void Play(string sequenceName)
@@ -116,7 +107,7 @@ namespace OpenRA.Graphics
int CurrentSequenceTickOrDefault() int CurrentSequenceTickOrDefault()
{ {
const int DefaultTick = 40; // 25 fps == 40 ms const int DefaultTick = 40; // 25 fps == 40 ms
return CurrentSequence?.Tick ?? DefaultTick; return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
} }
void PlaySequence(string sequenceName) void PlaySequence(string sequenceName)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -35,21 +35,21 @@ namespace OpenRA.Graphics
ZOffset = zOffset; ZOffset = zOffset;
} }
public IRenderable[] Render(Actor self, PaletteReference pal) public IRenderable[] Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
{ {
var center = self.CenterPosition; var center = self.CenterPosition;
var offset = OffsetFunc?.Invoke() ?? WVec.Zero; var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
var z = ZOffset?.Invoke(center + offset) ?? 0; var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
return Animation.Render(center, offset, z, pal); return Animation.Render(center, offset, z, pal, scale);
} }
public Rectangle ScreenBounds(Actor self, WorldRenderer wr) public Rectangle ScreenBounds(Actor self, WorldRenderer wr, float scale)
{ {
var center = self.CenterPosition; var center = self.CenterPosition;
var offset = OffsetFunc?.Invoke() ?? WVec.Zero; var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
return Animation.ScreenBounds(wr, center, offset); return Animation.ScreenBounds(wr, center, offset, scale);
} }
public static implicit operator AnimationWithOffset(Animation a) public static implicit operator AnimationWithOffset(Animation a)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -52,7 +52,7 @@ namespace OpenRA.Graphics
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>(); public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
} }
public static IReadOnlyDictionary<string, Collection> Collections => collections; public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
static Dictionary<string, Collection> collections; static Dictionary<string, Collection> collections;
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets; static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites; static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
@@ -77,6 +77,8 @@ namespace OpenRA.Graphics
cachedPanelSprites = new Dictionary<string, Sprite[]>(); cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>(); cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
Collections = new ReadOnlyDictionary<string, Collection>(collections);
var chrome = MiniYaml.Merge(modData.Manifest.Chrome var chrome = MiniYaml.Merge(modData.Manifest.Chrome
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
@@ -144,15 +146,6 @@ namespace OpenRA.Graphics
} }
public static Sprite GetImage(string collectionName, string imageName) public static Sprite GetImage(string collectionName, string imageName)
{
var image = TryGetImage(collectionName, imageName);
if (image == null)
throw new ArgumentException($"Sprite `{collectionName}/{imageName}` was not found.");
return image;
}
public static Sprite TryGetImage(string collectionName, string imageName)
{ {
if (string.IsNullOrEmpty(collectionName)) if (string.IsNullOrEmpty(collectionName))
return null; return null;
@@ -162,7 +155,10 @@ namespace OpenRA.Graphics
return sprite; return sprite;
if (!collections.TryGetValue(collectionName, out var collection)) if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null; return null;
}
if (!collection.Regions.TryGetValue(imageName, out var mi)) if (!collection.Regions.TryGetValue(imageName, out var mi))
return null; return null;
@@ -182,15 +178,6 @@ namespace OpenRA.Graphics
} }
public static Sprite[] GetPanelImages(string collectionName) public static Sprite[] GetPanelImages(string collectionName)
{
var panel = TryGetPanelImages(collectionName);
if (panel == null)
throw new ArgumentException($"Panel `{collectionName}` was not found.");
return panel;
}
public static Sprite[] TryGetPanelImages(string collectionName)
{ {
if (string.IsNullOrEmpty(collectionName)) if (string.IsNullOrEmpty(collectionName))
return null; return null;
@@ -200,14 +187,17 @@ namespace OpenRA.Graphics
return cachedSprites; return cachedSprites;
if (!collections.TryGetValue(collectionName, out var collection)) if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null; return null;
}
Sprite[] sprites; Sprite[] sprites;
if (collection.PanelRegion != null) if (collection.PanelRegion != null)
{ {
if (collection.PanelRegion.Length != 8) if (collection.PanelRegion.Length != 8)
{ {
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion"); Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
return null; return null;
} }
@@ -234,23 +224,18 @@ namespace OpenRA.Graphics
} }
else else
{ {
// PERF: We don't need to search for images if there are no definitions.
// PERF: It's more efficient to send an empty array rather than an array of 9 nulls.
if (!collection.Regions.Any())
return Array.Empty<Sprite>();
// Support manual definitions for unusual dialog layouts // Support manual definitions for unusual dialog layouts
sprites = new[] sprites = new[]
{ {
TryGetImage(collectionName, "corner-tl"), GetImage(collectionName, "corner-tl"),
TryGetImage(collectionName, "border-t"), GetImage(collectionName, "border-t"),
TryGetImage(collectionName, "corner-tr"), GetImage(collectionName, "corner-tr"),
TryGetImage(collectionName, "border-l"), GetImage(collectionName, "border-l"),
TryGetImage(collectionName, "background"), GetImage(collectionName, "background"),
TryGetImage(collectionName, "border-r"), GetImage(collectionName, "border-r"),
TryGetImage(collectionName, "corner-bl"), GetImage(collectionName, "corner-bl"),
TryGetImage(collectionName, "border-b"), GetImage(collectionName, "border-b"),
TryGetImage(collectionName, "corner-br") GetImage(collectionName, "corner-br")
}; };
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -35,7 +35,7 @@ namespace OpenRA.Graphics
Cursor cursor; Cursor cursor;
bool isLocked = false; bool isLocked = false;
int2 lockedPosition; int2 lockedPosition;
readonly bool hardwareCursorsDisabled = false; bool hardwareCursorsDisabled = false;
bool hardwareCursorsDoubled = false; bool hardwareCursorsDoubled = false;
public CursorManager(CursorProvider cursorProvider) public CursorManager(CursorProvider cursorProvider)
@@ -69,16 +69,9 @@ namespace OpenRA.Graphics
// Hotspot is specified relative to the center of the frame // Hotspot is specified relative to the center of the frame
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2; var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
// Resolve indexed data to real colours // SheetBuilder expects data in BGRA
var data = f.Data; var data = FrameToBGRA(kv.Key, f, palette);
var type = f.Type; c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
if (type == SpriteFrameType.Indexed8)
{
data = ConvertIndexedToBgra(kv.Key, f, palette);
type = SpriteFrameType.Bgra32;
}
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
// Bounds relative to the hotspot // Bounds relative to the hotspot
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size)); c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
@@ -106,28 +99,34 @@ namespace OpenRA.Graphics
// Dispose any existing cursors to avoid leaking native resources // Dispose any existing cursors to avoid leaking native resources
ClearHardwareCursors(); ClearHardwareCursors();
foreach (var kv in cursors) try
{ {
var template = kv.Value; foreach (var kv in cursors)
for (var i = 0; i < template.Sprites.Length; i++)
{ {
if (template.Cursors[i] != null) var template = kv.Value;
template.Cursors[i].Dispose(); for (var i = 0; i < template.Sprites.Length; i++)
// Calculate the padding to position the frame within sequenceBounds
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
var hardwareCursor = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
if (hardwareCursor != null)
template.Cursors[i] = hardwareCursor;
else
{ {
Log.Write("debug", $"Failed to initialize hardware cursor for {template.Name}."); if (template.Cursors[i] != null)
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}."); template.Cursors[i].Dispose();
// Calculate the padding to position the frame within sequenceBounds
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
} }
} }
} }
catch (Exception e)
{
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
Log.Write("debug", "Error was: " + e.Message);
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
Console.WriteLine("Error was: " + e.Message);
ClearHardwareCursors();
}
hardwareCursorsDoubled = graphicSettings.CursorDouble; hardwareCursorsDoubled = graphicSettings.CursorDouble;
} }
@@ -171,11 +170,10 @@ namespace OpenRA.Graphics
if (cursor != null && frame >= cursor.Cursors.Length) if (cursor != null && frame >= cursor.Cursors.Length)
frame %= cursor.Cursors.Length; frame %= cursor.Cursors.Length;
var hardwareCursor = cursor?.Cursors[frame]; if (cursor == null || isLocked)
if (hardwareCursor == null || isLocked)
Game.Renderer.Window.SetHardwareCursor(null); Game.Renderer.Window.SetHardwareCursor(null);
else else
Game.Renderer.Window.SetHardwareCursor(hardwareCursor); Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
} }
public void Render(Renderer renderer) public void Render(Renderer renderer)
@@ -191,17 +189,17 @@ namespace OpenRA.Graphics
// Render cursor in software // Render cursor in software
var doubleCursor = graphicSettings.CursorDouble; var doubleCursor = graphicSettings.CursorDouble;
var cursorSprite = cursor.Sprites[frame % cursor.Length]; var cursorSprite = cursor.Sprites[frame % cursor.Length];
var cursorScale = doubleCursor ? 2 : 1; var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
// Cursor is rendered in native window coordinates // Cursor is rendered in native window coordinates
// Apply same scaling rules as hardware cursors // Apply same scaling rules as hardware cursors
if (Game.Renderer.NativeWindowScale > 1.5f) if (Game.Renderer.NativeWindowScale > 1.5f)
cursorScale *= 2; cursorSize = 2 * cursorSize;
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos; var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite, renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
mousePos, mousePos,
cursorScale / Game.Renderer.WindowScale); cursorSize / Game.Renderer.WindowScale);
} }
public void Lock() public void Lock()
@@ -219,27 +217,33 @@ namespace OpenRA.Graphics
Update(); Update();
} }
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette) public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
{ {
if (frame.Type != SpriteFrameType.Indexed8) // Data is already in BGRA format
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame)); if (frame.Type == SpriteFrameType.BGRA)
return frame.Data;
// Cursors may be either native BGRA or Indexed.
// Indexed sprites are converted to BGRA using the referenced palette.
// All palettes must be explicitly referenced, even if they are embedded in the sprite. // All palettes must be explicitly referenced, even if they are embedded in the sprite.
if (palette == null) if (frame.Type == SpriteFrameType.Indexed && palette == null)
throw new InvalidOperationException($"Cursor sequence `{name}` attempted to load an indexed sprite but does not define Palette"); throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
var width = frame.Size.Width; var width = frame.Size.Width;
var height = frame.Size.Height; var height = frame.Size.Height;
var data = new byte[4 * width * height]; var data = new byte[4 * width * height];
unsafe for (var j = 0; j < height; j++)
{ {
// Cast the data to an int array so we can copy the src data directly for (var i = 0; i < width; i++)
fixed (byte* bd = &data[0])
{ {
var rgba = (uint*)bd; var rgba = palette[frame.Data[j * width + i]];
for (var j = 0; j < height; j++) var k = 4 * (j * width + i);
for (var i = 0; i < width; i++)
rgba[j * width + i] = palette[frame.Data[j * width + i]]; // Convert RGBA to BGRA
data[k] = (byte)(rgba >> 16);
data[k + 1] = (byte)(rgba >> 8);
data[k + 2] = (byte)(rgba >> 0);
data[k + 3] = (byte)(rgba >> 24);
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -31,14 +31,15 @@ namespace OpenRA.Graphics
// Overwrite previous definitions if there are duplicates // Overwrite previous definitions if there are duplicates
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>(); var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>()) foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
if (p.Palette != null) if (p.Palette != null)
pals[p.Palette] = p; pals[p.Palette] = p;
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value) Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
.Where(p => p != null) .Where(p => p != null)
.Distinct() .Distinct()
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem)); .ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
.AsReadOnly();
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders); var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
var cursors = new Dictionary<string, CursorSequence>(); var cursors = new Dictionary<string, CursorSequence>();
@@ -46,7 +47,7 @@ namespace OpenRA.Graphics
foreach (var sequence in s.Value.Nodes) foreach (var sequence in s.Value.Nodes)
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value)); cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
Cursors = cursors; Cursors = cursors.AsReadOnly();
} }
public bool HasCursorSequence(string cursor) public bool HasCursorSequence(string cursor)
@@ -59,7 +60,7 @@ namespace OpenRA.Graphics
try { return Cursors[cursor]; } try { return Cursors[cursor]; }
catch (KeyNotFoundException) catch (KeyNotFoundException)
{ {
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`"); throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor));
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -17,94 +17,73 @@ namespace OpenRA.Graphics
{ {
public sealed class HardwarePalette : IDisposable public sealed class HardwarePalette : IDisposable
{ {
public ITexture Texture { get; } public ITexture Texture { get; private set; }
public ITexture ColorShifts { get; }
public int Height { get; private set; } public int Height { get; private set; }
readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>(); readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>();
readonly Dictionary<string, MutablePalette> mutablePalettes = new Dictionary<string, MutablePalette>(); readonly Dictionary<string, MutablePalette> modifiablePalettes = new Dictionary<string, MutablePalette>();
readonly IReadOnlyDictionary<string, MutablePalette> readOnlyModifiablePalettes;
readonly Dictionary<string, int> indices = new Dictionary<string, int>(); readonly Dictionary<string, int> indices = new Dictionary<string, int>();
byte[] buffer = Array.Empty<byte>(); byte[] buffer = new byte[0];
float[] colorShiftBuffer = Array.Empty<float>();
public HardwarePalette() public HardwarePalette()
{ {
Texture = Game.Renderer.Context.CreateTexture(); Texture = Game.Renderer.Context.CreateTexture();
ColorShifts = Game.Renderer.Context.CreateTexture(); readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
} }
public bool Contains(string name) public bool Contains(string name)
{ {
return mutablePalettes.ContainsKey(name) || palettes.ContainsKey(name); return modifiablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
} }
public IPalette GetPalette(string name) public IPalette GetPalette(string name)
{ {
if (mutablePalettes.TryGetValue(name, out var mutable)) if (modifiablePalettes.TryGetValue(name, out var mutable))
return mutable.AsReadOnly(); return mutable.AsReadOnly();
if (palettes.TryGetValue(name, out var immutable)) if (palettes.TryGetValue(name, out var immutable))
return immutable; return immutable;
throw new InvalidOperationException($"Palette `{name}` does not exist"); throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
} }
public int GetPaletteIndex(string name) public int GetPaletteIndex(string name)
{ {
if (!indices.TryGetValue(name, out var ret)) if (!indices.TryGetValue(name, out var ret))
throw new InvalidOperationException($"Palette `{name}` does not exist"); throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
return ret; return ret;
} }
public void AddPalette(string name, ImmutablePalette p, bool allowModifiers) public void AddPalette(string name, ImmutablePalette p, bool allowModifiers)
{ {
if (palettes.ContainsKey(name)) if (palettes.ContainsKey(name))
throw new InvalidOperationException($"Palette {name} has already been defined"); throw new InvalidOperationException("Palette {0} has already been defined".F(name));
// PERF: the first row in the palette textures is reserved as a placeholder for non-indexed sprites int index = palettes.Count;
// that do not have a color-shift applied. This provides a quick shortcut to avoid querying the
// color-shift texture for every pixel only to find that most are not shifted.
var index = palettes.Count + 1;
indices.Add(name, index); indices.Add(name, index);
palettes.Add(name, p); palettes.Add(name, p);
if (index >= Height) if (palettes.Count > Height)
{ {
Height = Exts.NextPowerOf2(index + 1); Height = Exts.NextPowerOf2(palettes.Count);
Array.Resize(ref buffer, Height * Palette.Size * 4); Array.Resize(ref buffer, Height * Palette.Size * 4);
Array.Resize(ref colorShiftBuffer, Height * 4);
} }
if (allowModifiers) if (allowModifiers)
mutablePalettes.Add(name, new MutablePalette(p)); modifiablePalettes.Add(name, new MutablePalette(p));
else else
CopyPaletteToBuffer(index, p); CopyPaletteToBuffer(index, p);
} }
public void ReplacePalette(string name, IPalette p) public void ReplacePalette(string name, IPalette p)
{ {
if (mutablePalettes.ContainsKey(name)) if (modifiablePalettes.ContainsKey(name))
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p)); CopyPaletteToBuffer(indices[name], modifiablePalettes[name] = new MutablePalette(p));
else if (palettes.ContainsKey(name)) else if (palettes.ContainsKey(name))
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p)); CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
else else
throw new InvalidOperationException($"Palette `{name}` does not exist"); throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
CopyBufferToTexture(); CopyBufferToTexture();
} }
public void SetColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
var index = GetPaletteIndex(name);
colorShiftBuffer[4 * index + 0] = hueOffset;
colorShiftBuffer[4 * index + 1] = satOffset;
colorShiftBuffer[4 * index + 2] = minHue;
colorShiftBuffer[4 * index + 3] = maxHue;
}
public bool HasColorShift(string name)
{
var index = GetPaletteIndex(name);
return colorShiftBuffer[4 * index + 2] != 0 || colorShiftBuffer[4 * index + 3] != 0;
}
public void Initialize() public void Initialize()
{ {
CopyModifiablePalettesToBuffer(); CopyModifiablePalettesToBuffer();
@@ -118,27 +97,26 @@ namespace OpenRA.Graphics
void CopyModifiablePalettesToBuffer() void CopyModifiablePalettesToBuffer()
{ {
foreach (var kvp in mutablePalettes) foreach (var kvp in modifiablePalettes)
CopyPaletteToBuffer(indices[kvp.Key], kvp.Value); CopyPaletteToBuffer(indices[kvp.Key], kvp.Value);
} }
void CopyBufferToTexture() void CopyBufferToTexture()
{ {
Texture.SetData(buffer, Palette.Size, Height); Texture.SetData(buffer, Palette.Size, Height);
ColorShifts.SetFloatData(colorShiftBuffer, 1, Height);
} }
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods) public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)
{ {
foreach (var mod in paletteMods) foreach (var mod in paletteMods)
mod.AdjustPalette(mutablePalettes); mod.AdjustPalette(readOnlyModifiablePalettes);
// Update our texture with the changes. // Update our texture with the changes.
CopyModifiablePalettesToBuffer(); CopyModifiablePalettesToBuffer();
CopyBufferToTexture(); CopyBufferToTexture();
// Reset modified palettes back to their original colors, ready for next time. // Reset modified palettes back to their original colors, ready for next time.
foreach (var kvp in mutablePalettes) foreach (var kvp in modifiablePalettes)
{ {
var originalPalette = palettes[kvp.Key]; var originalPalette = palettes[kvp.Key];
var modifiedPalette = kvp.Value; var modifiedPalette = kvp.Value;
@@ -149,7 +127,6 @@ namespace OpenRA.Graphics
public void Dispose() public void Dispose()
{ {
Texture.Dispose(); Texture.Dispose();
ColorShifts.Dispose();
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -10,7 +10,6 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Primitives; using OpenRA.Primitives;
@@ -30,7 +29,7 @@ namespace OpenRA.Graphics
Rectangle AggregateBounds { get; } Rectangle AggregateBounds { get; }
} }
public readonly struct ModelRenderData public struct ModelRenderData
{ {
public readonly int Start; public readonly int Start;
public readonly int Count; public readonly int Count;
@@ -46,7 +45,6 @@ namespace OpenRA.Graphics
public interface IModelCache : IDisposable public interface IModelCache : IDisposable
{ {
IModel GetModel(string model);
IModel GetModelSequence(string model, string sequence); IModel GetModelSequence(string model, string sequence);
bool HasModelSequence(string model, string sequence); bool HasModelSequence(string model, string sequence);
IVertexBuffer<Vertex> VertexBuffer { get; } IVertexBuffer<Vertex> VertexBuffer { get; }
@@ -64,15 +62,10 @@ namespace OpenRA.Graphics
class PlaceholderModelCache : IModelCache class PlaceholderModelCache : IModelCache
{ {
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException(); public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
public void Dispose() { } public void Dispose() { }
public IModel GetModel(string model)
{
throw new NotImplementedException();
}
public IModel GetModelSequence(string model, string sequence) public IModel GetModelSequence(string model, string sequence)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -84,7 +77,6 @@ namespace OpenRA.Graphics
} }
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "IDE0060:Remove unused parameter", Justification = "Load game API")]
public PlaceholderModelSequenceLoader(ModData modData) { } public PlaceholderModelSequenceLoader(ModData modData) { }
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions) public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public readonly struct ModelAnimation public struct ModelAnimation
{ {
public readonly IModel Model; public readonly IModel Model;
public readonly Func<WVec> OffsetFunc; public readonly Func<WVec> OffsetFunc;
@@ -46,6 +46,12 @@ namespace OpenRA.Graphics
xy.Y + (int)(r.Bottom * scale)); xy.Y + (int)(r.Bottom * scale));
} }
public bool IsVisible => DisableFunc == null || !DisableFunc(); public bool IsVisible
{
get
{
return DisableFunc == null || !DisableFunc();
}
}
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -42,7 +42,6 @@ namespace OpenRA.Graphics
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 }; static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1); static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2); static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
static readonly float[] GroundNormal = { 0, 0, 1, 1 };
readonly Renderer renderer; readonly Renderer renderer;
readonly IShader shader; readonly IShader shader;
@@ -65,7 +64,7 @@ namespace OpenRA.Graphics
shader.SetTexture("Palette", palette); shader.SetTexture("Palette", palette);
} }
public void SetViewportParams() public void SetViewportParams(Size screen, int2 scroll)
{ {
var a = 2f / renderer.SheetSize; var a = 2f / renderer.SheetSize;
var view = new[] var view = new[]
@@ -81,7 +80,7 @@ namespace OpenRA.Graphics
public ModelRenderProxy RenderAsync( public ModelRenderProxy RenderAsync(
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale, WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
in WRot groundOrientation, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor, float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette) PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
{ {
if (!isInFrame) if (!isInFrame)
@@ -93,10 +92,7 @@ namespace OpenRA.Graphics
// Correct for bogus light source definition // Correct for bogus light source definition
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix()); var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix()); var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
var ground = Util.MakeFloatMatrix(groundOrientation.AsMatrix()); var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
var shadowTransform = Util.MatrixMultiply(Util.MatrixMultiply(lightPitch, lightYaw), Util.MatrixInverse(ground));
var groundNormal = Util.MatrixVectorMultiply(ground, GroundNormal);
var invShadowTransform = Util.MatrixInverse(shadowTransform); var invShadowTransform = Util.MatrixInverse(shadowTransform);
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix()); var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
@@ -209,7 +205,7 @@ namespace OpenRA.Graphics
var t = m.Model.TransformationMatrix(i, frame); var t = m.Model.TransformationMatrix(i, frame);
var it = Util.MatrixInverse(t); var it = Util.MatrixInverse(t);
if (it == null) if (it == null)
throw new InvalidOperationException($"Failed to invert the transformed matrix of frame {i} during RenderAsync."); throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
// Transform light vector from shadow -> world -> limb coords // Transform light vector from shadow -> world -> limb coords
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform)); var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -42,10 +42,9 @@ namespace OpenRA.Graphics
class ReadOnlyPalette : IPalette class ReadOnlyPalette : IPalette
{ {
readonly IPalette palette; IPalette palette;
public ReadOnlyPalette(IPalette palette) { this.palette = palette; } public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
public uint this[int index] => palette[index]; public uint this[int index] { get { return palette[index]; } }
public void CopyToArray(Array destination, int destinationOffset) public void CopyToArray(Array destination, int destinationOffset)
{ {
palette.CopyToArray(destination, destinationOffset); palette.CopyToArray(destination, destinationOffset);
@@ -57,25 +56,28 @@ namespace OpenRA.Graphics
{ {
readonly uint[] colors = new uint[Palette.Size]; readonly uint[] colors = new uint[Palette.Size];
public uint this[int index] => colors[index]; public uint this[int index]
{
get { return colors[index]; }
}
public void CopyToArray(Array destination, int destinationOffset) public void CopyToArray(Array destination, int destinationOffset)
{ {
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4); Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
} }
public ImmutablePalette(string filename, int[] remapTransparent, int[] remap) public ImmutablePalette(string filename, int[] remap)
{ {
using (var s = File.OpenRead(filename)) using (var s = File.OpenRead(filename))
LoadFromStream(s, remapTransparent, remap); LoadFromStream(s, remap);
} }
public ImmutablePalette(Stream s, int[] remapTransparent, int[] remapShadow) public ImmutablePalette(Stream s, int[] remapShadow)
{ {
LoadFromStream(s, remapTransparent, remapShadow); LoadFromStream(s, remapShadow);
} }
void LoadFromStream(Stream s, int[] remapTransparent, int[] remapShadow) void LoadFromStream(Stream s, int[] remapShadow)
{ {
using (var reader = new BinaryReader(s)) using (var reader = new BinaryReader(s))
for (var i = 0; i < Palette.Size; i++) for (var i = 0; i < Palette.Size; i++)
@@ -92,9 +94,7 @@ namespace OpenRA.Graphics
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b); colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
} }
foreach (var i in remapTransparent) colors[0] = 0; // Convert black background to transparency.
colors[i] = 0;
foreach (var i in remapShadow) foreach (var i in remapShadow)
colors[i] = 140u << 24; colors[i] = 140u << 24;
} }
@@ -108,7 +108,7 @@ namespace OpenRA.Graphics
public ImmutablePalette(IPalette p) public ImmutablePalette(IPalette p)
{ {
for (var i = 0; i < Palette.Size; i++) for (int i = 0; i < Palette.Size; i++)
colors[i] = p[i]; colors[i] = p[i];
} }
@@ -126,8 +126,8 @@ namespace OpenRA.Graphics
public uint this[int index] public uint this[int index]
{ {
get => colors[index]; get { return colors[index]; }
set => colors[index] = value; set { colors[index] = value; }
} }
public void CopyToArray(Array destination, int destinationOffset) public void CopyToArray(Array destination, int destinationOffset)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
public readonly string Name; public readonly string Name;
public IPalette Palette { get; internal set; } public IPalette Palette { get; internal set; }
public float TextureIndex => index / hardwarePalette.Height; public float TextureIndex { get { return index / hardwarePalette.Height; } }
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height; public float TextureMidIndex { get { return (index + 0.5f) / hardwarePalette.Height; } }
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette) public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
{ {
@@ -28,7 +28,5 @@ namespace OpenRA.Graphics
this.index = index; this.index = index;
this.hardwarePalette = hardwarePalette; this.hardwarePalette = hardwarePalette;
} }
public bool HasColorShift => hardwarePalette.HasColorShift(Name);
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -59,7 +59,6 @@ namespace OpenRA
int DisplayCount { get; } int DisplayCount { get; }
int CurrentDisplay { get; } int CurrentDisplay { get; }
bool HasInputFocus { get; } bool HasInputFocus { get; }
bool IsSuspended { get; }
event Action<float, float, float, float> OnWindowScaleChanged; event Action<float, float, float, float> OnWindowScaleChanged;
@@ -72,7 +71,6 @@ namespace OpenRA
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble); IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
void SetHardwareCursor(IHardwareCursor cursor); void SetHardwareCursor(IHardwareCursor cursor);
void SetWindowTitle(string title);
void SetRelativeMouseMode(bool mode); void SetRelativeMouseMode(bool mode);
void SetScaleModifier(float scale); void SetScaleModifier(float scale);
@@ -84,7 +82,6 @@ namespace OpenRA
public interface IGraphicsContext : IDisposable public interface IGraphicsContext : IDisposable
{ {
IVertexBuffer<Vertex> CreateVertexBuffer(int size); IVertexBuffer<Vertex> CreateVertexBuffer(int size);
Vertex[] CreateVertices(int size);
ITexture CreateTexture(); ITexture CreateTexture();
IFrameBuffer CreateFrameBuffer(Size s); IFrameBuffer CreateFrameBuffer(Size s);
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor); IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
@@ -106,11 +103,6 @@ namespace OpenRA
{ {
void Bind(); void Bind();
void SetData(T[] vertices, int length); void SetData(T[] vertices, int length);
/// <summary>
/// Upon return `vertices` may reference another array object of at least the same size - containing random values.
/// </summary>
void SetData(ref T[] vertices, int length);
void SetData(T[] vertices, int offset, int start, int length); void SetData(T[] vertices, int offset, int start, int length);
} }
@@ -130,8 +122,8 @@ namespace OpenRA
public interface ITexture : IDisposable public interface ITexture : IDisposable
{ {
void SetData(uint[,] colors);
void SetData(byte[] colors, int width, int height); void SetData(byte[] colors, int width, int height);
void SetFloatData(float[] data, int width, int height);
byte[] GetData(); byte[] GetData();
Size Size { get; } Size Size { get; }
TextureScaleFilter ScaleFilter { get; set; } TextureScaleFilter ScaleFilter { get; set; }
@@ -153,7 +145,7 @@ namespace OpenRA
TriangleList, TriangleList,
} }
public readonly struct Range<T> public struct Range<T>
{ {
public readonly T Start, End; public readonly T Start, End;
public Range(T start, T end) { Start = start; End = end; } public Range(T start, T end) { Start = start; End = end; }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -10,6 +10,7 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Primitives; using OpenRA.Primitives;
@@ -17,35 +18,43 @@ namespace OpenRA.Graphics
{ {
public class PlayerColorRemap : IPaletteRemap public class PlayerColorRemap : IPaletteRemap
{ {
readonly int[] remapIndices; Dictionary<int, Color> remapColors;
readonly float hue;
readonly float saturation;
public PlayerColorRemap(int[] remapIndices, float hue, float saturation) public static int GetRemapIndex(int[] ramp, int i)
{ {
this.remapIndices = remapIndices; return ramp[i];
this.hue = hue; }
this.saturation = saturation;
public PlayerColorRemap(int[] ramp, Color c, float rampFraction)
{
var h = c.GetHue() / 360.0f;
var s = c.GetSaturation();
var l = c.GetBrightness();
// Increase luminosity if required to represent the full ramp
var rampRange = (byte)((1 - rampFraction) * l);
var c1 = Color.FromAhsl(h, s, Math.Max(rampRange, l));
var c2 = Color.FromAhsl(h, s, (byte)Math.Max(0, l - rampRange));
var baseIndex = ramp[0];
var remapRamp = ramp.Select(r => r - ramp[0]);
var rampMaxIndex = ramp.Length - 1;
// reversed remapping
if (ramp[0] > ramp[rampMaxIndex])
{
baseIndex = ramp[rampMaxIndex];
for (var i = rampMaxIndex; i > 0; i--)
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
}
remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
.ToDictionary(u => u.Item1, u => u.Item2);
} }
public Color GetRemappedColor(Color original, int index) public Color GetRemappedColor(Color original, int index)
{ {
if (!remapIndices.Contains(index)) return remapColors.TryGetValue(index, out var c)
return original; ? c : original;
// Color remapping is applied in a linear color space, so start
// by undoing the pre-multiplied alpha and gamma corrections
var (r, g, b) = original.ToLinear();
// Calculate the brightness (i.e HSV value) of the original colour
// This inlines the single line of Color.RgbToHsv() that we need
var value = Math.Max(Math.Max(r, g), b);
// Construct the new RGB color
(r, g, b) = Color.HsvToRgb(hue, saturation, value);
// Convert linear back to SRGB and pre-multiply by the alpha
return Color.FromLinear(original.A, r, g, b);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -9,7 +9,6 @@
*/ */
#endregion #endregion
using System;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
@@ -17,38 +16,21 @@ namespace OpenRA.Graphics
public interface IRenderable public interface IRenderable
{ {
WPos Pos { get; } WPos Pos { get; }
PaletteReference Palette { get; }
int ZOffset { get; } int ZOffset { get; }
bool IsDecoration { get; } bool IsDecoration { get; }
IRenderable WithPalette(PaletteReference newPalette);
IRenderable WithZOffset(int newOffset); IRenderable WithZOffset(int newOffset);
IRenderable OffsetBy(in WVec offset); IRenderable OffsetBy(WVec offset);
IRenderable AsDecoration(); IRenderable AsDecoration();
IFinalizedRenderable PrepareRender(WorldRenderer wr); IFinalizedRenderable PrepareRender(WorldRenderer wr);
} }
public interface IPalettedRenderable : IRenderable public interface ITintableRenderable
{ {
PaletteReference Palette { get; } IRenderable WithTint(in float3 newTint);
IPalettedRenderable WithPalette(PaletteReference newPalette);
}
[Flags]
public enum TintModifiers
{
None = 0,
IgnoreWorldTint = 1,
ReplaceColor = 2
}
public interface IModifyableRenderable : IRenderable
{
float Alpha { get; }
float3 Tint { get; }
TintModifiers TintModifiers { get; }
IModifyableRenderable WithAlpha(float newAlpha);
IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers);
} }
public interface IFinalizedRenderable public interface IFinalizedRenderable

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
this.parent = parent; this.parent = parent;
} }
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, BlendMode blendMode = BlendMode.Alpha) public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
{ {
var delta = (end - start) / (end - start).XY.Length; var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z); var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
@@ -52,10 +52,10 @@ namespace OpenRA.Graphics
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0); vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0); vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
} }
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha) public void DrawLine(in float3 start, in float3 end, float width, Color color)
{ {
var delta = (end - start) / (end - start).XY.Length; var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float2(-delta.Y, delta.X); var corner = width / 2 * new float2(-delta.Y, delta.X);
@@ -72,7 +72,7 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0); vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0); vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0); vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
} }
/// <summary> /// <summary>
@@ -90,7 +90,7 @@ namespace OpenRA.Graphics
return new float3(x / d, y / d, 0.5f * (a.Z + b.Z)); return new float3(x / d, y / d, 0.5f * (a.Z + b.Z));
} }
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color, BlendMode blendMode) void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color)
{ {
using (var e = points.GetEnumerator()) using (var e = points.GetEnumerator())
{ {
@@ -101,13 +101,13 @@ namespace OpenRA.Graphics
while (e.MoveNext()) while (e.MoveNext())
{ {
var point = e.Current; var point = e.Current;
DrawLine(lastPoint, point, width, color, blendMode); DrawLine(lastPoint, point, width, color);
lastPoint = point; lastPoint = point;
} }
} }
} }
void DrawConnectedLine(float3[] points, float width, Color color, bool closed, BlendMode blendMode) void DrawConnectedLine(float3[] points, float width, Color color, bool closed)
{ {
// Not a line // Not a line
if (points.Length < 2) if (points.Length < 2)
@@ -116,7 +116,7 @@ namespace OpenRA.Graphics
// Single segment // Single segment
if (points.Length == 2) if (points.Length == 2)
{ {
DrawLine(points[0], points[1], width, color, blendMode); DrawLine(points[0], points[1], width, color);
return; return;
} }
@@ -163,7 +163,7 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0); vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0); vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0); vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
// Advance line segment // Advance line segment
end = next; end = next;
@@ -175,32 +175,32 @@ namespace OpenRA.Graphics
} }
} }
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false, BlendMode blendMode = BlendMode.Alpha) public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false)
{ {
if (!connectSegments) if (!connectSegments)
DrawDisconnectedLine(points, width, color, blendMode); DrawDisconnectedLine(points, width, color);
else else
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false, blendMode); DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false);
} }
public void DrawPolygon(float3[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha) public void DrawPolygon(float3[] vertices, float width, Color color)
{ {
DrawConnectedLine(vertices, width, color, true, blendMode); DrawConnectedLine(vertices, width, color, true);
} }
public void DrawPolygon(float2[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha) public void DrawPolygon(float2[] vertices, float width, Color color)
{ {
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true, blendMode); DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
} }
public void DrawRect(in float3 tl, in float3 br, float width, Color color, BlendMode blendMode = BlendMode.Alpha) public void DrawRect(in float3 tl, in float3 br, float width, Color color)
{ {
var tr = new float3(br.X, tl.Y, tl.Z); var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z); var bl = new float3(tl.X, br.Y, br.Z);
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode); DrawPolygon(new[] { tl, tr, br, bl }, width, color);
} }
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha) public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
{ {
color = Util.PremultiplyAlpha(color); color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f; var cr = color.R / 255.0f;
@@ -211,17 +211,17 @@ namespace OpenRA.Graphics
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0); vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0); vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0); vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
} }
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha) public void FillRect(in float3 tl, in float3 br, Color color)
{ {
var tr = new float3(br.X, tl.Y, tl.Z); var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z); var bl = new float3(tl.X, br.Y, br.Z);
FillRect(tl, tr, br, bl, color, blendMode); FillRect(tl, tr, br, bl, color);
} }
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color, BlendMode blendMode = BlendMode.Alpha) public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
{ {
color = Util.PremultiplyAlpha(color); color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f; var cr = color.R / 255.0f;
@@ -235,10 +235,10 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0); vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0); vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0); vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
} }
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha) public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
{ {
vertices[0] = VertexWithColor(a + Offset, topLeftColor); vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor); vertices[1] = VertexWithColor(b + Offset, topRightColor);
@@ -247,7 +247,7 @@ namespace OpenRA.Graphics
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor); vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
vertices[5] = VertexWithColor(a + Offset, topLeftColor); vertices[5] = VertexWithColor(a + Offset, topLeftColor);
parent.DrawRGBAVertices(vertices, blendMode); parent.DrawRGBAVertices(vertices);
} }
static Vertex VertexWithColor(in float3 xyz, Color color) static Vertex VertexWithColor(in float3 xyz, Color color)
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
return new Vertex(xyz, cr, cg, cb, ca, 0, 0); return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
} }
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha) public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
{ {
// TODO: Create an ellipse polygon instead // TODO: Create an ellipse polygon instead
var a = (br.X - tl.X) / 2; var a = (br.X - tl.X) / 2;
@@ -272,7 +272,7 @@ namespace OpenRA.Graphics
{ {
var z = float2.Lerp(tl.Z, br.Z, (y - tl.Y) / (br.Y - tl.Y)); var z = float2.Lerp(tl.Z, br.Z, (y - tl.Y) / (br.Y - tl.Y));
var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b); var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b);
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color, blendMode); DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -22,36 +22,44 @@ namespace OpenRA.Graphics
this.parent = parent; this.parent = parent;
} }
public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f) public void DrawSprite(Sprite s, in float3 location, in float3 size)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, rotation); parent.DrawSprite(s, location, 0, size);
} }
public void DrawSprite(Sprite s, in float3 location, float scale = 1f, float rotation = 0f) public void DrawSprite(Sprite s, in float3 location)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, rotation); parent.DrawSprite(s, location, 0, s.Size);
} }
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha, float rotation = 0f) public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation); parent.DrawSprite(s, a, b, c, d);
} }
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha) public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
{ {
if (s.Channel != TextureChannel.RGBA) if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite."); throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha); parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -26,7 +26,6 @@ namespace OpenRA.Graphics
int Length { get; } int Length { get; }
int Stride { get; } int Stride { get; }
int Facings { get; } int Facings { get; }
int InterpolatedFacings { get; }
int Tick { get; } int Tick { get; }
int ZOffset { get; } int ZOffset { get; }
int ShadowStart { get; } int ShadowStart { get; }
@@ -34,13 +33,10 @@ namespace OpenRA.Graphics
int[] Frames { get; } int[] Frames { get; }
Rectangle Bounds { get; } Rectangle Bounds { get; }
bool IgnoreWorldTint { get; } bool IgnoreWorldTint { get; }
float Scale { get; }
Sprite GetSprite(int frame); Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing); Sprite GetSprite(int frame, WAngle facing);
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing); Sprite GetShadow(int frame, WAngle facing);
float GetAlpha(int frame);
} }
public interface ISpriteSequenceLoader public interface ISpriteSequenceLoader
@@ -54,7 +50,7 @@ namespace OpenRA.Graphics
readonly string tileSet; readonly string tileSet;
readonly Lazy<Sequences> sequences; readonly Lazy<Sequences> sequences;
readonly Lazy<SpriteCache> spriteCache; readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache => spriteCache.Value; public SpriteCache SpriteCache { get { return spriteCache.Value; } }
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>(); readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
@@ -74,15 +70,15 @@ namespace OpenRA.Graphics
public ISpriteSequence GetSequence(string unitName, string sequenceName) public ISpriteSequence GetSequence(string unitName, string sequenceName)
{ {
if (!sequences.Value.TryGetValue(unitName, out var unitSeq)) if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined."); throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq)) if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`"); throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
return seq; return seq;
} }
public IEnumerable<string> Images => sequences.Value.Keys; public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
public bool HasSequence(string unitName) public bool HasSequence(string unitName)
{ {
@@ -92,7 +88,7 @@ namespace OpenRA.Graphics
public bool HasSequence(string unitName, string sequenceName) public bool HasSequence(string unitName, string sequenceName)
{ {
if (!sequences.Value.TryGetValue(unitName, out var unitSeq)) if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined."); throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.ContainsKey(sequenceName); return unitSeq.Value.ContainsKey(sequenceName);
} }
@@ -100,7 +96,7 @@ namespace OpenRA.Graphics
public IEnumerable<string> Sequences(string unitName) public IEnumerable<string> Sequences(string unitName)
{ {
if (!sequences.Value.TryGetValue(unitName, out var unitSeq)) if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined."); throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.Keys; return unitSeq.Value.Keys;
} }
@@ -127,7 +123,7 @@ namespace OpenRA.Graphics
} }
} }
return items; return new ReadOnlyDictionary<string, UnitSequences>(items);
} }
public void Preload() public void Preload()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -32,7 +32,7 @@ namespace OpenRA.Graphics
return data; return data;
} }
public bool Buffered => data != null || texture == null; public bool Buffered { get { return data != null || texture == null; } }
public Sheet(SheetType type, Size size) public Sheet(SheetType type, Size size)
{ {
@@ -79,17 +79,21 @@ namespace OpenRA.Graphics
public Png AsPng() public Png AsPng()
{ {
if (Type == SheetType.Indexed) var data = GetData();
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height); // Convert BGRA to RGBA
for (var i = 0; i < Size.Width * Size.Height; i++)
{
var temp = data[i * 4];
data[i * 4] = data[i * 4 + 2];
data[i * 4 + 2] = temp;
}
return new Png(data, Size.Width, Size.Height);
} }
public Png AsPng(TextureChannel channel, IPalette pal) public Png AsPng(TextureChannel channel, IPalette pal)
{ {
if (Type != SheetType.Indexed)
throw new InvalidOperationException("AsPng(TextureChannel, IPalette) can only be called on Indexed sheets.");
var d = GetData(); var d = GetData();
var plane = new byte[Size.Width * Size.Height]; var plane = new byte[Size.Width * Size.Height];
var dataStride = 4 * Size.Width; var dataStride = 4 * Size.Width;
@@ -103,7 +107,7 @@ namespace OpenRA.Graphics
for (var i = 0; i < Palette.Size; i++) for (var i = 0; i < Palette.Size; i++)
palColors[i] = pal.GetColor(i); palColors[i] = pal.GetColor(i);
return new Png(plane, SpriteFrameType.Indexed8, Size.Width, Size.Height, palColors); return new Png(plane, Size.Width, Size.Height, palColors);
} }
public void CreateBuffer() public void CreateBuffer()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -52,16 +52,9 @@ namespace OpenRA.Graphics
{ {
switch (t) switch (t)
{ {
case SpriteFrameType.Indexed8: case SpriteFrameType.Indexed: return SheetType.Indexed;
return SheetType.Indexed; case SpriteFrameType.BGRA: return SheetType.BGRA;
default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t));
// Util.FastCopyIntoChannel will automatically convert these to BGRA
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
return SheetType.BGRA;
default: throw new NotImplementedException($"Unknown SpriteFrameType {t}");
} }
} }
@@ -81,16 +74,16 @@ namespace OpenRA.Graphics
this.margin = margin; this.margin = margin;
} }
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); } public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); } public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset) public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
{ {
// Don't bother allocating empty sprites // Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0) if (size.Width == 0 || size.Height == 0)
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha); return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
var rect = Allocate(size, zRamp, spriteOffset); var rect = Allocate(size, zRamp, spriteOffset);
Util.FastCopyIntoChannel(rect, src, type); Util.FastCopyIntoChannel(rect, src);
current.CommitBufferedData(); current.CommitBufferedData();
return rect; return rect;
} }
@@ -103,6 +96,15 @@ namespace OpenRA.Graphics
return rect; return rect;
} }
public Sprite Add(Size size, byte paletteIndex)
{
var data = new byte[size.Width * size.Height];
for (var i = 0; i < data.Length; i++)
data[i] = paletteIndex;
return Add(data, size);
}
TextureChannel? NextChannel(TextureChannel t) TextureChannel? NextChannel(TextureChannel t)
{ {
var nextChannel = (int)t + (int)Type; var nextChannel = (int)t + (int)Type;
@@ -147,9 +149,9 @@ namespace OpenRA.Graphics
return rect; return rect;
} }
public Sheet Current => current; public Sheet Current { get { return current; } }
public TextureChannel CurrentChannel => channel; public TextureChannel CurrentChannel { get { return channel; } }
public IEnumerable<Sheet> AllSheets => sheets; public IEnumerable<Sheet> AllSheets { get { return sheets; } }
public void Dispose() public void Dispose()
{ {

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -23,6 +23,7 @@ namespace OpenRA.Graphics
public readonly float ZRamp; public readonly float ZRamp;
public readonly float3 Size; public readonly float3 Size;
public readonly float3 Offset; public readonly float3 Offset;
public readonly float3 FractionalOffset;
public readonly float Top, Left, Bottom, Right; public readonly float Top, Left, Bottom, Right;
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1) public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
@@ -37,16 +38,13 @@ namespace OpenRA.Graphics
Channel = channel; Channel = channel;
Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp); Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
BlendMode = blendMode; BlendMode = blendMode;
FractionalOffset = Size.Z != 0 ? offset / Size :
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
// Some GPUs suffer from precision issues when rendering into non 1:1 framebuffers that result Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
// in rendering a line of texels that sample outside the sprite rectangle. Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
// Insetting the texture coordinates by a small fraction of a pixel avoids this Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
// with negligible impact on the 1:1 rendering case. Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
var inset = 1 / 128f;
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -10,6 +10,7 @@
#endregion #endregion
using System; using System;
using System.Linq;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Support; using OpenRA.Support;
@@ -17,9 +18,10 @@ namespace OpenRA.Graphics
{ {
public sealed class SpriteFont : IDisposable public sealed class SpriteFont : IDisposable
{ {
public int TopOffset { get; } public int TopOffset { get; private set; }
readonly int size; readonly int size;
readonly SheetBuilder builder; readonly SheetBuilder builder;
readonly Func<string, float> lineWidth;
readonly IFont font; readonly IFont font;
readonly Cache<char, GlyphInfo> glyphs; readonly Cache<char, GlyphInfo> glyphs;
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs; readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
@@ -30,7 +32,7 @@ namespace OpenRA.Graphics
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder) public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
{ {
if (builder.Type != SheetType.BGRA) if (builder.Type != SheetType.BGRA)
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder)); throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
deviceScale = scale; deviceScale = scale;
this.size = size; this.size = size;
@@ -41,9 +43,13 @@ namespace OpenRA.Graphics
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph); contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap); dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
// PERF: Cache these delegates for Measure calls.
Func<char, float> characterWidth = character => glyphs[character].Advance;
lineWidth = line => line.Sum(characterWidth) / deviceScale;
// Pre-cache small font sizes so glyphs are immediately available when we need them // Pre-cache small font sizes so glyphs are immediately available when we need them
if (size <= 24) if (size <= 24)
using (new PerfTimer($"Precache {name} {size}px")) using (new PerfTimer("Precache {0} {1}px".F(name, size)))
for (var n = (char)0x20; n < (char)0x7f; n++) for (var n = (char)0x20; n < (char)0x7f; n++)
if (glyphs[n] == null) if (glyphs[n] == null)
throw new InvalidOperationException(); throw new InvalidOperationException();
@@ -83,10 +89,10 @@ namespace OpenRA.Graphics
if (g.Sprite != null) if (g.Sprite != null)
{ {
var contrastSprite = contrastGlyphs[(s, screenContrast)]; var contrastSprite = contrastGlyphs[(s, screenContrast)];
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite, Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
(screen + g.Offset - contrastVector) / deviceScale, (screen + g.Offset - contrastVector) / deviceScale,
1f / deviceScale, contrastSprite.Size / deviceScale,
tint, 1f); tint);
} }
screen += new int2((int)(g.Advance + 0.5f), 0); screen += new int2((int)(g.Advance + 0.5f), 0);
@@ -114,10 +120,10 @@ namespace OpenRA.Graphics
// Convert screen coordinates back to UI coordinates for drawing // Convert screen coordinates back to UI coordinates for drawing
if (g.Sprite != null) if (g.Sprite != null)
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite, Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
(screen + g.Offset).ToFloat2() / deviceScale, (screen + g.Offset).ToFloat2() / deviceScale,
1f / deviceScale, g.Sprite.Size / deviceScale,
tint, 1f); tint);
screen += new int2((int)(g.Advance + 0.5f), 0); screen += new int2((int)(g.Advance + 0.5f), 0);
} }
@@ -166,12 +172,12 @@ namespace OpenRA.Graphics
// Offset rotated glyph to align the top-left corner with the screen pixel grid // Offset rotated glyph to align the top-left corner with the screen pixel grid
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra; var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite, Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
ra + screenOffset, ra + screenOffset,
rb + screenOffset, rb + screenOffset,
rc + screenOffset, rc + screenOffset,
rd + screenOffset, rd + screenOffset,
tint, 1f); tint);
} }
p += new float2(g.Advance / deviceScale, 0); p += new float2(g.Advance / deviceScale, 0);
@@ -232,26 +238,16 @@ namespace OpenRA.Graphics
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
return new int2(0, size); return new int2(0, size);
var lines = text.SplitLines('\n'); var lines = text.Split('\n');
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
var maxWidth = 0f;
var rows = 0;
foreach (var line in lines)
{
rows++;
maxWidth = Math.Max(maxWidth, LineWidth(line));
}
return new int2((int)Math.Ceiling(maxWidth), rows * size);
} }
float LineWidth(ReadOnlySpan<char> line) static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
{ {
var result = 0f; var maxWidth = 0f;
foreach (var c in line) foreach (var line in lines)
result += glyphs[c].Advance; maxWidth = Math.Max(maxWidth, lineWidth(line));
return maxWidth;
return result / deviceScale;
} }
GlyphInfo CreateGlyph(char c) GlyphInfo CreateGlyph(char c)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -18,33 +18,11 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
/// <summary> public enum SpriteFrameType { Indexed, BGRA }
/// Describes the format of the pixel data in a ISpriteFrame.
/// Note that the channel order is defined for little-endian bytes, so BGRA corresponds
/// to a 32bit ARGB value, such as that returned by Color.ToArgb()!
/// </summary>
public enum SpriteFrameType
{
// 8 bit index into an external palette
Indexed8,
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
// (remember that little-endian systems place the little bits in the first byte!)
Bgra32,
// Like BGRA, but without an alpha channel
Bgr24,
// 32 bit color in big-endian format, like png
Rgba32,
// Like RGBA, but without an alpha channel
Rgb24
}
public interface ISpriteLoader public interface ISpriteLoader
{ {
bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata); bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
} }
public interface ISpriteFrame public interface ISpriteFrame
@@ -69,7 +47,7 @@ namespace OpenRA.Graphics
public class SpriteCache public class SpriteCache
{ {
public readonly Cache<SheetType, SheetBuilder> SheetBuilders; public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders; readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem; readonly IReadOnlyFileSystem fileSystem;
@@ -79,7 +57,7 @@ namespace OpenRA.Graphics
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders) public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{ {
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t)); SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
this.fileSystem = fileSystem; this.fileSystem = fileSystem;
this.loaders = loaders; this.loaders = loaders;
@@ -125,7 +103,7 @@ namespace OpenRA.Graphics
{ {
if (unloaded[i] != null) if (unloaded[i] != null)
{ {
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]); sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
unloaded[i] = null; unloaded[i] = null;
} }
} }
@@ -164,7 +142,7 @@ namespace OpenRA.Graphics
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _)); frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
} }
public ISpriteFrame[] this[string filename] => frames[filename]; public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
} }
public static class FrameLoader public static class FrameLoader
@@ -173,7 +151,7 @@ namespace OpenRA.Graphics
{ {
using (var stream = fileSystem.Open(filename)) using (var stream = fileSystem.Open(filename))
{ {
var spriteFrames = GetFrames(stream, loaders, filename, out metadata); var spriteFrames = GetFrames(stream, loaders, out metadata);
if (spriteFrames == null) if (spriteFrames == null)
throw new InvalidDataException(filename + " is not a valid sprite file!"); throw new InvalidDataException(filename + " is not a valid sprite file!");
@@ -181,12 +159,12 @@ namespace OpenRA.Graphics
} }
} }
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, string filename, out TypeDictionary metadata) public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
{ {
metadata = null; metadata = null;
foreach (var loader in loaders) foreach (var loader in loaders)
if (loader.TryParseSprite(stream, filename, out var frames, out metadata)) if (loader.TryParseSprite(stream, out var frames, out metadata))
return frames; return frames;
return null; return null;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -9,15 +9,14 @@
*/ */
#endregion #endregion
using System;
using System.Collections.Generic; using System.Collections.Generic;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
{ {
public static readonly IEnumerable<IRenderable> None = Array.Empty<IRenderable>(); public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
readonly Sprite sprite; readonly Sprite sprite;
readonly WPos pos; readonly WPos pos;
@@ -25,14 +24,17 @@ namespace OpenRA.Graphics
readonly int zOffset; readonly int zOffset;
readonly PaletteReference palette; readonly PaletteReference palette;
readonly float scale; readonly float scale;
readonly WAngle rotation = WAngle.Zero;
readonly float3 tint; readonly float3 tint;
readonly TintModifiers tintModifiers;
readonly float alpha;
readonly bool isDecoration; readonly bool isDecoration;
readonly bool ignoreWorldTint;
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha, public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
float3 tint, TintModifiers tintModifiers, bool isDecoration, WAngle rotation) : this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
{ {
this.sprite = sprite; this.sprite = sprite;
this.pos = pos; this.pos = pos;
@@ -40,83 +42,46 @@ namespace OpenRA.Graphics
this.zOffset = zOffset; this.zOffset = zOffset;
this.palette = palette; this.palette = palette;
this.scale = scale; this.scale = scale;
this.rotation = rotation;
this.tint = tint; this.tint = tint;
this.isDecoration = isDecoration; this.isDecoration = isDecoration;
this.tintModifiers = tintModifiers; this.ignoreWorldTint = ignoreWorldTint;
this.alpha = alpha;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
this.palette = null;
} }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha, public WPos Pos { get { return pos + offset; } }
float3 tint, TintModifiers tintModifiers, bool isDecoration) public WVec Offset { get { return offset; } }
: this(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, WAngle.Zero) { } public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return isDecoration; } }
public WPos Pos => pos + offset; public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
public WVec Offset => offset; public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public PaletteReference Palette => palette; public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public int ZOffset => zOffset; public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public bool IsDecoration => isDecoration;
public float Alpha => alpha; public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
public float3 Tint => tint;
public TintModifiers TintModifiers => tintModifiers;
public IPalettedRenderable WithPalette(PaletteReference newPalette)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
}
public IRenderable WithZOffset(int newOffset)
{
return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
}
public IRenderable OffsetBy(in WVec vec)
{
return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, rotation);
}
public IRenderable AsDecoration()
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true, rotation);
}
public IModifyableRenderable WithAlpha(float newAlpha)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration, rotation);
}
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration, rotation);
}
float3 ScreenPosition(WorldRenderer wr) float3 ScreenPosition(WorldRenderer wr)
{ {
var s = 0.5f * scale * sprite.Size; var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset) - (0.5f * scale * sprite.Size.XY).ToInt2();
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(offset) - new float3((int)s.X, (int)s.Y, s.Z);
// HACK: The z offset needs to be applied somewhere, but this probably is the wrong place.
return new float3(xy, sprite.Offset.Z + wr.ScreenZPosition(pos, 0) - 0.5f * scale * sprite.Size.Z);
} }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr) public void Render(WorldRenderer wr)
{ {
var wsr = Game.Renderer.WorldSpriteRenderer; var wsr = Game.Renderer.WorldSpriteRenderer;
var t = alpha * tint; if (ignoreWorldTint)
if (wr.TerrainLighting != null && (tintModifiers & TintModifiers.IgnoreWorldTint) == 0) wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
t *= wr.TerrainLighting.TintAt(pos); else
{
var t = tint;
if (wr.TerrainLighting != null)
t *= wr.TerrainLighting.TintAt(pos);
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
var a = alpha; }
if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;
wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
} }
public void RenderDebugGeometry(WorldRenderer wr) public void RenderDebugGeometry(WorldRenderer wr)
@@ -124,16 +89,13 @@ namespace OpenRA.Graphics
var pos = ScreenPosition(wr) + sprite.Offset; var pos = ScreenPosition(wr) + sprite.Offset;
var tl = wr.Viewport.WorldToViewPx(pos); var tl = wr.Viewport.WorldToViewPx(pos);
var br = wr.Viewport.WorldToViewPx(pos + sprite.Size); var br = wr.Viewport.WorldToViewPx(pos + sprite.Size);
if (rotation == WAngle.Zero) Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(tl, br - tl, rotation.RendererRadians()), 1, Color.Red);
} }
public Rectangle ScreenBounds(WorldRenderer wr) public Rectangle ScreenBounds(WorldRenderer wr)
{ {
var screenOffset = ScreenPosition(wr) + sprite.Offset; var screenOffset = ScreenPosition(wr) + sprite.Offset;
return Util.BoundingRectangle(screenOffset, sprite.Size, rotation.RendererRadians()); return new Rectangle((int)screenOffset.X, (int)screenOffset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -10,20 +10,20 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic; using System.Linq;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public class SpriteRenderer : Renderer.IBatchRenderer public class SpriteRenderer : Renderer.IBatchRenderer
{ {
public const int SheetCount = 8; const int SheetCount = 7;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}"); static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
readonly Renderer renderer; readonly Renderer renderer;
readonly IShader shader; readonly IShader shader;
Vertex[] vertices; readonly Vertex[] vertices;
readonly Sheet[] sheets = new Sheet[SheetCount]; readonly Sheet[] sheets = new Sheet[SheetCount];
BlendMode currentBlend = BlendMode.Alpha; BlendMode currentBlend = BlendMode.Alpha;
@@ -34,7 +34,7 @@ namespace OpenRA.Graphics
{ {
this.renderer = renderer; this.renderer = renderer;
this.shader = shader; this.shader = shader;
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize); vertices = new Vertex[renderer.TempBufferSize];
} }
public void Flush() public void Flush()
@@ -49,9 +49,7 @@ namespace OpenRA.Graphics
renderer.Context.SetBlendMode(currentBlend); renderer.Context.SetBlendMode(currentBlend);
shader.PrepareRender(); shader.PrepareRender();
renderer.DrawBatch(vertices, nv, PrimitiveType.TriangleList);
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
renderer.Context.SetBlendMode(BlendMode.None); renderer.Context.SetBlendMode(BlendMode.None);
nv = 0; nv = 0;
@@ -83,170 +81,119 @@ namespace OpenRA.Graphics
for (; secondarySheetIndex < ns; secondarySheetIndex++) for (; secondarySheetIndex < ns; secondarySheetIndex++)
if (sheets[secondarySheetIndex] == secondarySheet) if (sheets[secondarySheetIndex] == secondarySheet)
break; break;
// If neither sheet has been mapped both index values will be set to ns.
// This is fine if they both reference the same texture, but if they don't
// we must increment the secondary sheet index to the next free sampler.
if (secondarySheetIndex == sheetIndex && secondarySheet != sheet)
secondarySheetIndex++;
} }
// Make sure that we have enough free samplers to map both if needed, otherwise flush // Make sure that we have enough free samplers to map both if needed, otherwise flush
if (Math.Max(sheetIndex, secondarySheetIndex) >= sheets.Length) var needSamplers = (sheetIndex == ns ? 1 : 0) + (secondarySheetIndex == ns ? 1 : 0);
if (ns + needSamplers >= sheets.Length)
{ {
Flush(); Flush();
sheetIndex = 0; sheetIndex = 0;
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0; if (ss != null)
secondarySheetIndex = 1;
} }
if (sheetIndex >= ns) if (sheetIndex >= ns)
{ {
sheets[sheetIndex] = sheet; sheets[sheetIndex] = sheet;
ns++; ns += 1;
} }
if (secondarySheetIndex >= ns && ss != null) if (secondarySheetIndex >= ns && ss != null)
{ {
sheets[secondarySheetIndex] = ss.SecondarySheet; sheets[secondarySheetIndex] = ss.SecondarySheet;
ns++; ns += 1;
} }
return new int2(sheetIndex, secondarySheetIndex); return new int2(sheetIndex, secondarySheetIndex);
} }
float ResolveTextureIndex(Sprite s, PaletteReference pal) internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
{
if (pal == null)
return 0;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (s.Channel == TextureChannel.RGBA && !pal.HasColorShift)
return 0;
return pal.TextureIndex;
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
1f, rotation);
nv += 6; nv += 6;
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f) public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
{
DrawSprite(s, location, pal.TextureIndex, s.Size);
}
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
{
DrawSprite(s, location, pal.TextureIndex, size);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
1f, rotation);
nv += 6; nv += 6;
} }
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f) internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
{
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
float rotation = 0f)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha, Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
rotation);
nv += 6; nv += 6;
} }
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha, public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
float rotation = 0f)
{ {
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation); DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
} }
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha) public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{ {
var samplers = SetRenderStateForSprite(s); var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv); Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
nv += 6; nv += 6;
} }
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode) public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
{ {
var i = 0; shader.SetTexture("Texture0", sheet.GetTexture());
foreach (var s in sheets)
{
if (i >= SheetCount)
ThrowSheetOverflow(nameof(sheets));
if (s != null)
shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture());
}
renderer.Context.SetBlendMode(blendMode); renderer.Context.SetBlendMode(blendMode);
shader.PrepareRender(); shader.PrepareRender();
renderer.DrawBatch(buffer, start, length, type); renderer.DrawBatch(buffer, start, length, type);
renderer.Context.SetBlendMode(BlendMode.None); renderer.Context.SetBlendMode(BlendMode.None);
} }
// PERF: methods that throw won't be inlined by the JIT, so extract a static helper for use on hot paths
static void ThrowSheetOverflow(string paramName)
{
throw new ArgumentException($"SpriteRenderer only supports {SheetCount} simultaneous textures", paramName);
}
// For RGBAColorRenderer // For RGBAColorRenderer
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode) internal void DrawRGBAVertices(Vertex[] v)
{ {
renderer.CurrentBatchRenderer = this; renderer.CurrentBatchRenderer = this;
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize) if (currentBlend != BlendMode.Alpha || nv + v.Length > renderer.TempBufferSize)
Flush(); Flush();
currentBlend = blendMode; currentBlend = BlendMode.Alpha;
Array.Copy(v, 0, vertices, nv, v.Length); Array.Copy(v, 0, vertices, nv, v.Length);
nv += v.Length; nv += v.Length;
} }
public void SetPalette(ITexture palette, ITexture colorShifts) public void SetPalette(ITexture palette)
{ {
shader.SetTexture("Palette", palette); shader.SetTexture("Palette", palette);
shader.SetTexture("ColorShifts", colorShifts);
} }
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll) public void SetViewportParams(Size screen, float depthScale, float depthOffset, int2 scroll)
{ {
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1], shader.SetVec("r1",
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2. 2f / screen.Width,
var width = 2f / (downscale * sheetSize.Width); 2f / screen.Height,
var height = 2f / (downscale * sheetSize.Height); -depthScale / screen.Height);
shader.SetVec("r2", -1, -1, 1 - depthOffset);
// Depth is more complicated: // Texture index is sampled as a float, so convert to pixels then scale
// * The OpenGL z axis is inverted (negative is closer) relative to OpenRA (positive is closer). shader.SetVec("DepthTextureScale", 128 * depthScale / screen.Height);
// * We want to avoid clipping pixels that are behind the nominal z == y plane at the
// top of the map, or above the nominal z == y plane at the bottom of the map.
// We therefore expand the depth range by an extra margin that is calculated based on
// the maximum expected world height (see Renderer.InitializeDepthBuffer).
// * Sprites can specify an additional per-pixel depth offset map, which is applied in the
// fragment shader. The fragment shader operates in OpenGL window coordinates, not NDC,
// with a depth range [0, 1] corresponding to the NDC [-1, 1]. We must therefore multiply the
// sprite channel value [0, 1] by 255 to find the pixel depth offset, then by our depth scale
// to find the equivalent NDC offset, then divide by 2 to find the window coordinate offset.
// * If depthMargin == 0 (which indicates per-pixel depth testing is disabled) sprites that
// extend beyond the top of bottom edges of the screen may be pushed outside [-1, 1] and
// culled by the GPU. We avoid this by forcing everything into the z = 0 plane.
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
shader.SetVec("DepthTextureScale", 128 * depth);
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
shader.SetVec("r1", width, height, -depth);
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
} }
public void SetDepthPreview(bool enabled, float contrast, float offset) public void SetDepthPreviewEnabled(bool enabled)
{ {
shader.SetBool("EnableDepthPreview", enabled); shader.SetBool("EnableDepthPreview", enabled);
shader.SetVec("DepthPreviewParams", contrast, offset);
} }
public void SetAntialiasingPixelsPerTexel(float pxPerTx) public void SetAntialiasingPixelsPerTexel(float pxPerTx)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -15,14 +15,14 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public class TargetLineRenderable : IRenderable, IFinalizedRenderable public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
{ {
readonly IEnumerable<WPos> waypoints; readonly IEnumerable<WPos> waypoints;
readonly Color color; readonly Color color;
readonly int width; readonly int width;
readonly int markerSize; readonly int markerSize;
public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width, int markerSize) public TargetLineRenderable(IEnumerable<WPos> waypoints, Color color, int width = 1, int markerSize = 1)
{ {
this.waypoints = waypoints; this.waypoints = waypoints;
this.color = color; this.color = color;
@@ -30,19 +30,14 @@ namespace OpenRA.Graphics
this.markerSize = markerSize; this.markerSize = markerSize;
} }
public WPos Pos => waypoints.First(); public WPos Pos { get { return waypoints.First(); } }
public int ZOffset => 0; public PaletteReference Palette { get { return null; } }
public bool IsDecoration => true; public int ZOffset { get { return 0; } }
public bool IsDecoration { get { return true; } }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec)
{
// Lambdas can't use 'in' variables, so capture a copy for later
var offset = vec;
return new TargetLineRenderable(waypoints.Select(w => w + offset), color, width, markerSize);
}
public IRenderable WithPalette(PaletteReference newPalette) { return new TargetLineRenderable(waypoints, color); }
public IRenderable WithZOffset(int newOffset) { return new TargetLineRenderable(waypoints, color); }
public IRenderable OffsetBy(WVec vec) { return new TargetLineRenderable(waypoints.Select(w => w + vec), color); }
public IRenderable AsDecoration() { return this; } public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
@@ -56,14 +51,14 @@ namespace OpenRA.Graphics
foreach (var b in waypoints.Skip(1).Select(pos => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos)))) foreach (var b in waypoints.Skip(1).Select(pos => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos))))
{ {
Game.Renderer.RgbaColorRenderer.DrawLine(a, b, width, color); Game.Renderer.RgbaColorRenderer.DrawLine(a, b, width, color);
DrawTargetMarker(color, b, markerSize); DrawTargetMarker(wr, color, b, markerSize);
a = b; a = b;
} }
DrawTargetMarker(color, first); DrawTargetMarker(wr, color, first);
} }
public static void DrawTargetMarker(Color color, int2 screenPos, int size = 1) public static void DrawTargetMarker(WorldRenderer wr, Color color, int2 screenPos, int size = 1)
{ {
var offset = new int2(size, size); var offset = new int2(size, size);
var tl = screenPos - offset; var tl = screenPos - offset;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -12,6 +12,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
@@ -19,9 +20,9 @@ namespace OpenRA.Graphics
{ {
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 }; static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
public readonly Sheet Sheet;
public readonly BlendMode BlendMode; public readonly BlendMode BlendMode;
readonly Sheet[] sheets;
readonly Sprite emptySprite; readonly Sprite emptySprite;
readonly IVertexBuffer<Vertex> vertexBuffer; readonly IVertexBuffer<Vertex> vertexBuffer;
@@ -34,22 +35,22 @@ namespace OpenRA.Graphics
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly Map map; readonly Map map;
readonly PaletteReference[] palettes; readonly PaletteReference palette;
public TerrainSpriteLayer(World world, WorldRenderer wr, Sprite emptySprite, BlendMode blendMode, bool restrictToBounds) public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds)
{ {
worldRenderer = wr; worldRenderer = wr;
this.restrictToBounds = restrictToBounds; this.restrictToBounds = restrictToBounds;
this.emptySprite = emptySprite; Sheet = sheet;
sheets = new Sheet[SpriteRenderer.SheetCount];
BlendMode = blendMode; BlendMode = blendMode;
this.palette = palette;
map = world.Map; map = world.Map;
rowStride = 6 * map.MapSize.X; rowStride = 6 * map.MapSize.X;
vertices = new Vertex[rowStride * map.MapSize.Y]; vertices = new Vertex[rowStride * map.MapSize.Y];
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length); vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
wr.PaletteInvalidated += UpdatePaletteIndices; wr.PaletteInvalidated += UpdatePaletteIndices;
@@ -62,11 +63,12 @@ namespace OpenRA.Graphics
void UpdatePaletteIndices() void UpdatePaletteIndices()
{ {
// Everything in the layer uses the same palette,
// so we can fix the indices in one pass
for (var i = 0; i < vertices.Length; i++) for (var i = 0; i < vertices.Length; i++)
{ {
var v = vertices[i]; var v = vertices[i];
var p = palettes[i / 6]?.TextureIndex ?? 0; vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, p, v.C, v.R, v.G, v.B, v.A);
} }
for (var row = 0; row < map.MapSize.Y; row++) for (var row = 0; row < map.MapSize.Y; row++)
@@ -75,24 +77,24 @@ namespace OpenRA.Graphics
public void Clear(CPos cell) public void Clear(CPos cell)
{ {
Update(cell, null, null, 1f, 1f, true); Update(cell, null, true);
} }
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame) public void Update(CPos cell, ISpriteSequence sequence, int frame)
{ {
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint); Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
} }
public void Update(CPos cell, Sprite sprite, PaletteReference palette, float scale = 1f, float alpha = 1f, bool ignoreTint = false) public void Update(CPos cell, Sprite sprite, bool ignoreTint)
{ {
var xyz = float3.Zero; var xyz = float3.Zero;
if (sprite != null) if (sprite != null)
{ {
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset); var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size); xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
} }
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint); Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
} }
void UpdateTint(MPos uv) void UpdateTint(MPos uv)
@@ -100,10 +102,11 @@ namespace OpenRA.Graphics
var offset = rowStride * uv.V + 6 * uv.U; var offset = rowStride * uv.V + 6 * uv.U;
if (ignoreTint[offset]) if (ignoreTint[offset])
{ {
var noTint = float3.Ones;
for (var i = 0; i < 6; i++) for (var i = 0; i < 6; i++)
{ {
var v = vertices[offset + i]; var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * float3.Ones, v.A); vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
} }
return; return;
@@ -128,61 +131,31 @@ namespace OpenRA.Graphics
for (var i = 0; i < 6; i++) for (var i = 0; i < 6; i++)
{ {
var v = vertices[offset + i]; var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A); vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
} }
dirtyRows.Add(uv.V); dirtyRows.Add(uv.V);
} }
int GetOrAddSheetIndex(Sheet sheet) public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
{ {
if (sheet == null)
return 0;
for (var i = 0; i < sheets.Length; i++)
{
if (sheets[i] == sheet)
return i;
if (sheets[i] == null)
{
sheets[i] = sheet;
return i;
}
}
throw new InvalidDataException("Sheet overflow");
}
public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 pos, float scale, float alpha, bool ignoreTint)
{
int2 samplers;
if (sprite != null) if (sprite != null)
{ {
if (sprite.Sheet != Sheet)
throw new InvalidDataException("Attempted to add sprite from a different sheet");
if (sprite.BlendMode != BlendMode) if (sprite.BlendMode != BlendMode)
throw new InvalidDataException("Attempted to add sprite with a different blend mode"); throw new InvalidDataException("Attempted to add sprite with a different blend mode");
samplers = new int2(GetOrAddSheetIndex(sprite.Sheet), GetOrAddSheetIndex((sprite as SpriteWithSecondaryData)?.SecondarySheet));
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
palette = null;
} }
else else
{
sprite = emptySprite; sprite = emptySprite;
samplers = int2.Zero;
}
// The vertex buffer does not have geometry for cells outside the map // The vertex buffer does not have geometry for cells outside the map
if (!map.Tiles.Contains(uv)) if (!map.Tiles.Contains(uv))
return; return;
var offset = rowStride * uv.V + 6 * uv.U; var offset = rowStride * uv.V + 6 * uv.U;
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha); Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
palettes[uv.V * map.MapSize.X + uv.U] = palette;
if (worldRenderer.TerrainLighting != null) if (worldRenderer.TerrainLighting != null)
{ {
@@ -215,7 +188,7 @@ namespace OpenRA.Graphics
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow), vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
PrimitiveType.TriangleList, sheets, BlendMode); PrimitiveType.TriangleList, Sheet, BlendMode);
Game.Renderer.Flush(); Game.Renderer.Flush();
} }

View File

@@ -0,0 +1,197 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA.Graphics
{
class TheaterTemplate
{
public readonly Sprite[] Sprites;
public readonly int Stride;
public readonly int Variants;
public TheaterTemplate(Sprite[] sprites, int stride, int variants)
{
Sprites = sprites;
Stride = stride;
Variants = variants;
}
}
public sealed class Theater : IDisposable
{
readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>();
SheetBuilder sheetBuilder;
readonly Sprite missingTile;
readonly MersenneTwister random;
TileSet tileset;
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
{
this.tileset = tileset;
var allocated = false;
Func<Sheet> allocate = () =>
{
if (allocated)
throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter.");
allocated = true;
return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize));
};
random = new MersenneTwister();
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders);
foreach (var t in tileset.Templates)
{
var variants = new List<Sprite[]>();
foreach (var i in t.Value.Images)
{
ISpriteFrame[] allFrames;
if (onMissingImage != null)
{
try
{
allFrames = frameCache[i];
}
catch (FileNotFoundException)
{
onMissingImage(t.Key, i);
continue;
}
}
else
allFrames = frameCache[i];
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j);
var start = indices.Min();
var end = indices.Max();
if (start < 0 || end >= frameCount)
throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist"
.F(t.Key, start, end, i, frameCount - 1));
variants.Add(indices.Select(j =>
{
var f = allFrames[j];
var tile = t.Value.Contains(j) ? t.Value[j] : null;
// The internal z axis is inverted from expectation (negative is closer)
var zOffset = tile != null ? -tile.ZOffset : 0;
var zRamp = tile != null ? tile.ZRamp : 1f;
var offset = new float3(f.Offset, zOffset);
var type = SheetBuilder.FrameTypeToSheetType(f.Type);
// Defer SheetBuilder creation until we know what type of frames we are loading!
// TODO: Support mixed indexed and BGRA frames
if (sheetBuilder == null)
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
else if (type != sheetBuilder.Type)
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(s, f.Data);
if (tileset.EnableDepth)
{
var ss = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data);
// s and ss are guaranteed to use the same sheet
// because of the custom terrain sheet allocation
s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel);
}
return s;
}).ToArray());
}
var allSprites = variants.SelectMany(s => s);
// Ignore the offsets baked into R8 sprites
if (tileset.IgnoreTileSpriteOffsets)
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
if (onMissingImage != null && !variants.Any())
continue;
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
}
// 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1));
Sheet.ReleaseBuffer();
}
public bool HasTileSprite(TerrainTile r, int? variant = null)
{
return TileSprite(r, variant) != missingTile;
}
public Sprite TileSprite(TerrainTile r, int? variant = null)
{
if (!templates.TryGetValue(r.Type, out var template))
return missingTile;
if (r.Index >= template.Stride)
return missingTile;
var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0;
return template.Sprites[start * template.Stride + r.Index];
}
public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)
{
Rectangle? templateRect = null;
var i = 0;
for (var y = 0; y < template.Size.Y; y++)
{
for (var x = 0; x < template.Size.X; x++)
{
var tile = new TerrainTile(template.Id, (byte)(i++));
var tileInfo = tileset.GetTileInfo(tile);
// Empty tile
if (tileInfo == null)
continue;
var sprite = TileSprite(tile);
var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f;
var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f;
var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size;
var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y);
templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect;
}
}
return templateRect.HasValue ? templateRect.Value : Rectangle.Empty;
}
public Sheet Sheet { get { return sheetBuilder.Current; } }
public void Dispose()
{
sheetBuilder.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -13,7 +13,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
{ {
readonly Sprite sprite; readonly Sprite sprite;
readonly WPos effectiveWorldPos; readonly WPos effectiveWorldPos;
@@ -21,10 +21,8 @@ namespace OpenRA.Graphics
readonly int zOffset; readonly int zOffset;
readonly PaletteReference palette; readonly PaletteReference palette;
readonly float scale; readonly float scale;
readonly float alpha;
readonly float rotation = 0f;
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f) public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale)
{ {
this.sprite = sprite; this.sprite = sprite;
this.effectiveWorldPos = effectiveWorldPos; this.effectiveWorldPos = effectiveWorldPos;
@@ -32,48 +30,37 @@ namespace OpenRA.Graphics
this.zOffset = zOffset; this.zOffset = zOffset;
this.palette = palette; this.palette = palette;
this.scale = scale; this.scale = scale;
this.alpha = alpha;
this.rotation = rotation;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
this.palette = null;
} }
// Does not exist in the world, so a world positions don't make sense // Does not exist in the world, so a world positions don't make sense
public WPos Pos => effectiveWorldPos; public WPos Pos { get { return effectiveWorldPos; } }
public WVec Offset => WVec.Zero; public WVec Offset { get { return WVec.Zero; } }
public bool IsDecoration => true; public bool IsDecoration { get { return true; } }
public PaletteReference Palette => palette; public PaletteReference Palette { get { return palette; } }
public int ZOffset => zOffset; public int ZOffset { get { return zOffset; } }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale, alpha, rotation); } public IRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale); }
public IRenderable WithZOffset(int newOffset) { return this; } public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec) { return this; } public IRenderable OffsetBy(WVec vec) { return this; }
public IRenderable AsDecoration() { return this; } public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr) public void Render(WorldRenderer wr)
{ {
Game.Renderer.SpriteRenderer.DrawSprite(sprite, palette, screenPos, scale, float3.Ones, alpha, rotation); Game.Renderer.SpriteRenderer.DrawSprite(sprite, screenPos, palette, scale * sprite.Size);
} }
public void RenderDebugGeometry(WorldRenderer wr) public void RenderDebugGeometry(WorldRenderer wr)
{ {
var offset = screenPos + sprite.Offset.XY; var offset = screenPos + sprite.Offset.XY;
if (rotation == 0f) Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
else
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(offset, sprite.Size, rotation), 1, Color.Red);
} }
public Rectangle ScreenBounds(WorldRenderer wr) public Rectangle ScreenBounds(WorldRenderer wr)
{ {
var offset = screenPos + sprite.Offset; var offset = screenPos + sprite.Offset;
return Util.BoundingRectangle(offset, sprite.Size, rotation); return new Rectangle((int)offset.X, (int)offset.Y, (int)sprite.Size.X, (int)sprite.Size.Y);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -20,60 +20,29 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts. // yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 }; static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint)
in float3 size, in float3 tint, float alpha, float rotation = 0f)
{ {
float3 a, b, c, d; var b = new float3(o.X + size.X, o.Y, o.Z);
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
// Rotate sprite if rotation angle is not equal to 0 var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
if (rotation != 0f) FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
{
var center = o + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
a = center - ra;
b = center + rb;
c = center + ra;
d = center - rb;
}
else
{
a = o;
b = new float3(o.X + size.X, o.Y, o.Z);
c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
}
FastCreateQuad(vertices, a, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
} }
public static void FastCreateQuad(Vertex[] vertices, public static void FastCreateQuad(Vertex[] vertices,
in float3 a, in float3 b, in float3 c, in float3 d, in float3 a, in float3 b, in float3 c, in float3 d,
Sprite r, int2 samplers, float paletteTextureIndex, Sprite r, int2 samplers, float paletteTextureIndex,
in float3 tint, float alpha, int nv) in float3 tint, int nv)
{ {
float sl = 0; float sl = 0;
float st = 0; float st = 0;
float sr = 0; float sr = 0;
float sb = 0; float sb = 0;
// See combined.vert for documentation on the channel attribute format // See shp.vert for documentation on the channel attribute format
var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01; var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01;
attribC |= samplers.X << 6; attribC |= samplers.X << 6;
if (r is SpriteWithSecondaryData ss) var ss = r as SpriteWithSecondaryData;
if (ss != null)
{ {
sl = ss.SecondaryLeft; sl = ss.SecondaryLeft;
st = ss.SecondaryTop; st = ss.SecondaryTop;
@@ -85,15 +54,15 @@ namespace OpenRA.Graphics
} }
var fAttribC = (float)attribC; var fAttribC = (float)attribC;
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha); vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
} }
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType) public static void FastCopyIntoChannel(Sprite dest, byte[] src)
{ {
var destData = dest.Sheet.GetData(); var destData = dest.Sheet.GetData();
var width = dest.Bounds.Width; var width = dest.Bounds.Width;
@@ -116,34 +85,12 @@ namespace OpenRA.Graphics
{ {
for (var i = 0; i < width; i++) for (var i = 0; i < width; i++)
{ {
byte r, g, b, a; var r = src[k++];
switch (srcType) var g = src[k++];
{ var b = src[k++];
case SpriteFrameType.Bgra32: var a = src[k++];
case SpriteFrameType.Bgr24:
{
b = src[k++];
g = src[k++];
r = src[k++];
a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255;
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
r = src[k++];
g = src[k++];
b = src[k++];
a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255;
break;
}
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
}
var cc = Color.FromArgb(a, r, g, b); var cc = Color.FromArgb(a, r, g, b);
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
} }
} }
@@ -192,29 +139,16 @@ namespace OpenRA.Graphics
for (var i = 0; i < width; i++) for (var i = 0; i < width; i++)
{ {
Color cc; Color cc;
switch (src.Type) if (src.Palette == null)
{ {
case SpriteFrameType.Indexed8: var r = src.Data[k++];
{ var g = src.Data[k++];
cc = src.Palette[src.Data[k++]]; var b = src.Data[k++];
break; var a = src.Data[k++];
} cc = Color.FromArgb(a, r, g, b);
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
var r = src.Data[k++];
var g = src.Data[k++];
var b = src.Data[k++];
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
cc = Color.FromArgb(a, r, g, b);
break;
}
// Pngs don't support BGR[A], so no need to include them here
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
} }
else
cc = src.Palette[src.Data[k++]];
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
} }
@@ -223,69 +157,6 @@ namespace OpenRA.Graphics
} }
} }
/// <summary>Rotates a quad about its center in the x-y plane.</summary>
/// <param name="tl">The top left vertex of the quad</param>
/// <param name="size">A float3 containing the X, Y, and Z lengths of the quad</param>
/// <param name="rotation">The number of radians to rotate by</param>
/// <returns>An array of four vertices representing the rotated quad (top-left, top-right, bottom-right, bottom-left)</returns>
public static float3[] RotateQuad(float3 tl, float3 size, float rotation)
{
var center = tl + 0.5f * size;
var angleSin = (float)Math.Sin(-rotation);
var angleCos = (float)Math.Cos(-rotation);
// Rotated offset for +/- x with +/- y
var ra = 0.5f * new float3(
size.X * angleCos - size.Y * angleSin,
size.X * angleSin + size.Y * angleCos,
(size.X * angleSin + size.Y * angleCos) * size.Z / size.Y);
// Rotated offset for +/- x with -/+ y
var rb = 0.5f * new float3(
size.X * angleCos + size.Y * angleSin,
size.X * angleSin - size.Y * angleCos,
(size.X * angleSin - size.Y * angleCos) * size.Z / size.Y);
return new float3[]
{
center - ra,
center + rb,
center + ra,
center - rb
};
}
/// <summary>
/// Returns the bounds of an object. Used for determining which objects need to be rendered on screen, and which do not.
/// </summary>
/// <param name="offset">The top left vertex of the object</param>
/// <param name="size">A float 3 containing the X, Y, and Z lengths of the object</param>
/// <param name="rotation">The angle to rotate the object by (use 0f if there is no rotation)</param>
public static Rectangle BoundingRectangle(float3 offset, float3 size, float rotation)
{
if (rotation == 0f)
return new Rectangle((int)offset.X, (int)offset.Y, (int)size.X, (int)size.Y);
var rotatedQuad = Util.RotateQuad(offset, size, rotation);
var minX = rotatedQuad[0].X;
var maxX = rotatedQuad[0].X;
var minY = rotatedQuad[0].Y;
var maxY = rotatedQuad[0].Y;
for (var i = 1; i < rotatedQuad.Length; i++)
{
minX = Math.Min(rotatedQuad[i].X, minX);
maxX = Math.Max(rotatedQuad[i].X, maxX);
minY = Math.Min(rotatedQuad[i].Y, minY);
maxY = Math.Max(rotatedQuad[i].Y, maxY);
}
return new Rectangle(
(int)minX,
(int)minY,
(int)Math.Ceiling(maxX) - (int)minX,
(int)Math.Ceiling(maxY) - (int)minY);
}
public static Color PremultiplyAlpha(Color c) public static Color PremultiplyAlpha(Color c)
{ {
if (c.A == byte.MaxValue) if (c.A == byte.MaxValue)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -14,7 +14,7 @@ using System.Runtime.InteropServices;
namespace OpenRA.Graphics namespace OpenRA.Graphics
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public readonly struct Vertex public struct Vertex
{ {
// 3d position // 3d position
public readonly float X, Y, Z; public readonly float X, Y, Z;
@@ -26,24 +26,24 @@ namespace OpenRA.Graphics
public readonly float P, C; public readonly float P, C;
// Color tint // Color tint
public readonly float R, G, B, A; public readonly float R, G, B;
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c) public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { } : this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a) public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { } : this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a) public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { } : this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b, float a) public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b)
{ {
X = x; Y = y; Z = z; X = x; Y = y; Z = z;
S = s; T = t; S = s; T = t;
U = u; V = v; U = u; V = v;
P = p; C = c; P = p; C = c;
R = r; G = g; B = b; A = a; R = r; G = g; B = b;
} }
} }
} }

View File

@@ -1,36 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
namespace OpenRA.Video
{
public interface IVideo
{
ushort FrameCount { get; }
byte Framerate { get; }
ushort Width { get; }
ushort Height { get; }
/// <summary>
/// Current frame color data in 32-bit BGRA.
/// </summary>
byte[] CurrentFrameData { get; }
int CurrentFrameIndex { get; }
void AdvanceFrame();
bool HasAudio { get; }
byte[] AudioData { get; }
int AudioChannels { get; }
int SampleBits { get; }
int SampleRate { get; }
void Reset();
}
}

View File

@@ -1,32 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.IO;
namespace OpenRA.Video
{
public interface IVideoLoader
{
bool TryParseVideo(Stream s, bool useFramePadding, out IVideo video);
}
public static class VideoLoader
{
public static IVideo GetVideo(Stream stream, bool useFramePadding, IVideoLoader[] loaders)
{
foreach (var loader in loaders)
if (loader.TryParseVideo(stream, useFramePadding, out var video))
return video;
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -51,11 +51,11 @@ namespace OpenRA.Graphics
// Viewport geometry (world-px) // Viewport geometry (world-px)
public int2 CenterLocation { get; private set; } public int2 CenterLocation { get; private set; }
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation); public WPos CenterPosition { get { return worldRenderer.ProjectedPosition(CenterLocation); } }
public Rectangle Rectangle => new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y)); public Rectangle Rectangle { get { return new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y)); } }
public int2 TopLeft => CenterLocation - viewportSize / 2; public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
public int2 BottomRight => CenterLocation + viewportSize / 2; public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
int2 viewportSize; int2 viewportSize;
ProjectedCellRegion cells; ProjectedCellRegion cells;
bool cellsDirty = true; bool cellsDirty = true;
@@ -75,7 +75,10 @@ namespace OpenRA.Graphics
public float Zoom public float Zoom
{ {
get => zoom; get
{
return zoom;
}
private set private set
{ {
@@ -86,7 +89,7 @@ namespace OpenRA.Graphics
} }
} }
public float MinZoom => minZoom; public float MinZoom { get { return minZoom; } }
public void AdjustZoom(float dz) public void AdjustZoom(float dz)
{ {
@@ -238,7 +241,7 @@ namespace OpenRA.Graphics
if (unlockMinZoom) if (unlockMinZoom)
{ {
// Spectators and the map editor support zooming out by an extra factor of two. // Specators and the map editor support zooming out by an extra factor of two.
// TODO: Allow zooming out until the full map is visible // TODO: Allow zooming out until the full map is visible
// We need to improve our viewport scroll handling to center the map as we zoom out // We need to improve our viewport scroll handling to center the map as we zoom out
// before this will work well enough to enable // before this will work well enough to enable
@@ -250,9 +253,6 @@ namespace OpenRA.Graphics
else else
Zoom = Zoom.Clamp(minZoom, maxZoom); Zoom = Zoom.Clamp(minZoom, maxZoom);
var maxSize = (1f / (unlockMinZoom ? unlockedMinZoom : minZoom) * new float2(Game.Renderer.NativeResolution));
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>()) foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(minZoom, maxZoom); t.ViewportZoomExtentsChanged(minZoom, maxZoom);
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -21,12 +21,13 @@ namespace OpenRA.Graphics
public sealed class WorldRenderer : IDisposable public sealed class WorldRenderer : IDisposable
{ {
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey = public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
r => r.Pos.Y + r.Pos.Z + r.ZOffset; r => ZPosition(r.Pos, r.ZOffset);
public readonly Size TileSize; public readonly Size TileSize;
public readonly int TileScale; public readonly int TileScale;
public readonly World World; public readonly World World;
public Viewport Viewport { get; } public readonly Theater Theater;
public Viewport Viewport { get; private set; }
public readonly ITerrainLighting TerrainLighting; public readonly ITerrainLighting TerrainLighting;
public event Action PaletteInvalidated = null; public event Action PaletteInvalidated = null;
@@ -45,6 +46,8 @@ namespace OpenRA.Graphics
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>(); readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
bool lastDepthPreviewEnabled;
internal WorldRenderer(ModData modData, World world) internal WorldRenderer(ModData modData, World world)
{ {
World = world; World = world;
@@ -65,6 +68,7 @@ namespace OpenRA.Graphics
palette.Initialize(); palette.Initialize();
Theater = new Theater(world.Map.Rules.TileSet);
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>(); TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>(); terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
@@ -83,13 +87,7 @@ namespace OpenRA.Graphics
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette); return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
} }
public PaletteReference Palette(string name) public PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
{
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed.
return name == null ? null : palettes.GetOrAdd(name, createPaletteReference);
}
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false) public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
{ {
if (allowOverwrite && palette.Contains(name)) if (allowOverwrite && palette.Contains(name))
@@ -113,11 +111,6 @@ namespace OpenRA.Graphics
palettes[name].Palette = pal; palettes[name].Palette = pal;
} }
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
}
// PERF: Avoid LINQ. // PERF: Avoid LINQ.
void GenerateRenderables() void GenerateRenderables()
{ {
@@ -151,14 +144,14 @@ namespace OpenRA.Graphics
// PERF: Avoid LINQ. // PERF: Avoid LINQ.
void GenerateOverlayRenderables() void GenerateOverlayRenderables()
{ {
World.ApplyToActorsWithTrait<IRenderAboveShroud>((actor, trait) => foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
{ {
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor))) if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
return; continue;
foreach (var renderable in trait.RenderAboveShroud(actor, this)) foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
preparedOverlayRenderables.Add(renderable.PrepareRender(this)); preparedOverlayRenderables.Add(renderable.PrepareRender(this));
}); }
foreach (var a in World.Selection.Actors) foreach (var a in World.Selection.Actors)
{ {
@@ -177,7 +170,8 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects) foreach (var e in World.Effects)
{ {
if (!(e is IEffectAboveShroud ea)) var ea = e as IEffectAboveShroud;
if (ea == null)
continue; continue;
foreach (var renderable in ea.RenderAboveShroud(this)) foreach (var renderable in ea.RenderAboveShroud(this))
@@ -192,14 +186,14 @@ namespace OpenRA.Graphics
// PERF: Avoid LINQ. // PERF: Avoid LINQ.
void GenerateAnnotationRenderables() void GenerateAnnotationRenderables()
{ {
World.ApplyToActorsWithTrait<IRenderAnnotations>((actor, trait) => foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
{ {
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor))) if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
return; continue;
foreach (var renderAnnotation in trait.RenderAnnotations(actor, this)) foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this)); preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
}); }
foreach (var a in World.Selection.Actors) foreach (var a in World.Selection.Actors)
{ {
@@ -218,7 +212,8 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects) foreach (var e in World.Effects)
{ {
if (!(e is IEffectAnnotation ea)) var ea = e as IEffectAnnotation;
if (ea == null)
continue; continue;
foreach (var renderAnnotation in ea.RenderAnnotation(this)) foreach (var renderAnnotation in ea.RenderAnnotation(this))
@@ -252,7 +247,11 @@ namespace OpenRA.Graphics
if (World.WorldActor.Disposed) if (World.WorldActor.Disposed)
return; return;
debugVis.Value?.UpdateDepthBuffer(); if (debugVis.Value != null && lastDepthPreviewEnabled != debugVis.Value.DepthBuffer)
{
lastDepthPreviewEnabled = debugVis.Value.DepthBuffer;
Game.Renderer.WorldSpriteRenderer.SetDepthPreviewEnabled(lastDepthPreviewEnabled);
}
var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor); var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
Game.Renderer.EnableScissor(bounds); Game.Renderer.EnableScissor(bounds);
@@ -270,16 +269,15 @@ namespace OpenRA.Graphics
if (enableDepthBuffer) if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer(); Game.Renderer.ClearDepthBuffer();
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) => foreach (var a in World.ActorsWithTrait<IRenderAboveWorld>())
{ if (a.Actor.IsInWorld && !a.Actor.Disposed)
if (actor.IsInWorld && !actor.Disposed) a.Trait.RenderAboveWorld(a.Actor, this);
trait.RenderAboveWorld(actor, this);
});
if (enableDepthBuffer) if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer(); Game.Renderer.ClearDepthBuffer();
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this)); foreach (var a in World.ActorsWithTrait<IRenderShroud>())
a.Trait.RenderShroud(this);
if (enableDepthBuffer) if (enableDepthBuffer)
Game.Renderer.Context.DisableDepthBuffer(); Game.Renderer.Context.DisableDepthBuffer();
@@ -358,13 +356,7 @@ namespace OpenRA.Graphics
public float3 Screen3DPosition(WPos pos) public float3 Screen3DPosition(WPos pos)
{ {
// The projection from world coordinates to screen coordinates has var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
// a non-obvious relationship between the y and z coordinates:
// * A flat surface with constant y (e.g. a vertical wall) in world coordinates
// transforms into a flat surface with constant z (depth) in screen coordinates.
// * Increasing the world y coordinate increases screen y and z coordinates equally.
// * Increases the world z coordinate decreases screen y but doesn't change screen z.
var z = pos.Y * (float)TileSize.Height / TileScale;
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z); return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
} }
@@ -383,7 +375,7 @@ namespace OpenRA.Graphics
} }
// For scaling vectors to pixel sizes in the model renderer // For scaling vectors to pixel sizes in the model renderer
public float3 ScreenVectorComponents(in WVec vec) public float3 ScreenVectorComponents(WVec vec)
{ {
return new float3( return new float3(
(float)TileSize.Width * vec.X / TileScale, (float)TileSize.Width * vec.X / TileScale,
@@ -392,19 +384,29 @@ namespace OpenRA.Graphics
} }
// For scaling vectors to pixel sizes in the model renderer // For scaling vectors to pixel sizes in the model renderer
public float[] ScreenVector(in WVec vec) public float[] ScreenVector(WVec vec)
{ {
var xyz = ScreenVectorComponents(vec); var xyz = ScreenVectorComponents(vec);
return new[] { xyz.X, xyz.Y, xyz.Z, 1f }; return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
} }
public int2 ScreenPxOffset(in WVec vec) public int2 ScreenPxOffset(WVec vec)
{ {
// Round to nearest pixel // Round to nearest pixel
var xyz = ScreenVectorComponents(vec); var xyz = ScreenVectorComponents(vec);
return new int2((int)Math.Round(xyz.X), (int)Math.Round(xyz.Y)); return new int2((int)Math.Round(xyz.X), (int)Math.Round(xyz.Y));
} }
public float ScreenZPosition(WPos pos, int offset)
{
return ZPosition(pos, offset) * (float)TileSize.Height / TileScale;
}
static int ZPosition(WPos pos, int offset)
{
return pos.Y + pos.Z + offset;
}
/// <summary> /// <summary>
/// Returns a position in the world that is projected to the given screen position. /// Returns a position in the world that is projected to the given screen position.
/// There are many possible world positions, and the returned value chooses the value with no elevation. /// There are many possible world positions, and the returned value chooses the value with no elevation.
@@ -423,6 +425,7 @@ namespace OpenRA.Graphics
World.Dispose(); World.Dispose();
palette.Dispose(); palette.Dispose();
Theater.Dispose();
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -20,8 +20,6 @@ namespace OpenRA
public readonly Hotkey Default = Hotkey.Invalid; public readonly Hotkey Default = Hotkey.Invalid;
public readonly string Description = ""; public readonly string Description = "";
public readonly HashSet<string> Types = new HashSet<string>(); public readonly HashSet<string> Types = new HashSet<string>();
public readonly HashSet<string> Contexts = new HashSet<string>();
public readonly bool Readonly = false;
public bool HasDuplicates { get; internal set; } public bool HasDuplicates { get; internal set; }
public HotkeyDefinition(string name, MiniYaml node) public HotkeyDefinition(string name, MiniYaml node)
@@ -38,22 +36,6 @@ namespace OpenRA
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types"); var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
if (typesNode != null) if (typesNode != null)
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value); Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
if (contextsNode != null)
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
if (platformNode != null)
{
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
if (platformOverride != null)
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
}
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
if (readonlyNode != null)
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -35,12 +35,12 @@ namespace OpenRA
foreach (var kv in settings) foreach (var kv in settings)
{ {
if (definitions.ContainsKey(kv.Key) && !definitions[kv.Key].Readonly) if (definitions.ContainsKey(kv.Key))
keys[kv.Key] = kv.Value; keys[kv.Key] = kv.Value;
} }
foreach (var hd in definitions) foreach (var hd in definitions)
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null; hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
} }
internal Func<Hotkey> GetHotkeyReference(string name) internal Func<Hotkey> GetHotkeyReference(string name)
@@ -61,9 +61,6 @@ namespace OpenRA
if (!definitions.TryGetValue(name, out var definition)) if (!definitions.TryGetValue(name, out var definition))
return; return;
if (definition.Readonly)
return;
keys[name] = value; keys[name] = value;
if (value != definition.Default) if (value != definition.Default)
settings[name] = value; settings[name] = value;
@@ -71,7 +68,7 @@ namespace OpenRA
settings.Remove(name); settings.Remove(name);
var hadDuplicates = definition.HasDuplicates; var hadDuplicates = definition.HasDuplicates;
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null; definition.HasDuplicates = GetFirstDuplicate(definition.Name, this[definition.Name].GetValue(), definition) != null;
if (hadDuplicates || definition.HasDuplicates) if (hadDuplicates || definition.HasDuplicates)
{ {
@@ -80,30 +77,33 @@ namespace OpenRA
if (hd.Value == definition) if (hd.Value == definition)
continue; continue;
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null; hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
} }
} }
} }
public HotkeyDefinition GetFirstDuplicate(HotkeyDefinition definition, Hotkey value) public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)
{ {
if (definition == null)
return null;
foreach (var kv in keys) foreach (var kv in keys)
{ {
if (kv.Key == definition.Name) if (kv.Key == name)
continue; continue;
if (kv.Value == value && definitions[kv.Key].Contexts.Overlaps(definition.Contexts)) if (kv.Value == value && definitions[kv.Key].Types.Overlaps(definition.Types))
return definitions[kv.Key]; return definitions[kv.Key];
} }
return null; return null;
} }
public HotkeyReference this[string name] => new HotkeyReference(GetHotkeyReference(name)); public HotkeyReference this[string name]
{
get
{
return new HotkeyReference(GetHotkeyReference(name));
}
}
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values; public IEnumerable<HotkeyDefinition> Definitions { get { return definitions.Values; } }
} }
} }

View File

@@ -1,63 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace OpenRA
{
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
public static class HttpExtension
{
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
{
var total = response.Content.Headers.ContentLength ?? -1;
var canReportProgress = total > 0;
#if NET5_0_OR_GREATER
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
#else
using (var contentStream = await response.Content.ReadAsStreamAsync())
#endif
{
var totalRead = 0L;
var buffer = new byte[8192];
var hasMoreToRead = true;
do
{
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
if (read == 0)
hasMoreToRead = false;
else
{
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
totalRead += read;
if (canReportProgress)
{
var progressPercentage = (int)((double)totalRead / total * 100);
onProgress?.Invoke(total, totalRead, progressPercentage);
}
}
}
while (hasMoreToRead && !token.IsCancellationRequested);
onProgress?.Invoke(total, totalRead, 100);
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -13,7 +13,7 @@ using System;
namespace OpenRA namespace OpenRA
{ {
public readonly struct Hotkey : IEquatable<Hotkey> public struct Hotkey : IEquatable<Hotkey>
{ {
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None); public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
public bool IsValid() public bool IsValid()
@@ -81,10 +81,11 @@ namespace OpenRA
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is Hotkey o && (Hotkey?)o == this; var o = obj as Hotkey?;
return o != null && o == this;
} }
public override string ToString() { return $"{Key} {Modifiers.ToString("F")}"; } public override string ToString() { return "{0} {1}".F(Key, Modifiers.ToString("F")); }
public string DisplayString() public string DisplayString()
{ {

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -37,24 +37,36 @@ namespace OpenRA
public void OnKeyInput(KeyInput input) public void OnKeyInput(KeyInput input)
{ {
Sync.RunUnsynced(world, () => Ui.HandleKeyPress(input)); Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleKeyPress(input));
} }
public void OnTextInput(string text) public void OnTextInput(string text)
{ {
Sync.RunUnsynced(world, () => Ui.HandleTextInput(text)); Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
} }
public void OnMouseInput(MouseInput input) public void OnMouseInput(MouseInput input)
{ {
Sync.RunUnsynced(world, () => Ui.HandleInput(input)); Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleInput(input));
} }
} }
public class MouseButtonPreference public class MouseButtonPreference
{ {
public MouseButton Action => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right; public MouseButton Action
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
}
}
public MouseButton Cancel => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left; public MouseButton Cancel
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
}
}
} }
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -15,18 +15,22 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA namespace OpenRA
{ {
public class InstalledMods : IReadOnlyDictionary<string, Manifest> public class InstalledMods : IReadOnlyDictionary<string, Manifest>
{ {
readonly Dictionary<string, Manifest> mods; readonly Dictionary<string, Manifest> mods;
readonly SheetBuilder sheetBuilder;
/// <summary>Initializes the collection of locally installed mods.</summary> /// <summary>Initializes the collection of locally installed mods.</summary>
/// <param name="searchPaths">Filesystem paths to search for mod packages.</param> /// <param name="searchPaths">Filesystem paths to search for mod packages.</param>
/// <param name="explicitPaths">Filesystem paths to additional mod packages.</param> /// <param name="explicitPaths">Filesystem paths to additional mod packages.</param>
public InstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths) public InstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{ {
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
mods = GetInstalledMods(searchPaths, explicitPaths); mods = GetInstalledMods(searchPaths, explicitPaths);
} }
@@ -71,7 +75,7 @@ namespace OpenRA
} }
catch (Exception e) catch (Exception e)
{ {
Log.Write("debug", $"Load mod '{path}': {e}"); Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
} }
package?.Dispose(); package?.Dispose();
@@ -95,10 +99,10 @@ namespace OpenRA
return ret; return ret;
} }
public Manifest this[string key] => mods[key]; public Manifest this[string key] { get { return mods[key]; } }
public IEnumerable<string> Keys => mods.Keys; public int Count { get { return mods.Count; } }
public IEnumerable<Manifest> Values => mods.Values; public ICollection<string> Keys { get { return mods.Keys; } }
public int Count => mods.Count; public ICollection<Manifest> Values { get { return mods.Values; } }
public bool ContainsKey(string key) { return mods.ContainsKey(key); } public bool ContainsKey(string key) { return mods.ContainsKey(key); }
public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); } public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); }
public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); } public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -12,10 +12,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenRA.Support;
namespace OpenRA namespace OpenRA
{ {
@@ -24,11 +24,11 @@ namespace OpenRA
const int AuthKeySize = 2048; const int AuthKeySize = 2048;
public enum LinkState { Uninitialized, GeneratingKeys, Unlinked, CheckingLink, ConnectionFailed, Linked } public enum LinkState { Uninitialized, GeneratingKeys, Unlinked, CheckingLink, ConnectionFailed, Linked }
public LinkState State => innerState; public LinkState State { get { return innerState; } }
public string Fingerprint => innerFingerprint; public string Fingerprint { get { return innerFingerprint; } }
public string PublicKey => innerPublicKey; public string PublicKey { get { return innerPublicKey; } }
public PlayerProfile ProfileData => innerData; public PlayerProfile ProfileData { get { return innerData; } }
volatile LinkState innerState; volatile LinkState innerState;
volatile PlayerProfile innerData; volatile PlayerProfile innerData;
@@ -76,16 +76,17 @@ namespace OpenRA
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed) if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
return; return;
Task.Run(async () => Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
{ {
try try
{ {
var client = HttpClientFactory.Create(); if (i.Error != null)
{
innerState = LinkState.ConnectionFailed;
return;
}
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint); var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
var yaml = MiniYaml.FromStream(result).First();
if (yaml.Key == "Player") if (yaml.Key == "Player")
{ {
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value); innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
@@ -109,9 +110,10 @@ namespace OpenRA
{ {
onComplete?.Invoke(); onComplete?.Invoke();
} }
}); };
innerState = LinkState.CheckingLink; innerState = LinkState.CheckingLink;
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
} }
public void GenerateKeypair() public void GenerateKeypair()
@@ -157,7 +159,7 @@ namespace OpenRA
} }
innerState = LinkState.Uninitialized; innerState = LinkState.Uninitialized;
parameters = default; parameters = default(RSAParameters);
innerFingerprint = null; innerFingerprint = null;
innerData = null; innerData = null;
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA namespace OpenRA
{ {
public readonly struct MPos : IEquatable<MPos> public struct MPos : IEquatable<MPos>
{ {
public readonly int U, V; public readonly int U, V;
@@ -66,7 +66,7 @@ namespace OpenRA
/// <summary> /// <summary>
/// Projected map position /// Projected map position
/// </summary> /// </summary>
public readonly struct PPos : IEquatable<PPos> public struct PPos : IEquatable<PPos>
{ {
public readonly int U, V; public readonly int U, V;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -11,7 +11,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using OpenRA.FileSystem; using OpenRA.FileSystem;
@@ -21,17 +20,6 @@ namespace OpenRA
{ {
public interface IGlobalModData { } public interface IGlobalModData { }
public sealed class TerrainFormat : IGlobalModData
{
public readonly string Type;
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
public TerrainFormat(MiniYaml yaml)
{
Type = yaml.Value;
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
}
}
public sealed class SpriteSequenceFormat : IGlobalModData public sealed class SpriteSequenceFormat : IGlobalModData
{ {
public readonly string Type; public readonly string Type;
@@ -60,7 +48,6 @@ namespace OpenRA
public string Version; public string Version;
public string Website; public string Website;
public string WebIcon32; public string WebIcon32;
public string WindowTitle;
public bool Hidden; public bool Hidden;
} }
@@ -79,19 +66,17 @@ namespace OpenRA
public readonly IReadOnlyDictionary<string, string> Packages; public readonly IReadOnlyDictionary<string, string> Packages;
public readonly IReadOnlyDictionary<string, string> MapFolders; public readonly IReadOnlyDictionary<string, string> MapFolders;
public readonly MiniYaml LoadScreen; public readonly MiniYaml LoadScreen;
public readonly string DefaultOrderGenerator;
public readonly string[] SoundFormats = Array.Empty<string>(); public readonly string[] SoundFormats = { };
public readonly string[] SpriteFormats = Array.Empty<string>(); public readonly string[] SpriteFormats = { };
public readonly string[] PackageFormats = Array.Empty<string>(); public readonly string[] PackageFormats = { };
public readonly string[] VideoFormats = Array.Empty<string>();
readonly string[] reservedModuleNames = readonly string[] reservedModuleNames =
{ {
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules", "Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons", "Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys", "Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats", "ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
"RequiresMods", "PackageFormats" "RequiresMods", "PackageFormats"
}; };
@@ -115,7 +100,7 @@ namespace OpenRA
var filename = nodes[i].Value.Value; var filename = nodes[i].Value.Value;
var contents = package.GetStream(filename); var contents = package.GetStream(filename);
if (contents == null) if (contents == null)
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found."); throw new YamlException("{0}: File `{1}` not found.".F(nodes[i].Location, filename));
nodes.RemoveAt(i); nodes.RemoveAt(i);
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename)); nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
@@ -130,7 +115,7 @@ namespace OpenRA
MapFolders = YamlDictionary(yaml, "MapFolders"); MapFolders = YamlDictionary(yaml, "MapFolders");
if (yaml.TryGetValue("Packages", out var packages)) if (yaml.TryGetValue("Packages", out var packages))
Packages = packages.ToDictionary(x => x.Value); Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
Rules = YamlList(yaml, "Rules"); Rules = YamlList(yaml, "Rules");
Sequences = YamlList(yaml, "Sequences"); Sequences = YamlList(yaml, "Sequences");
@@ -162,9 +147,6 @@ namespace OpenRA
MapCompatibility = compat.ToArray(); MapCompatibility = compat.ToArray();
if (yaml.ContainsKey("DefaultOrderGenerator"))
DefaultOrderGenerator = yaml["DefaultOrderGenerator"].Value;
if (yaml.ContainsKey("PackageFormats")) if (yaml.ContainsKey("PackageFormats"))
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value); PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
@@ -173,9 +155,6 @@ namespace OpenRA
if (yaml.ContainsKey("SpriteFormats")) if (yaml.ContainsKey("SpriteFormats"))
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value); SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
if (yaml.ContainsKey("VideoFormats"))
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", yaml["VideoFormats"].Value);
} }
public void LoadCustomData(ObjectCreator oc) public void LoadCustomData(ObjectCreator oc)
@@ -187,7 +166,7 @@ namespace OpenRA
var t = oc.FindType(kv.Key); var t = oc.FindType(kv.Key);
if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t)) if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t))
throw new InvalidDataException($"`{kv.Key}` is not a valid mod manifest entry."); throw new InvalidDataException("`{0}` is not a valid mod manifest entry.".F(kv.Key));
IGlobalModData module; IGlobalModData module;
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) }); var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
@@ -209,10 +188,10 @@ namespace OpenRA
customDataLoaded = true; customDataLoaded = true;
} }
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key) static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key, bool parsePaths = false)
{ {
if (!yaml.ContainsKey(key)) if (!yaml.ContainsKey(key))
return Array.Empty<string>(); return new string[] { };
return yaml[key].ToDictionary().Keys.ToArray(); return yaml[key].ToDictionary().Keys.ToArray();
} }
@@ -220,9 +199,10 @@ namespace OpenRA
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key) static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
{ {
if (!yaml.ContainsKey(key)) if (!yaml.ContainsKey(key))
return new Dictionary<string, string>(); return new ReadOnlyDictionary<string, string>();
return yaml[key].ToDictionary(my => my.Value); var inner = yaml[key].ToDictionary(my => my.Value);
return new ReadOnlyDictionary<string, string>(inner);
} }
public bool Contains<T>() where T : IGlobalModData public bool Contains<T>() where T : IGlobalModData

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -37,7 +37,7 @@ namespace OpenRA
public class ActorInitializer : IActorInitializer public class ActorInitializer : IActorInitializer
{ {
public readonly Actor Self; public readonly Actor Self;
public World World => Self.World; public World World { get { return Self.World; } }
internal TypeDictionary Dict; internal TypeDictionary Dict;
@@ -66,7 +66,7 @@ namespace OpenRA
{ {
var init = GetOrDefault<T>(info); var init = GetOrDefault<T>(info);
if (init == null) if (init == null)
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`"); throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
return init; return init;
} }
@@ -140,7 +140,7 @@ namespace OpenRA
public abstract class ValueActorInit<T> : ActorInit public abstract class ValueActorInit<T> : ActorInit
{ {
readonly T value; protected readonly T value;
protected ValueActorInit(TraitInfo info, T value) protected ValueActorInit(TraitInfo info, T value)
: base(info.InstanceName) { this.value = value; } : base(info.InstanceName) { this.value = value; }
@@ -150,16 +150,16 @@ namespace OpenRA
protected ValueActorInit(T value) { this.value = value; } protected ValueActorInit(T value) { this.value = value; }
public virtual T Value => value; public virtual T Value { get { return value; } }
public virtual void Initialize(MiniYaml yaml) public virtual void Initialize(MiniYaml yaml)
{ {
Initialize((T)FieldLoader.GetValue(nameof(value), typeof(T), yaml.Value)); Initialize((T)FieldLoader.GetValue("value", typeof(T), yaml.Value));
} }
public virtual void Initialize(T value) public virtual void Initialize(T value)
{ {
var field = typeof(ValueActorInit<T>).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance); var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) if (field != null)
field.SetValue(this, value); field.SetValue(this, value);
} }
@@ -226,7 +226,7 @@ namespace OpenRA
public class OwnerInit : ActorInit, ISingleInstanceInit public class OwnerInit : ActorInit, ISingleInstanceInit
{ {
public readonly string InternalName; public readonly string InternalName;
readonly Player value; protected readonly Player value;
public OwnerInit(Player value) public OwnerInit(Player value)
{ {
@@ -246,14 +246,14 @@ namespace OpenRA
public void Initialize(MiniYaml yaml) public void Initialize(MiniYaml yaml)
{ {
var field = typeof(OwnerInit).GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance); var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
if (field != null) if (field != null)
field.SetValue(this, yaml.Value); field.SetValue(this, yaml.Value);
} }
public void Initialize(Player player) public void Initialize(Player player)
{ {
var field = typeof(OwnerInit).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance); var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) if (field != null)
field.SetValue(this, player); field.SetValue(this, player);
} }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS) * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of * as published by the Free Software Foundation, either version 3 of
@@ -25,9 +25,9 @@ namespace OpenRA
public class ActorReference : IEnumerable public class ActorReference : IEnumerable
{ {
public string Type; public string Type;
readonly Lazy<TypeDictionary> initDict; Lazy<TypeDictionary> initDict;
internal TypeDictionary InitDict => initDict.Value; internal TypeDictionary InitDict { get { return initDict.Value; } }
public ActorReference(string type) public ActorReference(string type)
: this(type, new Dictionary<string, MiniYaml>()) { } : this(type, new Dictionary<string, MiniYaml>()) { }
@@ -42,7 +42,7 @@ namespace OpenRA
{ {
var init = LoadInit(i.Key, i.Value); var init = LoadInit(i.Key, i.Value);
if (init is ISingleInstanceInit && dict.Contains(init.GetType())) if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'"); throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
dict.Add(init); dict.Add(init);
} }
@@ -68,15 +68,15 @@ namespace OpenRA
var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator); var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator);
var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init"); var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init");
if (type == null) if (type == null)
throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'"); throw new InvalidDataException("Unknown initializer type '{0}Init'".F(initInstance[0]));
var init = (ActorInit)FormatterServices.GetUninitializedObject(type); var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
if (initInstance.Length > 1) if (initInstance.Length > 1)
type.GetField(nameof(ActorInit.InstanceName)).SetValue(init, initInstance[1]); type.GetField("InstanceName").SetValue(init, initInstance[1]);
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) }); var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
if (loader == null) if (loader == null)
throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type."); throw new InvalidDataException("{0}Init does not define a yaml-assignable type.".F(initInstance[0]));
loader.Invoke(init, new[] { initYaml }); loader.Invoke(init, new[] { initYaml });
return init; return init;
@@ -87,7 +87,8 @@ namespace OpenRA
var ret = new MiniYaml(Type); var ret = new MiniYaml(Type);
foreach (var o in initDict.Value) foreach (var o in initDict.Value)
{ {
if (!(o is ActorInit init) || o is ISuppressInitExport) var init = o as ActorInit;
if (init == null || o is ISuppressInitExport)
continue; continue;
if (initFilter != null && !initFilter(init)) if (initFilter != null && !initFilter(init))
@@ -118,7 +119,7 @@ namespace OpenRA
public void Add(ActorInit init) public void Add(ActorInit init)
{ {
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType())) if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'"); throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
InitDict.Add(init); InitDict.Add(init);
} }
@@ -161,7 +162,7 @@ namespace OpenRA
{ {
var init = GetOrDefault<T>(info); var init = GetOrDefault<T>(info);
if (init == null) if (init == null)
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`"); throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
return init; return init;
} }

Some files were not shown because too many files have changed in this diff Show More