Compare commits
20 Commits
devtest-20
...
devtest-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b05a0a9f2 | ||
|
|
22dd61676c | ||
|
|
ca75797169 | ||
|
|
b2e5379537 | ||
|
|
24247cc2e5 | ||
|
|
75bb879f84 | ||
|
|
d2b42fc9a1 | ||
|
|
a789a906ac | ||
|
|
cf2c842049 | ||
|
|
0f105d62d4 | ||
|
|
85d138624e | ||
|
|
2cde1effcb | ||
|
|
4d29c39a6d | ||
|
|
003943514b | ||
|
|
ba983754ba | ||
|
|
0ff9f0dfa6 | ||
|
|
474c912439 | ||
|
|
40b2768183 | ||
|
|
cbb5d072e3 | ||
|
|
4541d2b8ff |
200
.editorconfig
200
.editorconfig
@@ -1,213 +1,17 @@
|
||||
; Top-most http://editorconfig.org/ file
|
||||
root = true
|
||||
charset=utf-8
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
; 4-column tab indentation and .NET coding conventions
|
||||
; 4-column tab indentation
|
||||
[*.cs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
csharp_prefer_braces = when_multiline:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
|
||||
## Naming styles:
|
||||
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.i_prefix_pascal_case.capitalization = pascal_case
|
||||
dotnet_naming_style.i_prefix_pascal_case.required_prefix = I
|
||||
|
||||
## Symbol specifications:
|
||||
|
||||
dotnet_naming_symbols.const_locals.applicable_kinds = local
|
||||
dotnet_naming_symbols.const_locals.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_locals.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.const_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.const_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.const_fields.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
|
||||
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_protected_fields.applicable_accessibilities = private, protected, private_protected
|
||||
|
||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
|
||||
|
||||
dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
|
||||
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_symbols.most_symbols.applicable_accessibilities = *
|
||||
|
||||
## Naming rules:
|
||||
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.symbols = const_locals
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_locals_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.symbols = const_fields
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.const_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static_readonly_fields
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = pascal_case
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_or_protected_fields_should_be_camel_case.symbols = private_or_protected_fields
|
||||
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
|
||||
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
|
||||
|
||||
# Also handled by StyleCopAnalyzers - SA1024: ColonsMustBeSpacedCorrectly.
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
|
||||
# Also handled by StyleCopAnalyzers - SA1000: KeywordsMustBeSpacedCorrectly.
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
|
||||
# Leave code block on single line.
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
# Leave statements and member declarations on the same line.
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias.
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# IDE0049, IDE-only counterpart of StyleCopAnalyzers - SA1121: UseBuiltInTypeAlias.
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
|
||||
## 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
|
||||
[*.yaml]
|
||||
indent_style = tab
|
||||
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
|
||||
indent_size = 4
|
||||
39
.github/ISSUE_TEMPLATE.md
vendored
Normal file
39
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<!--
|
||||
This is a guideline, which shall help to write enhancement requests or bug reports.
|
||||
Fill in the placeholders below. Delete any headings and placeholders that you do not use.
|
||||
|
||||
Before you start check if a similar request/bug report already exists in this Github issue tracker and comment there.
|
||||
|
||||
When submitting a feature or enhancement request:
|
||||
|
||||
1. Explain briefly what the enhancement is and why you think it would be useful.
|
||||
2. Provide any other necessary or useful information regarding your issue, such as (code) examples or related links.
|
||||
|
||||
|
||||
When submitting a bug report, please follow the template below:
|
||||
-->
|
||||
|
||||
### Issue Summary
|
||||
<!-- Explanation of the issue. Expectation vs. actual behavior. -->
|
||||
... ... ...
|
||||
|
||||
#### System Information
|
||||
- **Operating System:** [e.g. Windows 10, Mac OS 10.12, Ubuntu 16.04, ...]
|
||||
- **.NET / Mono Version:** [e.g. .NET 4.7.1, Mono 4.6.2, ...]
|
||||
- **OpenRA Version:** [e.g. release-20180218, playtest-20180208, ...]
|
||||
- **Mod:** [e.g. Red Alert, Tiberian Dawn, Dune2000, ...]
|
||||
|
||||
#### Additional Information:
|
||||
- Steps to reproduce
|
||||
1. Step
|
||||
2. Step
|
||||
3. ...
|
||||
|
||||
- Logs
|
||||
<!-- If you have a log (e.g. debug.log, exception.log), zip and attach it. -->
|
||||
|
||||
- OpenRA Replays
|
||||
<!-- You have to zip it before you can attach it. When does the issue appear [e.g. 10:33]? -->
|
||||
|
||||
- Screenshots & Videos
|
||||
<!-- You should be able to attach screenshots by drag&drop. Videos need to be uploaded to an external platform (e.g. https://www.youtube.com, https://www.dropbox.com) -->
|
||||
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
33
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report unexpected behavior or any issues you experienced in OpenRA.
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include information we need to understand and fix the issue you experienced. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing reports for your issue. Please do not submit duplicate reports! Try to help others to find your report by using a precise title. -->
|
||||
|
||||
## Issue Summary
|
||||
<!-- Please provide a a clear and concise description of what the issue is below. -->
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
|
||||
## Reproduction
|
||||
<!-- Please provide information about how the issue can be reproduced below. -->
|
||||
|
||||
STEPS TO REPRODUCE THE ISSUE
|
||||
|
||||
|
||||
## Expected behavior
|
||||
<!-- Please explain what you expected to happen below. -->
|
||||
|
||||
EXPECTED BEHAVIOR
|
||||
|
||||
|
||||
## Screenshots / Screen recordings / Replays
|
||||
<!-- If applicable, attach screenshots, screen recordings or replays to help explain your problem. -->
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frequently Asked Questions
|
||||
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
|
||||
about: Explanations for common problems and questions.
|
||||
- name: OpenRA Forum
|
||||
url: https://forum.openra.net/
|
||||
about: Please ask questions about modding here.
|
||||
- name: OpenRA Discord server
|
||||
url: https://discord.openra.net/
|
||||
about: Join the community Discord server for community discussion and support.
|
||||
- name: OpenRA IRC Channel
|
||||
url: https://web.libera.chat/#openra
|
||||
about: Join our development IRC channel on Libera for discussion of development topics.
|
||||
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
35
.github/ISSUE_TEMPLATE/crash-report.md
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
|
||||
title: My game crashed
|
||||
labels: Crash
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to include all the required information we depend on to investigate and fix game-breaking bugs. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
## System Information
|
||||
<!-- Information about the operating system, engine version, game mod and package source are mandatory for investigating and fixing crashes. -->
|
||||
|
||||
- Operating System: OPERATING SYSTEM
|
||||
- OpenRA Version: ENGINE VERSION
|
||||
- OpenRA Mod: GAME MOD
|
||||
- Source: Official download package OR self-compiled OR third-party package
|
||||
- For self-compiled or third-party packages: Mono version
|
||||
|
||||
|
||||
## Exception log
|
||||
<!-- Please replace the placeholder below with the content of the exception.log file. The three backticks before and after the placeholder are used for formatting, so don't remove them. If you don't find the log folder consult https://github.com/OpenRA/OpenRA/wiki/FAQ#my-game-just-crashed. -->
|
||||
|
||||
```
|
||||
PASTE LOG HERE
|
||||
```
|
||||
|
||||
|
||||
## Replay
|
||||
<!-- If you have a replay file for the game that crashed, and it crashes again when you play it back, it will be a great help for us to fix the issue. Please compress the replay into a zip file and drag it here to include it in the report. -->
|
||||
|
||||
|
||||
## Additional information
|
||||
<!-- Please tell us below everything that you think is important for us to know about the crash. Specifically, what you were doing in the moment before the crash or ideally steps to reproduce it are very valuable information. -->
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Feature / Enhancement request
|
||||
about: Describe what you think is missing or could be improved in OpenRA.
|
||||
title: ''
|
||||
labels: Idea/Wishlist
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a guideline that shall help you to describe your idea for a missing feature or enhancement. Please follow the instructions and replace any placeholders that are written in capital letters. Instructions like this comment will not be visible in your report. -->
|
||||
|
||||
<!-- Important: Help us to avoid duplicates and use the search function to find existing requests. Please do not submit duplicate requests! Try to help others to find your request by using a precise title. -->
|
||||
|
||||
## Motivation
|
||||
<!-- Please provide a clear and concise description of the motivation behind the request. If your request is related to a problem or limitation, describe it below. -->
|
||||
|
||||
REQUEST MOTIVATION
|
||||
|
||||
|
||||
## Proposed solution
|
||||
<!-- Please describe your idea and how it is intended to address the motivation of your request. Provide a clear and concise description of the proposed changes. -->
|
||||
|
||||
PROPOSED SOLUTION
|
||||
|
||||
|
||||
## Side effects
|
||||
<!-- Changes often have side effects or unintended consequences. If you expect that your solution has any side effects, please describe them below. -->
|
||||
|
||||
EXPECTED SIDE EFFECTS
|
||||
|
||||
|
||||
## Alternatives
|
||||
<!-- Please outline any alternative solutions you have considered. -->
|
||||
|
||||
ALTERNATIVES
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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!
|
||||
* 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).
|
||||
80
.github/workflows/ci.yml
vendored
80
.github/workflows/ci.yml
vendored
@@ -1,80 +0,0 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed, 'prep-*' ]
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (.NET 6.0)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
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:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make RUNTIME=mono check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
# check-scripts does not depend on .net/mono, so is not needed here
|
||||
make RUNTIME=mono test
|
||||
|
||||
windows:
|
||||
name: Windows (.NET 6.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
.\make.ps1 check
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
143
.github/workflows/documentation.yml
vendored
143
.github/workflows/documentation.yml
vendored
@@ -1,143 +0,0 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Install .NET 6
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: playtest
|
||||
|
||||
- name: Clone docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
ref: release
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/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/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" | python3 ./packaging/format-docs.py > "docs/api/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/lua.md"
|
||||
|
||||
- name: Commit docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add api/*.md
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
|
||||
- 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
|
||||
87
.github/workflows/itch.yml
vendored
87
.github/workflows/itch.yml
vendored
@@ -1,87 +0,0 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
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/${{ 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/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.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/${{ github.event.inputs.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://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
||||
|
||||
- name: Publish Windows Itch Bundle
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: itch
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
133
.github/workflows/packaging.yml
vendored
133
.github/workflows/packaging.yml
vendored
@@ -1,133 +0,0 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
permissions:
|
||||
contents: write # for release creation (svenstaro/upload-release-action)
|
||||
|
||||
jobs:
|
||||
source:
|
||||
name: Source Tarball
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
sudo apt install libfuse2
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- 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/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Image
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Image
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Package
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt-get update
|
||||
sudo apt-get install nsis wine64
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- 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/windows/*
|
||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -13,11 +13,20 @@ obj
|
||||
_ReSharper.*/
|
||||
/.vs
|
||||
|
||||
# Visual Studio Code
|
||||
/.vscode/settings.json
|
||||
|
||||
# binaries
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
mods/*/*.dll
|
||||
mods/*/*.mdb
|
||||
mods/*/*.pdb
|
||||
/*.dll
|
||||
/*.dll.config
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.pdb
|
||||
/*.mdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
@@ -41,6 +50,10 @@ Settings.md
|
||||
openra.6
|
||||
update.log
|
||||
|
||||
# StyleCop
|
||||
*.Cache
|
||||
StyleCopViolations.xml
|
||||
|
||||
# SublimeText
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
70
.travis.yml
Normal file
70
.travis.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
dist: xenial
|
||||
language: csharp
|
||||
mono: 5.20.1
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- thirdparty/download
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- dpkg
|
||||
- markdown
|
||||
- zlib1g-dev
|
||||
- libbz2-dev
|
||||
- cmake
|
||||
- genisoimage
|
||||
- fakeroot
|
||||
- zsync
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- travis_retry make all-dependencies
|
||||
- make all
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^pkgtest-.*$/
|
||||
- /^devtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
before_deploy:
|
||||
- wget http://mirrors.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.03-2_all.deb
|
||||
- wget http://mirrors.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.03-2_amd64.deb
|
||||
- sudo dpkg -i nsis-common_3.03-2_all.deb
|
||||
- sudo dpkg -i nsis_3.03-2_amd64.deb
|
||||
- makensis -VERSION
|
||||
- export PATH=$PATH:$HOME/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: BzLdjZHvDqd5jyyHS8KK8zrNiXEIQiGRS/RMfqK6LuFPC4mXYDE4C/0oLPe93ULtai6LgUUkwB+zKq4lDV109EBO8Xp0jXKdDLLsepGU5xRLt3Z2tT4Zj+Nx6daVjAJe1MaKO5Zo5ODi8kBploRnPCffA8AMzOyQ+OwwJNlcRSs=
|
||||
file_glob: true
|
||||
file: build/*
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
repo: pchote/OpenRA
|
||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"openra.oraide-vscode",
|
||||
"openra.vscode-openra-lua",
|
||||
"EditorConfig.EditorConfig",
|
||||
"macabeus.vscode-fluent",
|
||||
]
|
||||
}
|
||||
63
.vscode/launch.json
vendored
63
.vscode/launch.json
vendored
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch (TD)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (RA)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (D2k)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
"name": "Launch (TS)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/OpenRA.dll",
|
||||
"windows": {
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
]
|
||||
}
|
||||
36
.vscode/tasks.json
vendored
36
.vscode/tasks.json
vendored
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"options": {
|
||||
"env": {
|
||||
"ENGINE_DIR": ".."
|
||||
}
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "make",
|
||||
"args": ["all", "CONFIGURATION=Debug"],
|
||||
"windows": {
|
||||
"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"
|
||||
},
|
||||
]
|
||||
}
|
||||
67
AUTHORS
67
AUTHORS
@@ -2,30 +2,27 @@ OpenRA wouldn't be where it is today without the
|
||||
hard work of many contributors.
|
||||
|
||||
The OpenRA developers are:
|
||||
* Gustas Kažukauskas (PunkPun)
|
||||
* Chris Forbes (chrisf)
|
||||
* Igor Popov (ihptru)
|
||||
* Lukas Franke (abcdefg30)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Reaperrr
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Previous developers included:
|
||||
* Alli Witheford (alzeih)
|
||||
* Caleb Anderson (RobotCaleb)
|
||||
* Chris Forbes (chrisf)
|
||||
* Curtis Shmyr (hamb)
|
||||
* Daniel Hernandez (Mancano)
|
||||
* Igor Popov (ihptru)
|
||||
* Matthias Mailänder (Mailaender)
|
||||
* Megan Bowra-Dean (beedee)
|
||||
* Mike Bundy (kehaar)
|
||||
* Oliver Brakmann (obrakmann)
|
||||
* Paul Chote (pchote)
|
||||
* Pavel Penev (penev92)
|
||||
* Reaperrr
|
||||
* Robert Pepperell (ytinasni)
|
||||
* ScottNZ
|
||||
* Tom Roostan (RoosterDragon)
|
||||
|
||||
Also thanks to:
|
||||
* abmyii
|
||||
* anvilvapre (anvilvapre)
|
||||
* Adam Valy (Tschokky)
|
||||
* Akseli Virtanen (RAGEQUIT)
|
||||
* Alexander Fast (mizipzor)
|
||||
@@ -39,17 +36,13 @@ Also thanks to:
|
||||
* Arik Lirette (Angusm3)
|
||||
* Barnaby Smith (mvi)
|
||||
* Bellator
|
||||
* Bernd Stellwag (burned42)
|
||||
* Biofreak
|
||||
* Braxton Williams (Buddytex)
|
||||
* Brendan Gluth (Mechanical_Man)
|
||||
* Brent Gardner (bggardner)
|
||||
* Bryan Wilbur
|
||||
* Bugra Cuhadaroglu (BugraC)
|
||||
* Chris Cameron (Vesuvian)
|
||||
* Chris Grant (Unit158)
|
||||
* Christer Ulfsparre (Holloweye)
|
||||
* Christoph Lahner (chlah)
|
||||
* Chris Grant (Unit158)
|
||||
* clem
|
||||
* Cody Brittain (Generalcamo)
|
||||
* Constantin Helmig (CH4Code)
|
||||
@@ -62,7 +55,6 @@ Also thanks to:
|
||||
* DeadlySurprise
|
||||
* Dmitri Suvorov (suvjunmd)
|
||||
* dtluna
|
||||
* Eduardo Cáceres (eduherminio)
|
||||
* Erasmus Schroder (rasco)
|
||||
* Eric Bajumpaa (SteelPhase)
|
||||
* Evgeniy Sergeev (evgeniysergeev)
|
||||
@@ -80,7 +72,6 @@ Also thanks to:
|
||||
* Imago
|
||||
* Iran
|
||||
* Ishan Bhargava (ishantheperson)
|
||||
* Ivaylo Draganov (dragunoff)
|
||||
* Jacob Dufault (jacobdufault)
|
||||
* James Dunne (jsd)
|
||||
* James Gilbert (DSUK)
|
||||
@@ -141,22 +132,19 @@ Also thanks to:
|
||||
* Rikhardur Bjarni Einarsson (WolfGaming)
|
||||
* Sascha Biedermann (bidifx)
|
||||
* Sean Hunt (coppro)
|
||||
* Sebastien Kerguen (xanax)
|
||||
* Shawn Collins (UberWaffe)
|
||||
* Simon Verbeke (Saticmotion)
|
||||
* Stuart McHattie (SDJMcHattie)
|
||||
* Taryn Hill (Phrohdoh)
|
||||
* Teemu Nieminen (Temeez)
|
||||
* Thomas Christlieb (ThomasChr)
|
||||
* Tim Mylemans (gecko)
|
||||
* Tirili
|
||||
* Tomas Einarsson (Mesacer)
|
||||
* Tom van Leth (tovl)
|
||||
* Trevor Nichols (ocdi)
|
||||
* Tristan Keating (Kilkakon)
|
||||
* Tristan Mühlbacher (MicroBit)
|
||||
* UnknownProgrammer
|
||||
* Vladimir Komarov (VrKomarov)
|
||||
* Wojciech Walaszek (Voidwalker)
|
||||
* Wuschel
|
||||
|
||||
Using GNU FreeFont distributed under the GNU GPL
|
||||
@@ -170,6 +158,12 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using GeoLite2 data created by MaxMind and
|
||||
distributed under the CC BY-SA 3.0 license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -179,17 +173,9 @@ under the MIT license.
|
||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
||||
Kaluzhny and released under the GNU GPL terms.
|
||||
|
||||
Using Mono.Nat by Alan McGovern, Ben Motmans,
|
||||
Nicholas Terry 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 Open.Nat by Lucas Ontivero, based on the work
|
||||
of Alan McGovern and Ben Motmans and distributed
|
||||
under the MIT license.
|
||||
|
||||
Using ICSharpCode.SharpZipLib initially by Mike
|
||||
Krueger and distributed under the GNU GPL terms.
|
||||
@@ -197,23 +183,6 @@ Krueger and distributed under the GNU GPL terms.
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
|
||||
Using DiscordRichPresence developed by Lachee
|
||||
distributed under MIT License.
|
||||
|
||||
Using Json.NET developed by James Newton-King
|
||||
distributed under MIT 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
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
games which OpenRA aims to reimagine.
|
||||
|
||||
@@ -56,8 +56,8 @@ further defined and clarified by project maintainers.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by private-messaging a project team member (users with a + in front
|
||||
of their name) via our IRC channel (#openra on Libera –
|
||||
[webchat](https://web.libera.chat/#openra)). All
|
||||
of their name) via our IRC channel (#openra on freenode –
|
||||
[webchat](http://webchat.freenode.net/?channels=openra)). All
|
||||
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
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
65
ConvertFrom-Markdown.ps1
Normal file
65
ConvertFrom-Markdown.ps1
Normal file
File diff suppressed because one or more lines are too long
101
INSTALL.md
101
INSTALL.md
@@ -8,76 +8,105 @@ Windows
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [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 Framework 4.6.1 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net461) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
|
||||
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.
|
||||
Type `make dependencies` in a command terminal to download pre-compiled native libraries for:
|
||||
* [SDL 2](http://www.libsdl.org/download-2.0.php)
|
||||
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
|
||||
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm)
|
||||
* [OpenAL](http://kcat.strangesoft.net/openal.html)
|
||||
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
|
||||
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 5.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.
|
||||
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
|
||||
|
||||
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.
|
||||
To compile OpenRA, run `make all` 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.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
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.
|
||||
Arch Linux
|
||||
----------
|
||||
|
||||
These can be installed using your package manager on various distros:
|
||||
|
||||
<details><summary>Arch Linux</summary>
|
||||
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>
|
||||
The EPEL repository is required in order for the following command to run properly.
|
||||
|
||||
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
|
||||
----------------------------------------------------
|
||||
|
||||
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
|
||||
OSX
|
||||
=====
|
||||
|
||||
[.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 >= 5.4](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.
|
||||
Use `make dependencies` to download pre-compiled native libraries for:
|
||||
* [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)
|
||||
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
|
||||
Run with `./launch-game.sh`.
|
||||
|
||||
452
Makefile
452
Makefile
@@ -1,43 +1,57 @@
|
||||
############################# INSTRUCTIONS #############################
|
||||
#
|
||||
# to compile, run:
|
||||
# make
|
||||
#
|
||||
# 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:
|
||||
# make [RUNTIME=net6] TARGETPLATFORM=unix-generic
|
||||
# make [DEBUG=true]
|
||||
#
|
||||
# to check unit tests (requires NUnit version >= 2.6), run:
|
||||
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
|
||||
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
|
||||
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
|
||||
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
|
||||
# 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:
|
||||
# make [RUNTIME=net6] check
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# make check
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
|
||||
# make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] install
|
||||
# to install, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] install
|
||||
#
|
||||
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
|
||||
# using system libraries for native dependencies, run:
|
||||
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
|
||||
# to install Linux startup scripts, desktop files and icons:
|
||||
# make install-linux-shortcuts [DEBUG=false]
|
||||
#
|
||||
# to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata
|
||||
# make install-linux-shortcuts
|
||||
# to install the engine and common mod files (omitting the default mods):
|
||||
# make install-engine
|
||||
# make install-common-mod-files
|
||||
#
|
||||
# to install FreeDesktop AppStream metadata
|
||||
# make install-linux-appdata
|
||||
#
|
||||
# to install the Unix man page
|
||||
# make install-man
|
||||
# to uninstall, run:
|
||||
# make uninstall
|
||||
#
|
||||
# for help, run:
|
||||
# make help
|
||||
#
|
||||
# to start the game, run:
|
||||
# openra
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
# OpenRA.Game.dll is a harmless false positive that we can ignore
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.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
|
||||
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
# install locations
|
||||
prefix ?= /usr/local
|
||||
datarootdir ?= $(prefix)/share
|
||||
datadir ?= $(datarootdir)
|
||||
@@ -46,160 +60,326 @@ bindir ?= $(prefix)/bin
|
||||
libdir ?= $(prefix)/lib
|
||||
gameinstalldir ?= $(libdir)/openra
|
||||
|
||||
# Toolchain
|
||||
CWD = $(shell pwd)
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
DOTNET = dotnet
|
||||
MONO = mono
|
||||
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
|
||||
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
|
||||
|
||||
# install tools
|
||||
RM = rm
|
||||
RM_R = $(RM) -r
|
||||
RM_F = $(RM) -f
|
||||
RM_RF = $(RM) -rf
|
||||
CP = cp
|
||||
CP_R = $(CP) -r
|
||||
INSTALL = install
|
||||
INSTALL_DIR = $(INSTALL) -d
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
|
||||
RUNTIME ?= net6
|
||||
CONFIGURATION ?= Release
|
||||
DOTNET_RID = $(shell ${DOTNET} --info | grep RID: | cut -w -f3)
|
||||
# Toolchain
|
||||
MSBUILD = msbuild -verbosity:m -nologo
|
||||
|
||||
# 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))
|
||||
# Enable 32 bit builds while generating the windows installer
|
||||
WIN32 = false
|
||||
|
||||
# Detect target platform for dependencies if not given by the user
|
||||
ifndef TARGETPLATFORM
|
||||
# program targets
|
||||
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
|
||||
|
||||
# dependencies
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
ifeq ($(RUNTIME)-$(DOTNET_RID),net6-osx-arm64)
|
||||
TARGETPLATFORM = osx-arm64
|
||||
os-dependencies = osx-dependencies
|
||||
else
|
||||
TARGETPLATFORM = osx-x64
|
||||
os-dependencies = linux-dependencies
|
||||
endif
|
||||
else
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
TARGETPLATFORM = linux-arm64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
#
|
||||
all:
|
||||
@echo "Compiling in ${CONFIGURATION} mode..."
|
||||
ifeq ($(RUNTIME), 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)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@./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:
|
||||
@-$(RM_RF) ./bin ./*/obj
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in Debug mode..."
|
||||
ifeq ($(RUNTIME), mono)
|
||||
# Enabling EnforceCodeStyleInBuild and GenerateDocumentationFile as a workaround for some code style rules (in particular IDE0005) being bugged and not reporting warnings/errors otherwise.
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM) -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
|
||||
else
|
||||
# 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 "Checking for explicit interface violations..."
|
||||
@./utility.sh all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@./utility.sh all --check-conditional-trait-interface-overrides
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@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')
|
||||
|
||||
test: all
|
||||
check: dependencies
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -p:Configuration=Debug
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
|
||||
@echo
|
||||
@echo "Checking for incorrect conditional trait interface overrides..."
|
||||
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
|
||||
|
||||
|
||||
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
|
||||
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
|
||||
nunit: core
|
||||
@echo
|
||||
@echo "Checking unit tests..."
|
||||
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
|
||||
echo 'nunit[3|2]-console not found!'; \
|
||||
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
|
||||
echo 'NUnit version >= 2.6 required'>&2; \
|
||||
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
|
||||
echo 'See "make help".'; \
|
||||
exit 1; \
|
||||
fi
|
||||
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
|
||||
|
||||
test: core
|
||||
@echo
|
||||
@echo "Testing Tiberian Sun mod MiniYAML..."
|
||||
@./utility.sh ts --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ts --check-yaml
|
||||
@echo
|
||||
@echo "Testing Dune 2000 mod MiniYAML..."
|
||||
@./utility.sh d2k --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe d2k --check-yaml
|
||||
@echo
|
||||
@echo "Testing Tiberian Dawn mod MiniYAML..."
|
||||
@./utility.sh cnc --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe cnc --check-yaml
|
||||
@echo
|
||||
@echo "Testing Red Alert mod MiniYAML..."
|
||||
@./utility.sh ra --check-yaml
|
||||
@mono --debug OpenRA.Utility.exe ra --check-yaml
|
||||
|
||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
#
|
||||
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),)
|
||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||
all: dependencies core
|
||||
|
||||
core:
|
||||
@command -v $(MSBUILD) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
|
||||
else
|
||||
@$(MSBUILD) -t:build -p:Configuration=Release
|
||||
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:
|
||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
|
||||
clean:
|
||||
@ $(MSBUILD) -t:clean
|
||||
@-$(RM_F) *.config
|
||||
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
install-linux-shortcuts:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
|
||||
distclean: clean
|
||||
|
||||
cli-dependencies:
|
||||
@./thirdparty/fetch-thirdparty-deps.sh
|
||||
@ $(CP_R) thirdparty/download/*.dll .
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
|
||||
|
||||
linux-dependencies: cli-dependencies geoip-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies: cli-dependencies geoip-dependencies
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
|
||||
else
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
|
||||
endif
|
||||
|
||||
osx-dependencies: cli-dependencies geoip-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
geoip-dependencies:
|
||||
@./thirdparty/fetch-geoip-db.sh
|
||||
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies geoip-dependencies
|
||||
|
||||
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
|
||||
@echo "$(VERSION)" > VERSION
|
||||
@for i in $? ; do \
|
||||
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
|
||||
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
|
||||
rm $${i}.tmp; \
|
||||
done
|
||||
|
||||
install: dependencies core install-core
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-engine:
|
||||
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DATA) "GeoLite2-Country.mmdb.gz" "$(DATA_INSTALL_DIR)/GeoLite2-Country.mmdb.gz"
|
||||
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
|
||||
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
|
||||
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
|
||||
|
||||
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
|
||||
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
|
||||
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-common-mod-files:
|
||||
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
|
||||
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
|
||||
|
||||
install-default-mods:
|
||||
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
|
||||
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
|
||||
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
|
||||
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
|
||||
|
||||
install-core: install-engine install-common-mod-files install-default-mods
|
||||
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-linux-icons:
|
||||
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(INSTALL_DATA) packaging/linux/icons/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
|
||||
$(INSTALL_DATA) packaging/linux/icons/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
$(INSTALL_DATA) packaging/linux/icons/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
|
||||
install-linux-desktop:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
|
||||
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
|
||||
|
||||
install-linux-mime:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
|
||||
install-linux-appdata:
|
||||
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
|
||||
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
|
||||
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
|
||||
|
||||
install-man: all
|
||||
@mkdir -p $(DESTDIR)$(mandir)/man6/
|
||||
@./utility.sh all --man-page > $(DESTDIR)$(mandir)/man6/openra.6
|
||||
install-man-page:
|
||||
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
|
||||
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
|
||||
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
|
||||
@-$(RM) openra.6
|
||||
|
||||
install-linux-scripts:
|
||||
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
else
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
|
||||
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
|
||||
endif
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
|
||||
|
||||
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
|
||||
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
|
||||
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
|
||||
|
||||
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
|
||||
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
|
||||
|
||||
uninstall:
|
||||
@-$(RM_R) "$(DATA_INSTALL_DIR)"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
|
||||
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
|
||||
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
|
||||
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
|
||||
done
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
|
||||
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile using Mono (version 6.4 or greater) instead of .NET 6, run:'
|
||||
@echo ' make RUNTIME=mono'
|
||||
@echo
|
||||
@echo 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net6] TARGETPLATFORM=unix-generic'
|
||||
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
|
||||
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
|
||||
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
|
||||
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
|
||||
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
|
||||
@echo
|
||||
@echo 'to check the official mods for erroneous yaml files, run:'
|
||||
@echo ' make [RUNTIME=net6] test'
|
||||
@echo ' make test'
|
||||
@echo
|
||||
@echo 'to check the engine and official mod dlls for code style violations, run:'
|
||||
@echo ' make [RUNTIME=net6] check'
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo
|
||||
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
|
||||
@echo 'using system libraries for native dependencies, run:'
|
||||
@echo ' make [RUNTIME=net6] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo
|
||||
@echo 'to install FreeDesktop startup scripts, desktop files, icons, and MIME metadata'
|
||||
@echo ' make install-linux-shortcuts'
|
||||
@echo
|
||||
@echo 'to install FreeDesktop AppStream metadata'
|
||||
@echo ' make install-linux-appdata'
|
||||
@echo
|
||||
@echo 'to install a Unix man page'
|
||||
@echo ' make install-man'
|
||||
@echo 'to start the game, run:'
|
||||
@echo ' openra'
|
||||
|
||||
########################### MAKEFILE SETTINGS ##########################
|
||||
#
|
||||
@@ -207,4 +387,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata install-man help
|
||||
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies geoip-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -26,7 +26,7 @@ namespace OpenRA.Activities
|
||||
public readonly Color Color;
|
||||
public readonly Sprite Tile;
|
||||
|
||||
public TargetLineNode(in Target target, Color color, Sprite tile = null)
|
||||
public TargetLineNode(Target target, Color color, Sprite tile = null)
|
||||
{
|
||||
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
|
||||
// if "yield break" in TargetLineNode(Actor self) is not feasible.
|
||||
@@ -40,50 +40,21 @@ namespace OpenRA.Activities
|
||||
* Things to be aware of when writing activities:
|
||||
*
|
||||
* - Use "return true" at least once somewhere in the tick method.
|
||||
* - Do not "reuse" activity objects (by queuing them as next or child, for example) that have already started running.
|
||||
* - Do not "reuse" (with "SequenceActivities", for example) activity objects that have already started running.
|
||||
* Queue a new instance instead.
|
||||
* - Avoid calling actor.CancelActivity(). It is almost always a bug. Call activity.Cancel() instead.
|
||||
* - Do not evaluate dynamic state (an actor's location, health, conditions, etc.) in the activity's constructor,
|
||||
* as that might change before the activity gets to tick for the first time. Use the OnFirstRun() method instead.
|
||||
*/
|
||||
public abstract class Activity : IActivityInterface
|
||||
{
|
||||
public ActivityState State { get; private set; }
|
||||
|
||||
Activity childActivity;
|
||||
protected Activity ChildActivity
|
||||
{
|
||||
get => SkipDoneActivities(childActivity);
|
||||
private set => childActivity = value;
|
||||
}
|
||||
|
||||
Activity nextActivity;
|
||||
public Activity NextActivity
|
||||
{
|
||||
get => SkipDoneActivities(nextActivity);
|
||||
private set => nextActivity = value;
|
||||
}
|
||||
|
||||
internal static Activity SkipDoneActivities(Activity first)
|
||||
{
|
||||
// If first.Cancel() was called while it was queued (i.e. before it first ticked), its state will be Done
|
||||
// rather than Queued (the activity system guarantees that it cannot be Active or Canceling).
|
||||
// An unknown number of ticks may have elapsed between the Cancel() call and now,
|
||||
// so we cannot make any assumptions on the value of first.NextActivity.
|
||||
// We must not return first (ticking it would be bogus), but returning null would potentially
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
while (first != null && first.State == ActivityState.Done)
|
||||
first = first.nextActivity;
|
||||
|
||||
return first;
|
||||
}
|
||||
protected Activity ChildActivity { get; private set; }
|
||||
public Activity NextActivity { get; private set; }
|
||||
|
||||
public bool IsInterruptible { 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 firstRunCompleted;
|
||||
bool lastRun;
|
||||
|
||||
public Activity()
|
||||
@@ -95,18 +66,14 @@ namespace OpenRA.Activities
|
||||
public Activity TickOuter(Actor self)
|
||||
{
|
||||
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)
|
||||
{
|
||||
OnFirstRun(self);
|
||||
firstRunCompleted = true;
|
||||
State = ActivityState.Active;
|
||||
}
|
||||
|
||||
if (!firstRunCompleted)
|
||||
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method.");
|
||||
|
||||
// Only run the parent tick when the child is done.
|
||||
// We must always let the child finish on its own before continuing.
|
||||
if (ChildHasPriority)
|
||||
@@ -120,8 +87,7 @@ namespace OpenRA.Activities
|
||||
lastRun = Tick(self);
|
||||
|
||||
// Avoid a single tick delay if the childactivity was just queued.
|
||||
var ca = ChildActivity;
|
||||
if (ca != null && ca.State == ActivityState.Queued)
|
||||
if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
|
||||
{
|
||||
if (ChildHasPriority)
|
||||
lastRun = TickChild(self) && finishing;
|
||||
@@ -186,7 +152,8 @@ namespace OpenRA.Activities
|
||||
/// </summary>
|
||||
internal void OnActorDisposeOuter(Actor self)
|
||||
{
|
||||
ChildActivity?.OnActorDisposeOuter(self);
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.OnActorDisposeOuter(self);
|
||||
|
||||
OnActorDispose(self);
|
||||
}
|
||||
@@ -199,26 +166,26 @@ namespace OpenRA.Activities
|
||||
if (!IsInterruptible)
|
||||
return;
|
||||
|
||||
ChildActivity?.Cancel(self);
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Cancel(self);
|
||||
|
||||
// Directly mark activities that are queued and therefore didn't run yet as done
|
||||
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
|
||||
State = ActivityState.Canceling;
|
||||
}
|
||||
|
||||
public void Queue(Activity activity)
|
||||
{
|
||||
var it = this;
|
||||
while (it.nextActivity != null)
|
||||
it = it.nextActivity;
|
||||
it.nextActivity = activity;
|
||||
if (NextActivity != null)
|
||||
NextActivity.Queue(activity);
|
||||
else
|
||||
NextActivity = activity;
|
||||
}
|
||||
|
||||
public void QueueChild(Activity activity)
|
||||
{
|
||||
if (childActivity != null)
|
||||
childActivity.Queue(activity);
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Queue(activity);
|
||||
else
|
||||
childActivity = activity;
|
||||
ChildActivity = activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,9 +209,11 @@ namespace OpenRA.Activities
|
||||
|
||||
Console.WriteLine(GetType().ToString().Split('.').Last());
|
||||
|
||||
ChildActivity?.PrintActivityTree(self, origin, level + 1);
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.PrintActivityTree(self, origin, level + 1);
|
||||
|
||||
NextActivity?.PrintActivityTree(self, origin, level);
|
||||
if (NextActivity != null)
|
||||
NextActivity.PrintActivityTree(self, origin, level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,21 +239,15 @@ namespace OpenRA.Activities
|
||||
|
||||
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
|
||||
{
|
||||
// Skips Done child and next activities
|
||||
if (includeChildren)
|
||||
{
|
||||
var ca = ChildActivity;
|
||||
if (ca != null)
|
||||
foreach (var a in ca.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
if (includeChildren && ChildActivity != null)
|
||||
foreach (var a in ChildActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
|
||||
if (this is T)
|
||||
yield return (T)(object)this;
|
||||
|
||||
var na = NextActivity;
|
||||
if (na != null)
|
||||
foreach (var a in na.ActivitiesImplementing<T>())
|
||||
if (NextActivity != null)
|
||||
foreach (var a in NextActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -22,11 +22,11 @@ namespace OpenRA.Activities
|
||||
IsInterruptible = interruptible;
|
||||
}
|
||||
|
||||
readonly Action a;
|
||||
Action a;
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
a.Invoke();
|
||||
if (a != null) a();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using Eluant.ObjectBinding;
|
||||
@@ -24,18 +22,9 @@ using OpenRA.Traits;
|
||||
|
||||
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
|
||||
{
|
||||
internal readonly struct SyncHash
|
||||
internal struct SyncHash
|
||||
{
|
||||
public readonly ISync Trait;
|
||||
readonly Func<object, int> hashFunction;
|
||||
@@ -55,59 +44,35 @@ namespace OpenRA
|
||||
public bool WillDispose { get; private set; }
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
Activity currentActivity;
|
||||
public Activity CurrentActivity
|
||||
{
|
||||
get => Activity.SkipDoneActivities(currentActivity);
|
||||
private set => currentActivity = value;
|
||||
}
|
||||
public Activity CurrentActivity { get; private set; }
|
||||
|
||||
public int Generation;
|
||||
public Actor ReplacedByActor;
|
||||
|
||||
public IEffectiveOwner EffectiveOwner { get; }
|
||||
public IOccupySpace OccupiesSpace { get; }
|
||||
public ITargetable[] Targetables { get; }
|
||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||
public IOccupySpace OccupiesSpace { get; private set; }
|
||||
public ITargetable[] Targetables { get; private set; }
|
||||
|
||||
public bool IsIdle => CurrentActivity == null;
|
||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||
public bool IsIdle { get { return CurrentActivity == null; } }
|
||||
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
||||
|
||||
public CPos Location => OccupiesSpace.TopLeft;
|
||||
public WPos CenterPosition => OccupiesSpace.CenterPosition;
|
||||
public CPos Location { get { return OccupiesSpace.TopLeft; } }
|
||||
public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
|
||||
|
||||
public WRot Orientation => facing?.Orientation ?? WRot.None;
|
||||
|
||||
/// <summary>Value used to represent an invalid token.</summary>
|
||||
public static readonly int InvalidConditionToken = -1;
|
||||
|
||||
class ConditionState
|
||||
public WRot Orientation
|
||||
{
|
||||
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
|
||||
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
|
||||
|
||||
/// <summary>Unique integers identifying granted instances of the condition.</summary>
|
||||
public readonly HashSet<int> Tokens = new HashSet<int>();
|
||||
get
|
||||
{
|
||||
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
|
||||
var facingValue = facing != null ? facing.Facing : 0;
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
|
||||
}
|
||||
}
|
||||
|
||||
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
|
||||
|
||||
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
|
||||
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
|
||||
|
||||
int nextConditionToken = 1;
|
||||
|
||||
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
|
||||
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
|
||||
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
|
||||
|
||||
internal SyncHash[] SyncHashes { get; }
|
||||
internal SyncHash[] SyncHashes { get; private set; }
|
||||
|
||||
readonly IFacing facing;
|
||||
readonly IHealth health;
|
||||
readonly IResolveOrder[] resolveOrders;
|
||||
readonly IRenderModifier[] renderModifiers;
|
||||
readonly IRender[] renders;
|
||||
readonly IMouseBounds[] mouseBounds;
|
||||
@@ -115,26 +80,17 @@ namespace OpenRA
|
||||
readonly IDefaultVisibility defaultVisibility;
|
||||
readonly INotifyBecomingIdle[] becomingIdles;
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly IEnumerable<WPos> enabledTargetableWorldPositions;
|
||||
bool created;
|
||||
readonly ITargetablePositions[] targetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
|
||||
.FirstOrDefault(i => i.Count() > 1);
|
||||
|
||||
if (duplicateInit != null)
|
||||
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
|
||||
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
var ownerInit = init.GetOrDefault<OwnerInit>();
|
||||
if (ownerInit != null)
|
||||
Owner = ownerInit.Value(world);
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
Owner = init.Get<OwnerInit, Player>();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
@@ -144,117 +100,42 @@ namespace OpenRA
|
||||
throw new NotImplementedException("No rules definition for unit " + name);
|
||||
|
||||
Info = world.Map.Rules.Actors[name];
|
||||
|
||||
var resolveOrdersList = new List<IResolveOrder>();
|
||||
var renderModifiersList = new List<IRenderModifier>();
|
||||
var rendersList = new List<IRender>();
|
||||
var mouseBoundsList = new List<IMouseBounds>();
|
||||
var visibilityModifiersList = new List<IVisibilityModifier>();
|
||||
var becomingIdlesList = new List<INotifyBecomingIdle>();
|
||||
var tickIdlesList = new List<INotifyIdle>();
|
||||
var targetablesList = new List<ITargetable>();
|
||||
var targetablePositionsList = new List<ITargetablePositions>();
|
||||
var syncHashesList = new List<SyncHash>();
|
||||
|
||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||
foreach (var trait in Info.TraitsInConstructOrder())
|
||||
{
|
||||
var trait = traitInfo.Create(init);
|
||||
AddTrait(trait);
|
||||
AddTrait(trait.Create(init));
|
||||
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// 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
|
||||
// rules for spacing in order to keep these assignments compact and readable.
|
||||
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
|
||||
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
|
||||
{ if (trait is IFacing t) facing = t; }
|
||||
{ if (trait is IHealth t) health = t; }
|
||||
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
|
||||
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
|
||||
{ if (trait is IRender t) rendersList.Add(t); }
|
||||
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
|
||||
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
|
||||
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
|
||||
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
|
||||
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
|
||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||
}
|
||||
|
||||
resolveOrders = resolveOrdersList.ToArray();
|
||||
renderModifiers = renderModifiersList.ToArray();
|
||||
renders = rendersList.ToArray();
|
||||
mouseBounds = mouseBoundsList.ToArray();
|
||||
visibilityModifiers = visibilityModifiersList.ToArray();
|
||||
becomingIdles = becomingIdlesList.ToArray();
|
||||
tickIdles = tickIdlesList.ToArray();
|
||||
Targetables = targetablesList.ToArray();
|
||||
var targetablePositions = targetablePositionsList.ToArray();
|
||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||
SyncHashes = syncHashesList.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize(bool addToWorld = true)
|
||||
{
|
||||
created = true;
|
||||
|
||||
// Make sure traits are usable for condition notifiers
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
|
||||
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
|
||||
foreach (var provider in TraitsImplementing<IObservesVariables>())
|
||||
{
|
||||
foreach (var variableUser in provider.GetVariableObservers())
|
||||
{
|
||||
allObserverNotifiers.Add(variableUser.Notifier);
|
||||
foreach (var variable in variableUser.Variables)
|
||||
{
|
||||
var cs = conditionStates.GetOrAdd(variable);
|
||||
cs.Notifiers.Add(variableUser.Notifier);
|
||||
|
||||
// Initialize conditions that have not yet been granted to 0
|
||||
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
|
||||
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
|
||||
conditionCache[variable] = cs.Tokens.Count;
|
||||
}
|
||||
// Some traits rely on properties provided by IOccupySpace in their initialization,
|
||||
// so we must ready it now, we cannot wait until all traits have finished construction.
|
||||
if (trait is IOccupySpaceInfo)
|
||||
OccupiesSpace = Trait<IOccupySpace>();
|
||||
}
|
||||
}
|
||||
|
||||
// Update all traits with their initial condition state
|
||||
foreach (var notify in allObserverNotifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
|
||||
// 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.
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
foreach (var ica in TraitsImplementing<ICreationActivity>())
|
||||
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
|
||||
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
|
||||
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
|
||||
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
|
||||
facing = TraitOrDefault<IFacing>();
|
||||
health = TraitOrDefault<IHealth>();
|
||||
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
|
||||
renders = TraitsImplementing<IRender>().ToArray();
|
||||
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
|
||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||
defaultVisibility = Trait<IDefaultVisibility>();
|
||||
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
|
||||
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
|
||||
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
|
||||
world.AddFrameEndTask(w =>
|
||||
{
|
||||
if (!ica.IsTraitEnabled())
|
||||
continue;
|
||||
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
|
||||
// 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 (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
|
||||
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
|
||||
});
|
||||
|
||||
if (creationActivity != null)
|
||||
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}");
|
||||
|
||||
var activity = ica.GetCreationActivity();
|
||||
if (activity == null)
|
||||
continue;
|
||||
|
||||
creationActivity = ica;
|
||||
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
|
||||
if (addToWorld)
|
||||
World.Add(this);
|
||||
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
@@ -317,7 +198,7 @@ namespace OpenRA
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
public Rectangle MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
@@ -326,22 +207,18 @@ namespace OpenRA
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Polygon.Empty;
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
{
|
||||
if (!queued)
|
||||
CancelActivity();
|
||||
|
||||
QueueActivity(nextActivity);
|
||||
}
|
||||
|
||||
public void QueueActivity(Activity nextActivity)
|
||||
{
|
||||
if (!created)
|
||||
throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
|
||||
|
||||
if (CurrentActivity == null)
|
||||
CurrentActivity = nextActivity;
|
||||
else
|
||||
@@ -350,7 +227,8 @@ namespace OpenRA
|
||||
|
||||
public void CancelActivity()
|
||||
{
|
||||
CurrentActivity?.Cancel(this);
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.Cancel(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -360,7 +238,8 @@ namespace OpenRA
|
||||
|
||||
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)
|
||||
@@ -401,7 +280,8 @@ namespace OpenRA
|
||||
{
|
||||
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
|
||||
// This should be done before the FrameEndTask to avoid dependency issues.
|
||||
CurrentActivity?.OnActorDisposeOuter(this);
|
||||
if (CurrentActivity != null)
|
||||
CurrentActivity.OnActorDisposeOuter(this);
|
||||
|
||||
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
|
||||
WillDispose = true;
|
||||
@@ -420,16 +300,11 @@ namespace OpenRA
|
||||
World.TraitDict.RemoveActor(this);
|
||||
Disposed = true;
|
||||
|
||||
luaInterface?.Value.OnActorDestroyed();
|
||||
if (luaInterface != null)
|
||||
luaInterface.Value.OnActorDestroyed();
|
||||
});
|
||||
}
|
||||
|
||||
public void ResolveOrder(Order order)
|
||||
{
|
||||
foreach (var r in resolveOrders)
|
||||
r.ResolveOrder(this, order);
|
||||
}
|
||||
|
||||
// TODO: move elsewhere.
|
||||
public void ChangeOwner(Player newOwner)
|
||||
{
|
||||
@@ -481,7 +356,7 @@ namespace OpenRA
|
||||
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)
|
||||
return;
|
||||
@@ -522,7 +397,7 @@ namespace OpenRA
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var targetable in Targetables)
|
||||
if (targetable.TargetableBy(this, byActor))
|
||||
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -530,71 +405,16 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<WPos> GetTargetablePositions()
|
||||
{
|
||||
if (EnabledTargetablePositions.Any())
|
||||
return enabledTargetableWorldPositions;
|
||||
if (staticTargetablePositions != null)
|
||||
return staticTargetablePositions;
|
||||
|
||||
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||
if (enabledTargetablePositionTraits.Any())
|
||||
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
|
||||
|
||||
return new[] { CenterPosition };
|
||||
}
|
||||
|
||||
#region Conditions
|
||||
|
||||
void UpdateConditionState(string condition, int token, bool isRevoke)
|
||||
{
|
||||
var conditionState = conditionStates.GetOrAdd(condition);
|
||||
|
||||
if (isRevoke)
|
||||
conditionState.Tokens.Remove(token);
|
||||
else
|
||||
conditionState.Tokens.Add(token);
|
||||
|
||||
conditionCache[condition] = conditionState.Tokens.Count;
|
||||
|
||||
// Conditions may be granted or revoked before the state is initialized.
|
||||
// These notifications will be processed after INotifyCreated.Created.
|
||||
if (created)
|
||||
foreach (var notify in conditionState.Notifiers)
|
||||
notify(this, readOnlyConditionCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants a specified condition if it is valid.
|
||||
/// Otherwise, just returns InvalidConditionToken.
|
||||
/// </summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(condition))
|
||||
return InvalidConditionToken;
|
||||
|
||||
var token = nextConditionToken++;
|
||||
conditionTokens.Add(token, condition);
|
||||
UpdateConditionState(condition, token, false);
|
||||
return token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revokes a previously granted condition.
|
||||
/// </summary>
|
||||
/// <param name="token">The token ID returned by GrantCondition.</param>
|
||||
/// <returns>The invalid token ID.</returns>
|
||||
public int RevokeCondition(int token)
|
||||
{
|
||||
if (!conditionTokens.TryGetValue(token, out var condition))
|
||||
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}.");
|
||||
|
||||
conditionTokens.Remove(token);
|
||||
UpdateConditionState(condition, token, true);
|
||||
return InvalidConditionToken;
|
||||
}
|
||||
|
||||
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
|
||||
public bool TokenValid(int token)
|
||||
{
|
||||
return conditionTokens.ContainsKey(token);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scripting interface
|
||||
|
||||
Lazy<ScriptActorInterface> luaInterface;
|
||||
@@ -606,13 +426,14 @@ namespace OpenRA
|
||||
|
||||
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
|
||||
{
|
||||
get => luaInterface.Value[runtime, keyValue];
|
||||
set => luaInterface.Value[runtime, keyValue] = value;
|
||||
get { return luaInterface.Value[runtime, keyValue]; }
|
||||
set { luaInterface.Value[runtime, keyValue] = value; }
|
||||
}
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
|
||||
Actor a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -620,7 +441,7 @@ namespace OpenRA
|
||||
|
||||
public LuaValue ToString(LuaRuntime runtime)
|
||||
{
|
||||
return $"Actor ({this})";
|
||||
return "Actor ({0})".F(this);
|
||||
}
|
||||
|
||||
public bool HasScriptProperty(string name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -16,7 +16,7 @@ using OpenRA.Scripting;
|
||||
|
||||
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
|
||||
// X and Y are 12 bits (signed): -2048...2047
|
||||
@@ -25,13 +25,13 @@ namespace OpenRA
|
||||
public readonly int Bits;
|
||||
|
||||
// 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
|
||||
// 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 x, int y)
|
||||
@@ -58,13 +58,7 @@ namespace OpenRA
|
||||
public bool Equals(CPos other) { return Bits == other.Bits; }
|
||||
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Layer == 0)
|
||||
return X + "," + Y;
|
||||
|
||||
return X + "," + Y + "," + Layer;
|
||||
}
|
||||
public override string ToString() { return X + "," + Y; }
|
||||
|
||||
public MPos ToMPos(Map map)
|
||||
{
|
||||
@@ -95,35 +89,41 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
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})");
|
||||
CPos a;
|
||||
CVec b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
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);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
CPos a;
|
||||
var rightType = right.WrappedClrType();
|
||||
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})");
|
||||
if (!left.TryGetClrValue(out a))
|
||||
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))
|
||||
{
|
||||
right.TryGetClrValue(out CPos b);
|
||||
CPos b;
|
||||
right.TryGetClrValue(out b);
|
||||
return new LuaCustomClrObject(a - b);
|
||||
}
|
||||
else if (rightType == typeof(CVec))
|
||||
{
|
||||
right.TryGetClrValue(out CVec b);
|
||||
CVec b;
|
||||
right.TryGetClrValue(out 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)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
|
||||
CPos a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -138,11 +138,14 @@ namespace OpenRA
|
||||
case "X": return X;
|
||||
case "Y": return Y;
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -17,7 +17,7 @@ using OpenRA.Scripting;
|
||||
|
||||
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;
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace OpenRA
|
||||
|
||||
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 int LengthSquared => X * X + Y * Y;
|
||||
public int Length => Exts.ISqrt(LengthSquared);
|
||||
public int LengthSquared { get { return X * X + Y * Y; } }
|
||||
public int Length { get { return Exts.ISqrt(LengthSquared); } }
|
||||
|
||||
public CVec Clamp(Rectangle r)
|
||||
{
|
||||
@@ -75,16 +75,18 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
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})");
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
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);
|
||||
}
|
||||
|
||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
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})");
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
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);
|
||||
}
|
||||
@@ -96,7 +98,8 @@ namespace OpenRA
|
||||
|
||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||
{
|
||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||
CVec a, b;
|
||||
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
|
||||
return false;
|
||||
|
||||
return a == b;
|
||||
@@ -110,11 +113,14 @@ namespace OpenRA
|
||||
{
|
||||
case "X": return X;
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,12 +9,12 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Common.Installer
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum Availability
|
||||
public interface ICacheStorage<T>
|
||||
{
|
||||
Unavailable,
|
||||
GameSource,
|
||||
DigitalInstall
|
||||
void Remove(string key);
|
||||
void Store(string key, T data);
|
||||
T Retrieve(string key);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class DefaultPlayer : IGlobalModData
|
||||
{
|
||||
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
|
||||
}
|
||||
}
|
||||
97
OpenRA.Game/Download.cs
Normal file
97
OpenRA.Game/Download.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 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)
|
||||
if (wc != null)
|
||||
wc.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -17,8 +17,8 @@ namespace OpenRA.Effects
|
||||
{
|
||||
public class AsyncAction : IEffect
|
||||
{
|
||||
readonly Action a;
|
||||
readonly IAsyncResult ar;
|
||||
Action a;
|
||||
IAsyncResult ar;
|
||||
|
||||
public AsyncAction(IAsyncResult ar, Action a)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -17,7 +17,7 @@ namespace OpenRA.Effects
|
||||
{
|
||||
public class DelayedAction : IEffect
|
||||
{
|
||||
readonly Action a;
|
||||
Action a;
|
||||
int delay;
|
||||
|
||||
public DelayedAction(int delay, Action a)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -19,23 +18,26 @@ namespace OpenRA.Effects
|
||||
public class DelayedImpact : IEffect
|
||||
{
|
||||
readonly Target target;
|
||||
readonly Actor firedBy;
|
||||
readonly IEnumerable<int> damageModifiers;
|
||||
readonly IWarhead wh;
|
||||
readonly WarheadArgs args;
|
||||
|
||||
int delay;
|
||||
|
||||
public DelayedImpact(int delay, IWarhead wh, Target target, WarheadArgs args)
|
||||
public DelayedImpact(int delay, IWarhead wh, Target target, Actor firedBy, IEnumerable<int> damageModifiers)
|
||||
{
|
||||
this.wh = wh;
|
||||
this.delay = delay;
|
||||
|
||||
this.target = target;
|
||||
this.args = args;
|
||||
this.firedBy = firedBy;
|
||||
this.damageModifiers = damageModifiers;
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
{
|
||||
if (--delay <= 0)
|
||||
world.AddFrameEndTask(w => { w.Remove(this); wh.DoImpact(target, args); });
|
||||
world.AddFrameEndTask(w => { w.Remove(this); wh.DoImpact(target, firedBy, damageModifiers); });
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr) { yield break; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -24,5 +24,4 @@ namespace OpenRA.Effects
|
||||
public interface ISpatiallyPartitionable { }
|
||||
|
||||
public interface IEffectAboveShroud { IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr); }
|
||||
public interface IEffectAnnotation { IEnumerable<IRenderable> RenderAnnotation(WorldRenderer wr); }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -16,7 +16,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -31,8 +30,6 @@ namespace OpenRA
|
||||
public readonly string LaunchPath;
|
||||
public readonly string[] LaunchArgs;
|
||||
public Sprite Icon { get; internal set; }
|
||||
public Sprite Icon2x { get; internal set; }
|
||||
public Sprite Icon3x { get; internal set; }
|
||||
|
||||
public static string MakeKey(Manifest mod) { return MakeKey(mod.Id, mod.Metadata.Version); }
|
||||
public static string MakeKey(ExternalMod mod) { return MakeKey(mod.Id, mod.Version); }
|
||||
@@ -44,29 +41,14 @@ namespace OpenRA
|
||||
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
Sheet CreateSheet()
|
||||
{
|
||||
var sheet = new Sheet(SheetType.BGRA, new Size(512, 512));
|
||||
|
||||
// We must manually force the buffer creation to avoid a crash
|
||||
// that is indirectly triggered by rendering from a Sheet that
|
||||
// has not yet been written to.
|
||||
sheet.CreateBuffer();
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||
// If the player has defined a local support directory (in the game directory)
|
||||
// then this will override both the regular and system support dirs
|
||||
var sources = new[] { Platform.SystemSupportDir, Platform.SupportDir };
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
@@ -91,23 +73,11 @@ namespace OpenRA
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
|
||||
if (sheetBuilder != null)
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
@@ -116,60 +86,48 @@ namespace OpenRA
|
||||
mods[key] = mod;
|
||||
}
|
||||
|
||||
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
|
||||
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
|
||||
{
|
||||
if (mod.Metadata.Hidden)
|
||||
return;
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Id", mod.Id),
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||
}));
|
||||
|
||||
var iconData = "";
|
||||
using (var stream = mod.Package.GetStream("icon.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
iconData = Convert.ToBase64String(stream.ReadAllBytes());
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||
if (stream != null)
|
||||
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
var yaml = new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("Id", mod.Id),
|
||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||
new MiniYamlNode("Icon", iconData),
|
||||
new MiniYamlNode("LaunchPath", launchPath),
|
||||
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
|
||||
}))
|
||||
};
|
||||
|
||||
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||
if (stream != null)
|
||||
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))
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
{
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
|
||||
// If using the modern support dir we must also write the registration
|
||||
// to the legacy support dir for older engine versions, but ONLY if it exists
|
||||
var legacyPath = Platform.GetSupportDir(SupportDirType.LegacyUser);
|
||||
if (Directory.Exists(legacyPath))
|
||||
sources.Add(legacyPath);
|
||||
}
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.Value, forceRegistration: true);
|
||||
LoadMod(yaml.First().Value, forceRegistration: true);
|
||||
|
||||
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");
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(metadataPath);
|
||||
File.WriteAllLines(Path.Combine(metadataPath, key + ".yaml"), lines);
|
||||
File.WriteAllLines(Path.Combine(metadataPath, key + ".yaml"), yaml.ToLines().ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -186,9 +144,17 @@ namespace OpenRA
|
||||
/// * Filename doesn't match internal key
|
||||
/// * Fails to parse as a mod registration
|
||||
/// </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.SystemSupportDir);
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
var activeModKey = ExternalMod.MakeKey(activeMod);
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
@@ -203,10 +169,13 @@ namespace OpenRA
|
||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||
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
|
||||
// HACK: Explicitly invalidate paths to OpenRA.dll to clean up bogus metadata files
|
||||
// that were created after the initial migration from .NET Framework to Core/5.
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey && Path.GetExtension(m.LaunchPath) != ".dll")
|
||||
if (File.Exists(m.LaunchPath) && Path.GetFileNameWithoutExtension(path) == modKey &&
|
||||
!(activeMod != null && m.LaunchPath == activeMod.LaunchPath && m.Id == activeMod.Id && m.Version != activeMod.Version))
|
||||
continue;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -236,10 +205,17 @@ namespace OpenRA
|
||||
|
||||
internal void Unregister(Manifest mod, ModRegistration registration)
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
foreach (var source in GetSupportDirs(registration))
|
||||
foreach (var source in sources.Distinct())
|
||||
{
|
||||
var path = Path.Combine(source, "ModMetadata", key + ".yaml");
|
||||
try
|
||||
@@ -255,33 +231,10 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> GetSupportDirs(ModRegistration registration)
|
||||
{
|
||||
var sources = new HashSet<string>(4);
|
||||
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 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 ExternalMod this[string key] { get { return mods[key]; } }
|
||||
public int Count { get { return mods.Count; } }
|
||||
public ICollection<string> Keys { get { return mods.Keys; } }
|
||||
public ICollection<ExternalMod> Values { get { return mods.Values; } }
|
||||
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
|
||||
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
|
||||
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -38,6 +38,12 @@ namespace OpenRA
|
||||
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 IEnumerable<string> GetNamespaces(this Assembly a)
|
||||
@@ -47,7 +53,7 @@ namespace OpenRA
|
||||
|
||||
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)
|
||||
@@ -74,7 +80,7 @@ namespace OpenRA
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
}
|
||||
|
||||
public static bool PolygonContains(this int2[] polygon, int2 p)
|
||||
@@ -95,16 +101,6 @@ namespace OpenRA
|
||||
return windingNumber != 0;
|
||||
}
|
||||
|
||||
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
|
||||
{
|
||||
// If line segments AB and CD intersect:
|
||||
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
|
||||
// - 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
|
||||
// Assumes that lines are not collinear
|
||||
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
|
||||
}
|
||||
|
||||
public static bool HasModifier(this Modifiers k, Modifiers mod)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
@@ -114,19 +110,13 @@ namespace OpenRA
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
|
||||
where V : new()
|
||||
{
|
||||
return d.GetOrAdd(k, new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
d.Add(k, ret = v);
|
||||
return ret;
|
||||
return d.GetOrAdd(k, _ => new V());
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
|
||||
{
|
||||
if (!d.TryGetValue(k, out var ret))
|
||||
V ret;
|
||||
if (!d.TryGetValue(k, out ret))
|
||||
d.Add(k, ret = createFn(k));
|
||||
return ret;
|
||||
}
|
||||
@@ -153,34 +143,14 @@ namespace OpenRA
|
||||
if (xs.Count == 0)
|
||||
{
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
else
|
||||
return default;
|
||||
return default(T);
|
||||
}
|
||||
else
|
||||
return xs.ElementAt(r.Next(xs.Count));
|
||||
}
|
||||
|
||||
public static Rectangle Union(this IEnumerable<Rectangle> rects)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
var first = true;
|
||||
var result = Rectangle.Empty;
|
||||
foreach (var rect in rects)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
result = rect;
|
||||
continue;
|
||||
}
|
||||
|
||||
result = Rectangle.Union(rect, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float Product(this IEnumerable<float> xs)
|
||||
{
|
||||
return xs.Aggregate(1f, (a, x) => a * x);
|
||||
@@ -230,9 +200,9 @@ namespace OpenRA
|
||||
{
|
||||
if (!e.MoveNext())
|
||||
if (throws)
|
||||
throw new ArgumentException("Collection must not be empty.", nameof(ts));
|
||||
throw new ArgumentException("Collection must not be empty.", "ts");
|
||||
else
|
||||
return default;
|
||||
return default(T);
|
||||
t = e.Current;
|
||||
u = selector(t);
|
||||
while (e.MoveNext())
|
||||
@@ -272,7 +242,7 @@ namespace OpenRA
|
||||
public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -313,7 +283,7 @@ namespace OpenRA
|
||||
public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -351,14 +321,10 @@ namespace OpenRA
|
||||
return root;
|
||||
}
|
||||
|
||||
public static int MultiplyBySqrtTwo(short number)
|
||||
{
|
||||
return number * 46341 / 32768;
|
||||
}
|
||||
|
||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||
{
|
||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||
int remainder;
|
||||
var quotient = Math.DivRem(dividend, divisor, out remainder);
|
||||
if (remainder == 0)
|
||||
return quotient;
|
||||
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
|
||||
@@ -374,11 +340,6 @@ namespace OpenRA
|
||||
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)
|
||||
{
|
||||
return new HashSet<T>(source);
|
||||
@@ -401,8 +362,7 @@ namespace OpenRA
|
||||
|
||||
// Try to build a dictionary and log all duplicates found (if any):
|
||||
var dupKeys = new Dictionary<TKey, List<string>>();
|
||||
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
||||
var d = new Dictionary<TKey, TElement>(capacity);
|
||||
var d = new Dictionary<TKey, TElement>();
|
||||
foreach (var item in source)
|
||||
{
|
||||
var key = keySelector(item);
|
||||
@@ -413,28 +373,30 @@ namespace OpenRA
|
||||
continue;
|
||||
|
||||
// Check for a key conflict:
|
||||
if (!d.TryAdd(key, element))
|
||||
if (d.ContainsKey(key))
|
||||
{
|
||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||
List<string> dupKeyMessages;
|
||||
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
|
||||
{
|
||||
// Log the initial conflicting value already inserted:
|
||||
dupKeyMessages = new List<string>
|
||||
{
|
||||
logValue(d[key])
|
||||
};
|
||||
dupKeyMessages = new List<string>();
|
||||
dupKeyMessages.Add(logValue(d[key]));
|
||||
dupKeys.Add(key, dupKeyMessages);
|
||||
}
|
||||
|
||||
// Log this conflicting value:
|
||||
dupKeyMessages.Add(logValue(element));
|
||||
continue;
|
||||
}
|
||||
|
||||
d.Add(key, element);
|
||||
}
|
||||
|
||||
// If any duplicates were found, throw a descriptive error
|
||||
if (dupKeys.Count > 0)
|
||||
{
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
|
||||
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
|
||||
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => "{0}: [{1}]".F(logKey(p.Key), string.Join(",", p.Value))));
|
||||
var msg = "{0}, duplicate values found for the following keys: {1}".F(debugName, badKeysFormatted);
|
||||
throw new ArgumentException(msg);
|
||||
}
|
||||
|
||||
@@ -515,7 +477,8 @@ namespace OpenRA
|
||||
|
||||
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)
|
||||
@@ -525,7 +488,7 @@ namespace OpenRA
|
||||
if (t.IsTraitEnabled())
|
||||
return t;
|
||||
|
||||
return default;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public static T FirstEnabledTraitOrDefault<T>(this T[] ts)
|
||||
@@ -535,72 +498,8 @@ namespace OpenRA
|
||||
if (t.IsTraitEnabled())
|
||||
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>
|
||||
@@ -616,7 +515,7 @@ namespace OpenRA
|
||||
|
||||
if (values.Any(x => !names.Contains(x)))
|
||||
{
|
||||
value = default;
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -15,6 +15,7 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
@@ -72,14 +73,32 @@ namespace OpenRA
|
||||
return "";
|
||||
|
||||
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<>))
|
||||
{
|
||||
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
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<>))
|
||||
{
|
||||
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
|
||||
}
|
||||
|
||||
// This is only for documentation generation
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
@@ -94,14 +113,17 @@ namespace OpenRA
|
||||
var formattedKey = FormatValue(key);
|
||||
var formattedValue = FormatValue(value);
|
||||
|
||||
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
|
||||
result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (v is DateTime d)
|
||||
return d.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
|
||||
return ""; // TODO
|
||||
|
||||
if (t == typeof(DateTime))
|
||||
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
|
||||
|
||||
// Try the TypeConverter
|
||||
var conv = TypeDescriptor.GetConverter(t);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -17,7 +17,6 @@ using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
@@ -26,15 +25,12 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public Color[] Palette { get; }
|
||||
public byte[] Data { get; }
|
||||
public SpriteFrameType Type { get; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public Color[] Palette { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
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)
|
||||
{
|
||||
if (!Verify(s))
|
||||
@@ -42,8 +38,9 @@ namespace OpenRA.FileFormats
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
var isPaletted = false;
|
||||
var is24Bit = false;
|
||||
var data = new List<byte>();
|
||||
Type = SpriteFrameType.Rgba32;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -68,12 +65,14 @@ namespace OpenRA.FileFormats
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
if (IsPaletted(bitDepth, colorType))
|
||||
Type = SpriteFrameType.Indexed8;
|
||||
else if (colorType == PngColorType.Color)
|
||||
Type = SpriteFrameType.Rgb24;
|
||||
isPaletted = IsPaletted(bitDepth, colorType);
|
||||
is24Bit = colorType == PngColorType.Color;
|
||||
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
var dataLength = Width * Height;
|
||||
if (!isPaletted)
|
||||
dataLength *= 4;
|
||||
|
||||
Data = new byte[dataLength];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
@@ -134,28 +133,39 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var ds = new InflaterInputStream(ns))
|
||||
{
|
||||
var pxStride = PixelStride;
|
||||
var rowStride = Width * pxStride;
|
||||
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
|
||||
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++)
|
||||
{
|
||||
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
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
||||
if (isPaletted && Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
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)
|
||||
{
|
||||
var expectLength = width * height;
|
||||
@@ -175,46 +185,11 @@ namespace OpenRA.FileFormats
|
||||
if (data.Length != expectLength)
|
||||
throw new InvalidDataException("Input data does not match expected length");
|
||||
|
||||
Type = type;
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
Palette = palette;
|
||||
Data = data;
|
||||
|
||||
if (embeddedData != null)
|
||||
EmbeddedData = embeddedData;
|
||||
@@ -299,8 +274,9 @@ namespace OpenRA.FileFormats
|
||||
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||
header.WriteByte(8); // Bit depth
|
||||
|
||||
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color :
|
||||
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha;
|
||||
var colorType = Palette != null
|
||||
? PngColorType.Indexed | PngColorType.Color
|
||||
: PngColorType.Color | PngColorType.Alpha;
|
||||
header.WriteByte((byte)colorType);
|
||||
|
||||
header.WriteByte(0); // Compression
|
||||
@@ -310,7 +286,7 @@ namespace OpenRA.FileFormats
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
var alphaPalette = false;
|
||||
bool alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
@@ -342,12 +318,12 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var rowStride = Width * PixelStride;
|
||||
var stride = Width * (Palette != null ? 1 : 4);
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * rowStride, rowStride);
|
||||
compressed.Write(Data, y * stride, stride);
|
||||
}
|
||||
|
||||
compressed.Flush();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.FileFormats
|
||||
public ReplayMetadata(GameInformation info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
GameInfo = info;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace OpenRA.FileFormats
|
||||
// Read version
|
||||
var version = fs.ReadInt32();
|
||||
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)
|
||||
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
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<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
|
||||
readonly string modID;
|
||||
@@ -63,16 +63,19 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
// Raw directories are the easiest and one of the most common cases, so try these first
|
||||
var resolvedPath = Platform.ResolvePath(filename);
|
||||
if (!resolvedPath.Contains('|') && Directory.Exists(resolvedPath))
|
||||
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Children of another package require special handling
|
||||
if (TryGetPackageContaining(filename, out var parent, out var subPath))
|
||||
IReadOnlyPackage parent;
|
||||
string subPath = null;
|
||||
if (TryGetPackageContaining(filename, out parent, out subPath))
|
||||
return parent.OpenPackage(subPath, this);
|
||||
|
||||
// Try and open it normally
|
||||
IReadOnlyPackage package;
|
||||
var stream = Open(filename);
|
||||
if (TryParsePackage(stream, filename, out var package))
|
||||
if (TryParsePackage(stream, filename, out package))
|
||||
return package;
|
||||
|
||||
// No package loaders took ownership of the stream, so clean it up
|
||||
@@ -94,8 +97,9 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
name = name.Substring(1);
|
||||
|
||||
if (!installedMods.TryGetValue(name, out var mod))
|
||||
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(name, out mod))
|
||||
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
|
||||
|
||||
package = mod.Package;
|
||||
modPackages.Add(package);
|
||||
@@ -104,7 +108,7 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
package = OpenPackage(name);
|
||||
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);
|
||||
@@ -118,7 +122,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Mount(IReadOnlyPackage package, string explicitName = null)
|
||||
{
|
||||
if (mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (mountedPackages.TryGetValue(package, out mountCount))
|
||||
{
|
||||
// Package is already mounted
|
||||
// Increment the mount count and bump up the file loading priority
|
||||
@@ -144,7 +149,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool Unmount(IReadOnlyPackage package)
|
||||
{
|
||||
if (!mountedPackages.TryGetValue(package, out var mountCount))
|
||||
var mountCount = 0;
|
||||
if (!mountedPackages.TryGetValue(package, out mountCount))
|
||||
return false;
|
||||
|
||||
if (--mountCount <= 0)
|
||||
@@ -197,13 +203,17 @@ namespace OpenRA.FileSystem
|
||||
var package = fileIndex[filename]
|
||||
.LastOrDefault(x => x.Contains(filename));
|
||||
|
||||
return package?.GetStream(filename);
|
||||
if (package != null)
|
||||
return package.GetStream(filename);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream Open(string filename)
|
||||
{
|
||||
if (!TryOpen(filename, out var s))
|
||||
throw new FileNotFoundException($"File not found: {filename}", filename);
|
||||
Stream s;
|
||||
if (!TryOpen(filename, out s))
|
||||
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -228,7 +238,8 @@ namespace OpenRA.FileSystem
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
{
|
||||
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
|
||||
if (s != null)
|
||||
@@ -263,9 +274,12 @@ namespace OpenRA.FileSystem
|
||||
{
|
||||
var explicitSplit = filename.IndexOf('|');
|
||||
if (explicitSplit > 0)
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
{
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return fileIndex.ContainsKey(filename);
|
||||
}
|
||||
@@ -279,7 +293,8 @@ namespace OpenRA.FileSystem
|
||||
if (explicitSplit < 0)
|
||||
return false;
|
||||
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
|
||||
IReadOnlyPackage explicitPackage;
|
||||
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
|
||||
return false;
|
||||
|
||||
if (installedMods[modID].Package == explicitPackage)
|
||||
@@ -295,7 +310,7 @@ namespace OpenRA.FileSystem
|
||||
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||
{
|
||||
var explicitSplit = path.IndexOf('|');
|
||||
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||
if (explicitSplit > 0)
|
||||
{
|
||||
var parent = path.Substring(0, explicitSplit);
|
||||
var filename = path.Substring(explicitSplit + 1);
|
||||
@@ -306,7 +321,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||
{
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
|
||||
Manifest mod;
|
||||
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
|
||||
return null;
|
||||
|
||||
if (!(mod.Package is Folder))
|
||||
@@ -322,28 +338,6 @@ namespace OpenRA.FileSystem
|
||||
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||
}
|
||||
|
||||
public static string ResolveCaseInsensitivePath(string path)
|
||||
{
|
||||
var resolved = Path.GetPathRoot(path);
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
|
||||
foreach (var name in path.Substring(resolved.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
// Filter out paths of the form /foo/bar/./baz
|
||||
if (name == ".")
|
||||
continue;
|
||||
|
||||
resolved = Directory.GetFileSystemEntries(resolved).FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (resolved == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public string GetPrefix(IReadOnlyPackage package)
|
||||
{
|
||||
return explicitMounts.ContainsValue(package) ? explicitMounts.First(f => f.Value == package).Key : null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
@@ -27,17 +26,16 @@ namespace OpenRA.FileSystem
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public string Name => path;
|
||||
public string Name { get { return path; } }
|
||||
|
||||
public IEnumerable<string> Contents
|
||||
{
|
||||
get
|
||||
{
|
||||
// Order may vary on different file systems and it matters for hashing.
|
||||
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
|
||||
.Concat(Directory.GetDirectories(path))
|
||||
.Select(Path.GetFileName)
|
||||
.OrderBy(f => f);
|
||||
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
|
||||
yield return Path.GetFileName(filename);
|
||||
foreach (var filename in Directory.GetDirectories(path))
|
||||
yield return Path.GetFileName(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,15 +58,17 @@ namespace OpenRA.FileSystem
|
||||
return new Folder(resolvedPath);
|
||||
|
||||
// Zip files loaded from Folders (and *only* from Folders) can be read-write
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
|
||||
IReadWritePackage readWritePackage;
|
||||
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
|
||||
return readWritePackage;
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -14,12 +14,13 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileSystem
|
||||
{
|
||||
public class ZipFileLoader : IPackageLoader
|
||||
{
|
||||
static readonly uint ZipSignature = 0x04034b50;
|
||||
static readonly string[] Extensions = { ".zip", ".oramap" };
|
||||
|
||||
class ReadOnlyZipFile : IReadOnlyPackage
|
||||
{
|
||||
@@ -66,7 +67,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
pkg?.Close();
|
||||
if (pkg != null)
|
||||
pkg.Close();
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||
@@ -80,11 +82,12 @@ namespace OpenRA.FileSystem
|
||||
return new ZipFolder(this, filename);
|
||||
|
||||
// Other package types can be loaded normally
|
||||
IReadOnlyPackage package;
|
||||
var s = GetStream(filename);
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(s, filename, out var package))
|
||||
if (context.TryParsePackage(s, filename, out package))
|
||||
return package;
|
||||
|
||||
s.Dispose();
|
||||
@@ -141,8 +144,8 @@ namespace OpenRA.FileSystem
|
||||
|
||||
sealed class ZipFolder : IReadOnlyPackage
|
||||
{
|
||||
public string Name => path;
|
||||
public ReadOnlyZipFile Parent { get; }
|
||||
public string Name { get { return path; } }
|
||||
public ReadOnlyZipFile Parent { get; private set; }
|
||||
readonly string path;
|
||||
|
||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||
@@ -206,10 +209,7 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
var readSignature = s.ReadUInt32();
|
||||
s.Position -= 4;
|
||||
|
||||
if (readSignature != ZipSignature)
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
@@ -221,13 +221,10 @@ namespace OpenRA.FileSystem
|
||||
|
||||
public static bool TryParseReadWritePackage(string filename, out IReadWritePackage package)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
if (!Extensions.Any(e => filename.EndsWith(e, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
if (s.ReadUInt32() != ZipSignature)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new ReadWriteZipFile(filename);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
||||
|
||||
public class Fonts : IGlobalModData
|
||||
{
|
||||
[FieldLoader.LoadUsing(nameof(LoadFonts))]
|
||||
[FieldLoader.LoadUsing("LoadFonts")]
|
||||
public readonly Dictionary<string, FontData> FontList;
|
||||
|
||||
static object LoadFonts(MiniYaml y)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -16,12 +16,12 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Server;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
@@ -29,9 +29,8 @@ namespace OpenRA
|
||||
{
|
||||
public static class Game
|
||||
{
|
||||
[TranslationReference("filename")]
|
||||
const string SavedScreenshot = "notification-saved-screenshot";
|
||||
|
||||
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 static InstalledMods Mods { get; private set; }
|
||||
@@ -39,11 +38,9 @@ namespace OpenRA
|
||||
|
||||
public static ModData ModData;
|
||||
public static Settings Settings;
|
||||
public static CursorManager Cursor;
|
||||
public static ICursor Cursor;
|
||||
public static bool HideCursor;
|
||||
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
internal static OrderManager OrderManager;
|
||||
static Server.Server server;
|
||||
@@ -52,94 +49,68 @@ namespace OpenRA
|
||||
|
||||
public static Renderer Renderer;
|
||||
public static Sound Sound;
|
||||
public static bool HasInputFocus = false;
|
||||
|
||||
public static string EngineVersion { get; private set; }
|
||||
public static LocalPlayerProfile LocalPlayerProfile;
|
||||
|
||||
static Task discoverNat;
|
||||
static bool takeScreenshot = false;
|
||||
static Benchmark benchmark = null;
|
||||
|
||||
public static event Action OnShellmapLoaded = () => { };
|
||||
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
{
|
||||
var newConnection = new NetworkConnection(endpoint);
|
||||
var connection = new NetworkConnection(host, port);
|
||||
if (recordReplay)
|
||||
newConnection.StartRecording(() => { return TimestampedFilename(); });
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
|
||||
var om = new OrderManager(newConnection);
|
||||
var om = new OrderManager(host, port, password, connection);
|
||||
JoinInner(om);
|
||||
CurrentServerSettings.Password = password;
|
||||
CurrentServerSettings.Target = endpoint;
|
||||
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager, password, newConnection);
|
||||
|
||||
return om;
|
||||
}
|
||||
|
||||
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
|
||||
static string TimestampedFilename(bool includemilliseconds = false)
|
||||
{
|
||||
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
|
||||
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
return "OpenRA-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
static void JoinInner(OrderManager om)
|
||||
{
|
||||
// Refresh TextNotificationsManager before the game starts.
|
||||
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();
|
||||
|
||||
if (OrderManager != null) OrderManager.Dispose();
|
||||
OrderManager = om;
|
||||
lastConnectionState = ConnectionState.PreConnecting;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
}
|
||||
|
||||
public static void JoinReplay(string replayFile)
|
||||
{
|
||||
JoinInner(new OrderManager(new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager(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
|
||||
});
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
|
||||
}
|
||||
|
||||
// More accurate replacement for Environment.TickCount
|
||||
static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
|
||||
public static long RunTime => Stopwatch.ElapsedMilliseconds;
|
||||
static Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
|
||||
|
||||
public static int RenderFrame = 0;
|
||||
public static int NetFrameNumber => OrderManager.NetFrameNumber;
|
||||
public static int LocalTick => OrderManager.LocalFrameNumber;
|
||||
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
|
||||
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
|
||||
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
public static event Action<OrderManager, string, NetworkConnection> ConnectionStateChanged = (om, pass, conn) => { };
|
||||
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
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(string host, int port)
|
||||
{
|
||||
OnRemoteDirectConnect(endpoint);
|
||||
OnRemoteDirectConnect(host, port);
|
||||
}
|
||||
|
||||
// Hacky workaround for orderManager visibility
|
||||
@@ -182,7 +153,8 @@ namespace OpenRA
|
||||
internal static void StartGame(string mapUID, WorldType type)
|
||||
{
|
||||
// Dispose of the old world before creating a new one.
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
|
||||
Cursor.SetCursor(null);
|
||||
BeforeGameStart();
|
||||
@@ -191,7 +163,6 @@ namespace OpenRA
|
||||
|
||||
using (new PerfTimer("PrepareMap"))
|
||||
map = ModData.PrepareMap(mapUID);
|
||||
|
||||
using (new PerfTimer("NewWorld"))
|
||||
OrderManager.World = new World(ModData, map, OrderManager, type);
|
||||
|
||||
@@ -199,13 +170,11 @@ namespace OpenRA
|
||||
|
||||
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
using (new PerfTimer("LoadComplete"))
|
||||
OrderManager.World.LoadComplete(worldRenderer);
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
|
||||
if (OrderManager.GameStarted)
|
||||
@@ -214,44 +183,27 @@ namespace OpenRA
|
||||
Ui.MouseFocusWidget = null;
|
||||
Ui.KeyboardFocusWidget = null;
|
||||
|
||||
OrderManager.LocalFrameNumber = 0;
|
||||
OrderManager.LastTickTime = RunTime;
|
||||
OrderManager.StartGame();
|
||||
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.
|
||||
// - All the temporary garbage created during loading can be collected.
|
||||
// - Live objects are likely to live for the length of the game or longer,
|
||||
// thus promoting them into a higher generation is not an issue.
|
||||
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
|
||||
// - A loading screen is visible, so a delay won't matter to the user.
|
||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
public static void RestartGame()
|
||||
{
|
||||
var replay = OrderManager.Connection as ReplayConnection;
|
||||
var replayName = replay?.Filename;
|
||||
var replayName = replay != null ? replay.Filename : null;
|
||||
var lobbyInfo = OrderManager.LobbyInfo;
|
||||
|
||||
// Reseed the RNG so this isn't an exact repeat of the last game
|
||||
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[]
|
||||
{
|
||||
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"),
|
||||
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
|
||||
Order.Command("startgame")
|
||||
};
|
||||
|
||||
@@ -280,7 +232,7 @@ namespace OpenRA
|
||||
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
|
||||
}
|
||||
|
||||
public static bool IsHost
|
||||
@@ -299,47 +251,40 @@ namespace OpenRA
|
||||
|
||||
public static void InitializeSettings(Arguments args)
|
||||
{
|
||||
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
|
||||
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
|
||||
}
|
||||
|
||||
public static RunStatus InitializeAndRun(string[] args)
|
||||
{
|
||||
Initialize(new Arguments(args));
|
||||
|
||||
// Proactively collect memory during loading to reduce peak memory.
|
||||
GC.Collect();
|
||||
return Run();
|
||||
}
|
||||
|
||||
static void Initialize(Arguments args)
|
||||
{
|
||||
var engineDirArg = args.GetValue("Engine.EngineDir", null);
|
||||
if (!string.IsNullOrEmpty(engineDirArg))
|
||||
Platform.OverrideEngineDir(engineDirArg);
|
||||
|
||||
var supportDirArg = args.GetValue("Engine.SupportDir", null);
|
||||
if (!string.IsNullOrEmpty(supportDirArg))
|
||||
if (supportDirArg != null)
|
||||
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
|
||||
try
|
||||
{
|
||||
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
|
||||
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (string.IsNullOrEmpty(EngineVersion))
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine($"Engine version is {EngineVersion}");
|
||||
Console.WriteLine($"Runtime: {Platform.RuntimeVersion}");
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
|
||||
// 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
|
||||
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)))
|
||||
{
|
||||
explicitModPaths = new[] { modID };
|
||||
@@ -355,7 +300,6 @@ namespace OpenRA
|
||||
Log.AddChannel("graphics", "graphics.log");
|
||||
Log.AddChannel("geoip", "geoip.log");
|
||||
Log.AddChannel("nat", "nat.log");
|
||||
Log.AddChannel("client", "client.log");
|
||||
|
||||
var platforms = new[] { Settings.Game.Platform, "Default", null };
|
||||
foreach (var p in platforms)
|
||||
@@ -366,18 +310,10 @@ namespace OpenRA
|
||||
Settings.Game.Platform = p;
|
||||
try
|
||||
{
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
var loader = new AssemblyLoader(rendererPath);
|
||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
|
||||
#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 = System.Reflection.Assembly.LoadFile(rendererPath);
|
||||
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
#endif
|
||||
|
||||
if (platformType == null)
|
||||
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
||||
|
||||
@@ -389,51 +325,54 @@ namespace OpenRA
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("graphics", $"{e}");
|
||||
Log.Write("graphics", "{0}", e);
|
||||
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
|
||||
|
||||
Renderer?.Dispose();
|
||||
if (Renderer != null)
|
||||
Renderer.Dispose();
|
||||
|
||||
Sound?.Dispose();
|
||||
if (Sound != null)
|
||||
Sound.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Nat.Initialize();
|
||||
GeoIP.Initialize();
|
||||
|
||||
if (Settings.Server.DiscoverNatDevices)
|
||||
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
|
||||
|
||||
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
|
||||
var modSearchPaths = modSearchArg != null ?
|
||||
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
|
||||
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
||||
new[] { Path.Combine(".", "mods") };
|
||||
|
||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||
Console.WriteLine("Internal mods:");
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
|
||||
|
||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
if (modID != null && Mods.TryGetValue(modID, out _))
|
||||
Manifest currentMod;
|
||||
if (modID != null && Mods.TryGetValue(modID, out currentMod))
|
||||
{
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", null);
|
||||
var launchArgs = new List<string>();
|
||||
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
|
||||
|
||||
// Sanitize input from platform-specific launchers
|
||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
if (launchPath.First() == '"' && launchPath.Last() == '"')
|
||||
launchPath = launchPath.Substring(1, launchPath.Length - 2);
|
||||
|
||||
// Metadata registration requires an explicit launch path
|
||||
if (launchPath != null)
|
||||
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
|
||||
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
|
||||
|
||||
ExternalMods.ClearInvalidRegistrations(ModRegistration.User);
|
||||
ExternalMod activeMod;
|
||||
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
|
||||
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
|
||||
}
|
||||
|
||||
Console.WriteLine("External mods:");
|
||||
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);
|
||||
}
|
||||
@@ -442,17 +381,20 @@ namespace OpenRA
|
||||
{
|
||||
// Clear static state if we have switched mods
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = (om, p, conn) => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
OnRemoteDirectConnect = (a, b) => { };
|
||||
delayedActions = new ActionQueue();
|
||||
|
||||
Ui.ResetAll();
|
||||
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
worldRenderer = null;
|
||||
server?.Shutdown();
|
||||
OrderManager?.Dispose();
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
|
||||
if (ModData != null)
|
||||
{
|
||||
@@ -466,56 +408,84 @@ namespace OpenRA
|
||||
throw new InvalidOperationException("Game.Mod argument missing.");
|
||||
|
||||
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();
|
||||
|
||||
ModData = new ModData(Mods[mod], Mods, true);
|
||||
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
|
||||
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
|
||||
|
||||
if (!ModData.LoadScreen.BeforeLoad())
|
||||
return;
|
||||
|
||||
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
||||
Renderer.InitializeFonts(ModData);
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
ModData.InitializeLoaders(ModData.DefaultFileSystem);
|
||||
Renderer.InitializeFonts(ModData);
|
||||
|
||||
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
|
||||
Renderer.InitializeDepthBuffer(grid);
|
||||
|
||||
Cursor?.Dispose();
|
||||
Cursor = new CursorManager(ModData.CursorProvider);
|
||||
if (Cursor != null)
|
||||
Cursor.Dispose();
|
||||
|
||||
var metadata = ModData.Manifest.Metadata;
|
||||
if (!string.IsNullOrEmpty(metadata.WindowTitle))
|
||||
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
|
||||
if (Settings.Graphics.HardwareCursors)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cursor = new HardwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
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);
|
||||
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
}
|
||||
else
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
|
||||
PerfHistory.Items["render"].HasNormalTick = false;
|
||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_world"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_widgets"].HasNormalTick = false;
|
||||
PerfHistory.Items["render_flip"].HasNormalTick = false;
|
||||
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
|
||||
|
||||
JoinLocal();
|
||||
|
||||
try
|
||||
{
|
||||
if (discoverNat != null)
|
||||
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);
|
||||
}
|
||||
|
||||
public static void LoadEditor(string mapUid)
|
||||
{
|
||||
JoinLocal();
|
||||
StartGame(mapUid, WorldType.Editor);
|
||||
}
|
||||
|
||||
public static void LoadShellMap()
|
||||
{
|
||||
var shellmap = ChooseShellmap();
|
||||
|
||||
using (new PerfTimer("StartGame"))
|
||||
{
|
||||
StartGame(shellmap, WorldType.Shellmap);
|
||||
@@ -539,15 +509,8 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = mod.LaunchPath;
|
||||
var args = launchArguments != null ? mod.LaunchArgs.Append(launchArguments) : mod.LaunchArgs;
|
||||
if (modLaunchWrapper != null)
|
||||
{
|
||||
path = modLaunchWrapper;
|
||||
args = new[] { mod.LaunchPath }.Concat(args);
|
||||
}
|
||||
|
||||
var p = Process.Start(path, args.Select(a => "\"" + a + "\"").JoinWith(" "));
|
||||
var p = Process.Start(mod.LaunchPath, args.Select(a => "\"" + a + "\"").JoinWith(" "));
|
||||
if (p == null || p.HasExited)
|
||||
onFailed();
|
||||
else
|
||||
@@ -570,7 +533,8 @@ namespace OpenRA
|
||||
// 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.
|
||||
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 RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
|
||||
|
||||
@@ -579,15 +543,15 @@ namespace OpenRA
|
||||
using (new PerfTimer("Renderer.SaveScreenshot"))
|
||||
{
|
||||
var mod = ModData.Manifest.Metadata;
|
||||
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var filename = TimestampedFilename(true);
|
||||
var path = Path.Combine(directory, string.Concat(filename, ".png"));
|
||||
Log.Write("debug", "Taking screenshot " + path);
|
||||
|
||||
Renderer.SaveScreenshot(path);
|
||||
TextNotificationsManager.Debug(ModData.Translation.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||
Renderer.Context.SaveScreenshot(path);
|
||||
Debug("Saved screenshot " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,29 +561,48 @@ namespace OpenRA
|
||||
|
||||
var world = orderManager.World;
|
||||
|
||||
if (Ui.LastTickTime.ShouldAdvance(tick))
|
||||
var uiTickDelta = tick - Ui.LastTickTime;
|
||||
if (uiTickDelta >= Timestep)
|
||||
{
|
||||
Ui.LastTickTime.AdvanceTickTime(tick);
|
||||
Sync.RunUnsynced(world, Ui.Tick);
|
||||
// Explained below for the world tick calculation
|
||||
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
|
||||
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
|
||||
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.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"))
|
||||
{
|
||||
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();
|
||||
|
||||
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
|
||||
|
||||
if (world == null)
|
||||
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);
|
||||
});
|
||||
@@ -628,24 +611,27 @@ namespace OpenRA
|
||||
|
||||
PerfHistory.Tick();
|
||||
}
|
||||
else if (orderManager.NetFrameNumber == 0)
|
||||
orderManager.LastTickTime = RunTime;
|
||||
|
||||
// Wait until we have done our first world Tick before TickRendering
|
||||
if (orderManager.LocalFrameNumber > 0)
|
||||
Sync.RunUnsynced(world, () => world.TickRender(worldRenderer));
|
||||
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
|
||||
}
|
||||
|
||||
benchmark?.Tick(LocalTick);
|
||||
if (benchmark != null)
|
||||
benchmark.Tick(LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
static void LogicTick()
|
||||
{
|
||||
PerformDelayedActions();
|
||||
delayedActions.PerformActions(RunTime);
|
||||
|
||||
if (OrderManager.Connection is NetworkConnection nc && nc.ConnectionState != lastConnectionState)
|
||||
if (OrderManager.Connection.ConnectionState != lastConnectionState)
|
||||
{
|
||||
lastConnectionState = nc.ConnectionState;
|
||||
ConnectionStateChanged(OrderManager, null, nc);
|
||||
lastConnectionState = OrderManager.Connection.ConnectionState;
|
||||
ConnectionStateChanged(OrderManager);
|
||||
}
|
||||
|
||||
InnerLogicTick(OrderManager);
|
||||
@@ -653,11 +639,6 @@ namespace OpenRA
|
||||
InnerLogicTick(worldRenderer.World.OrderManager);
|
||||
}
|
||||
|
||||
public static void PerformDelayedActions()
|
||||
{
|
||||
delayedActions.PerformActions(RunTime);
|
||||
}
|
||||
|
||||
public static void TakeScreenshot()
|
||||
{
|
||||
takeScreenshot = true;
|
||||
@@ -669,39 +650,24 @@ namespace OpenRA
|
||||
{
|
||||
++RenderFrame;
|
||||
|
||||
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
||||
using (new PerfSample("render_prepare"))
|
||||
{
|
||||
Renderer.WorldModelRenderer.BeginFrame();
|
||||
|
||||
// World rendering is disabled while the loading screen is displayed
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
{
|
||||
worldRenderer.Viewport.Tick();
|
||||
worldRenderer.PrepareRenderables();
|
||||
}
|
||||
|
||||
Ui.PrepareRenderables();
|
||||
Renderer.WorldModelRenderer.EndFrame();
|
||||
}
|
||||
|
||||
// worldRenderer is null during the initial install/download screen
|
||||
// World rendering is disabled while the loading screen is displayed
|
||||
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
if (worldRenderer != null)
|
||||
{
|
||||
Renderer.BeginWorld(worldRenderer.Viewport.Rectangle);
|
||||
Renderer.BeginFrame(worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.Zoom);
|
||||
Sound.SetListenerPosition(worldRenderer.Viewport.CenterPosition);
|
||||
using (new PerfSample("render_world"))
|
||||
|
||||
// Use worldRenderer.World instead of OrderManager.World to avoid a rendering mismatch while processing orders
|
||||
if (!worldRenderer.World.IsLoadingGameSave)
|
||||
worldRenderer.Draw();
|
||||
}
|
||||
else
|
||||
Renderer.BeginFrame(int2.Zero, 1f);
|
||||
|
||||
using (new PerfSample("render_widgets"))
|
||||
{
|
||||
Renderer.BeginUI();
|
||||
|
||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||
worldRenderer.DrawAnnotations();
|
||||
Renderer.WorldModelRenderer.BeginFrame();
|
||||
Ui.PrepareRenderables();
|
||||
Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
Ui.Draw();
|
||||
|
||||
@@ -729,10 +695,8 @@ namespace OpenRA
|
||||
|
||||
PerfHistory.Items["render"].Tick();
|
||||
PerfHistory.Items["batches"].Tick();
|
||||
PerfHistory.Items["render_world"].Tick();
|
||||
PerfHistory.Items["render_widgets"].Tick();
|
||||
PerfHistory.Items["render_flip"].Tick();
|
||||
PerfHistory.Items["terrain_lighting"].Tick();
|
||||
}
|
||||
|
||||
static void Loop()
|
||||
@@ -778,24 +742,16 @@ namespace OpenRA
|
||||
var nextLogic = RunTime;
|
||||
var nextRender = RunTime;
|
||||
var forcedNextRender = RunTime;
|
||||
var renderBeforeNextTick = false;
|
||||
|
||||
while (state == RunStatus.Running)
|
||||
{
|
||||
var logicInterval = Ui.Timestep;
|
||||
var logicWorld = worldRenderer?.World;
|
||||
|
||||
// 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 logic updates. Timestep = 0 means the game is paused
|
||||
// but we still call LogicTick() because it handles pausing internally.
|
||||
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Timestep;
|
||||
|
||||
// Ideal time between screen updates
|
||||
var renderInterval = logicInterval;
|
||||
if (!Settings.Graphics.CapFramerateToGameFps)
|
||||
{
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
renderInterval = 1000 / maxFramerate;
|
||||
}
|
||||
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
|
||||
var renderInterval = 1000 / maxFramerate;
|
||||
|
||||
// Tick as fast as possible while restoring game saves, capping rendering at 5 FPS
|
||||
if (OrderManager.World != null && OrderManager.World.IsLoadingGameSave)
|
||||
@@ -814,9 +770,9 @@ namespace OpenRA
|
||||
var nextUpdate = Math.Min(nextLogic, nextRender);
|
||||
if (now >= nextUpdate)
|
||||
{
|
||||
var forceRender = renderBeforeNextTick || now >= forcedNextRender;
|
||||
var forceRender = now >= forcedNextRender;
|
||||
|
||||
if (now >= nextLogic && !renderBeforeNextTick)
|
||||
if (now >= nextLogic)
|
||||
{
|
||||
nextLogic += logicInterval;
|
||||
|
||||
@@ -824,12 +780,13 @@ namespace OpenRA
|
||||
|
||||
// Force at least one render per tick during regular gameplay
|
||||
if (OrderManager.World != null && !OrderManager.World.IsLoadingGameSave && !OrderManager.World.IsReplay)
|
||||
renderBeforeNextTick = true;
|
||||
forceRender = true;
|
||||
}
|
||||
|
||||
var haveSomeTimeUntilNextLogic = now < nextLogic;
|
||||
var isTimeToRender = now >= nextRender;
|
||||
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
|
||||
|
||||
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
|
||||
{
|
||||
nextRender = now + renderInterval;
|
||||
|
||||
@@ -842,20 +799,6 @@ namespace OpenRA
|
||||
forcedNextRender = now + maxRenderInterval;
|
||||
|
||||
RenderTick();
|
||||
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
|
||||
@@ -878,10 +821,12 @@ namespace OpenRA
|
||||
finally
|
||||
{
|
||||
// Ensure that the active replay is properly saved
|
||||
OrderManager?.Dispose();
|
||||
if (OrderManager != null)
|
||||
OrderManager.Dispose();
|
||||
}
|
||||
|
||||
worldRenderer?.Dispose();
|
||||
if (worldRenderer != null)
|
||||
worldRenderer.Dispose();
|
||||
ModData.Dispose();
|
||||
ChromeProvider.Deinitialize();
|
||||
|
||||
@@ -898,9 +843,25 @@ namespace OpenRA
|
||||
state = RunStatus.Success;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
OrderManager.World?.TraitDict.PrintReport();
|
||||
if (OrderManager.World != null)
|
||||
OrderManager.World.TraitDict.PrintReport();
|
||||
|
||||
OrderManager.Dispose();
|
||||
CloseServer();
|
||||
@@ -909,7 +870,8 @@ namespace OpenRA
|
||||
|
||||
public static void CloseServer()
|
||||
{
|
||||
server?.Shutdown();
|
||||
if (server != null)
|
||||
server.Shutdown();
|
||||
}
|
||||
|
||||
public static T CreateObject<T>(string name)
|
||||
@@ -917,19 +879,12 @@ namespace OpenRA
|
||||
return ModData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static ConnectionTarget CreateServer(ServerSettings settings)
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
{
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, false);
|
||||
}
|
||||
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
public static int CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
@@ -938,16 +893,9 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
// Always connect to local games using the same loopback connection
|
||||
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, false);
|
||||
|
||||
return server.GetEndpointForLocalConnection();
|
||||
return server.Port;
|
||||
}
|
||||
|
||||
public static bool IsCurrentWorld(World world)
|
||||
@@ -969,13 +917,16 @@ namespace OpenRA
|
||||
{
|
||||
var orders = new List<Order>
|
||||
{
|
||||
Order.Command("option gamespeed default"),
|
||||
Order.Command($"state {Session.ClientState.Ready}")
|
||||
Order.Command("option gamespeed {0}".F("default")),
|
||||
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)
|
||||
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
||||
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
|
||||
|
||||
CreateAndStartLocalServer(map.Uid, orders);
|
||||
}
|
||||
@@ -989,11 +940,4 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CurrentServerSettings
|
||||
{
|
||||
public static string Password;
|
||||
public static ConnectionTarget Target;
|
||||
public static ExternalMod ServerExternalMod;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -33,13 +33,11 @@ namespace OpenRA
|
||||
public DateTime EndTimeUtc;
|
||||
|
||||
/// <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 IList<Player> Players { get; }
|
||||
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
|
||||
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
|
||||
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;
|
||||
|
||||
@@ -76,7 +74,7 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException)
|
||||
{
|
||||
Log.Write("debug", $"GameInformation deserialized invalid MiniYaml:\n{data}");
|
||||
Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +87,7 @@ namespace OpenRA
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -98,10 +96,10 @@ namespace OpenRA
|
||||
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
||||
{
|
||||
if (runtimePlayer == null)
|
||||
throw new ArgumentNullException(nameof(runtimePlayer));
|
||||
throw new ArgumentNullException("runtimePlayer");
|
||||
|
||||
if (lobbyInfo == null)
|
||||
throw new ArgumentNullException(nameof(lobbyInfo));
|
||||
throw new ArgumentNullException("lobbyInfo");
|
||||
|
||||
// We don't care about spectators and map players
|
||||
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
||||
@@ -120,14 +118,11 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
Handicap = client.Handicap,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
|
||||
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
|
||||
Fingerprint = client.Fingerprint
|
||||
};
|
||||
|
||||
@@ -138,7 +133,9 @@ namespace OpenRA
|
||||
/// <summary>Gets the player information for the specified runtime player instance.</summary>
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out var player);
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
|
||||
return player;
|
||||
}
|
||||
@@ -161,14 +158,9 @@ namespace OpenRA
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
public int Handicap;
|
||||
|
||||
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomFaction;
|
||||
@@ -189,9 +181,6 @@ namespace OpenRA
|
||||
/// <summary>The time when this player won or lost the game.</summary>
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
|
||||
/// <summary>The frame at which this player disconnected.</summary>
|
||||
public int DisconnectFrame;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -23,7 +23,6 @@ namespace OpenRA
|
||||
public class ActorInfo
|
||||
{
|
||||
public const string AbstractActorPrefix = "^";
|
||||
public const char TraitInstanceSeparator = '@';
|
||||
|
||||
/// <summary>
|
||||
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
|
||||
@@ -33,7 +32,7 @@ namespace OpenRA
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
readonly TypeDictionary traits = new TypeDictionary();
|
||||
List<TraitInfo> constructOrderCache = null;
|
||||
List<ITraitInfo> constructOrderCache = null;
|
||||
|
||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||
{
|
||||
@@ -45,11 +44,7 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
|
||||
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
|
||||
var trait = LoadTraitInfo(creator, t.Key, t.Value);
|
||||
if (trait != null)
|
||||
traits.Add(trait);
|
||||
traits.Add(LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value));
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
{
|
||||
@@ -61,11 +56,11 @@ namespace OpenRA
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
throw new YamlException($"Actor type {name}: {e.Message}");
|
||||
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public ActorInfo(string name, params TraitInfo[] traitInfos)
|
||||
public ActorInfo(string name, params ITraitInfo[] traitInfos)
|
||||
{
|
||||
Name = name;
|
||||
foreach (var t in traitInfos)
|
||||
@@ -73,23 +68,14 @@ namespace OpenRA
|
||||
traits.TrimExcess();
|
||||
}
|
||||
|
||||
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}");
|
||||
|
||||
// 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
|
||||
var traitInstance = traitName.Split(TraitInstanceSeparator);
|
||||
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
throw new YamlException("Junk value `{0}` on trait node {1}"
|
||||
.F(my.Value, traitName));
|
||||
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
|
||||
try
|
||||
{
|
||||
if (traitInstance.Length > 1)
|
||||
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
|
||||
|
||||
FieldLoader.Load(info, my);
|
||||
}
|
||||
catch (FieldLoader.MissingFieldsException e)
|
||||
@@ -101,20 +87,19 @@ namespace OpenRA
|
||||
return info;
|
||||
}
|
||||
|
||||
public IEnumerable<TraitInfo> TraitsInConstructOrder()
|
||||
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
|
||||
{
|
||||
if (constructOrderCache != null)
|
||||
return constructOrderCache;
|
||||
|
||||
var source = traits.WithInterface<TraitInfo>().Select(i => new
|
||||
var source = traits.WithInterface<ITraitInfo>().Select(i => new
|
||||
{
|
||||
Trait = i,
|
||||
Type = i.GetType(),
|
||||
Dependencies = PrerequisitesOf(i).ToList(),
|
||||
OptionalDependencies = OptionalPrerequisitesOf(i).ToList()
|
||||
Dependencies = PrerequisitesOf(i).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 testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
|
||||
@@ -123,9 +108,7 @@ namespace OpenRA
|
||||
var more = unresolved.Where(u =>
|
||||
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.
|
||||
!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.
|
||||
!unresolved.Any(u1 => testResolve(d, u1.Type)))); // All matching traits that meet this dependency must be resolved first.
|
||||
|
||||
// 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.
|
||||
@@ -145,9 +128,7 @@ namespace OpenRA
|
||||
foreach (var u in unresolved)
|
||||
{
|
||||
var deps = u.Dependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var optDeps = u.OptionalDependencies.Where(d => !resolved.Exists(r => r.Type == d));
|
||||
var allDeps = string.Join(", ", deps.Select(o => o.ToString()).Concat(optDeps.Select(o => $"[{o}]")));
|
||||
exceptionString += $"{u.Type}: {{ {allDeps} }}\r\n";
|
||||
exceptionString += u.Type + ": { " + string.Join(", ", deps) + " }\r\n";
|
||||
}
|
||||
|
||||
throw new YamlException(exceptionString);
|
||||
@@ -157,7 +138,7 @@ namespace OpenRA
|
||||
return constructOrderCache;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
|
||||
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
|
||||
{
|
||||
return info
|
||||
.GetType()
|
||||
@@ -166,15 +147,6 @@ namespace OpenRA
|
||||
.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 T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
|
||||
namespace OpenRA.GameRules
|
||||
@@ -40,7 +41,8 @@ namespace OpenRA.GameRules
|
||||
|
||||
public void Load(IReadOnlyFileSystem fileSystem)
|
||||
{
|
||||
if (!fileSystem.TryOpen(Filename, out var stream))
|
||||
Stream stream;
|
||||
if (!fileSystem.TryOpen(Filename, out stream))
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -48,7 +50,8 @@ namespace OpenRA.GameRules
|
||||
Exists = true;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
if (loader.TryParseSound(stream, out var soundFormat))
|
||||
ISoundFormat soundFormat;
|
||||
if (loader.TryParseSound(stream, out soundFormat))
|
||||
{
|
||||
Length = (int)soundFormat.LengthInSeconds;
|
||||
soundFormat.Dispose();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -22,12 +22,12 @@ namespace OpenRA
|
||||
{
|
||||
public class Ruleset
|
||||
{
|
||||
public readonly ActorInfoDictionary Actors;
|
||||
public readonly IReadOnlyDictionary<string, ActorInfo> Actors;
|
||||
public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
|
||||
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
|
||||
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
|
||||
public readonly ITerrainInfo TerrainInfo;
|
||||
public readonly TileSet TileSet;
|
||||
public readonly SequenceProvider Sequences;
|
||||
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
|
||||
|
||||
@@ -37,16 +37,16 @@ namespace OpenRA
|
||||
IReadOnlyDictionary<string, SoundInfo> voices,
|
||||
IReadOnlyDictionary<string, SoundInfo> notifications,
|
||||
IReadOnlyDictionary<string, MusicInfo> music,
|
||||
ITerrainInfo terrainInfo,
|
||||
TileSet tileSet,
|
||||
SequenceProvider sequences,
|
||||
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
|
||||
{
|
||||
Actors = new ActorInfoDictionary(actors);
|
||||
Actors = actors;
|
||||
Weapons = weapons;
|
||||
Voices = voices;
|
||||
Notifications = notifications;
|
||||
Music = music;
|
||||
TerrainInfo = terrainInfo;
|
||||
TileSet = tileSet;
|
||||
Sequences = sequences;
|
||||
ModelSequences = modelSequences;
|
||||
|
||||
@@ -60,14 +60,15 @@ namespace OpenRA
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (weapon.Value.Projectile is IRulesetLoaded<WeaponInfo> projectileLoaded)
|
||||
var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
|
||||
if (projectileLoaded != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -75,13 +76,14 @@ namespace OpenRA
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (warhead is IRulesetLoaded<WeaponInfo> cacher)
|
||||
var cacher = warhead as IRulesetLoaded<WeaponInfo>;
|
||||
if (cacher != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -89,7 +91,7 @@ namespace OpenRA
|
||||
}
|
||||
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)
|
||||
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)
|
||||
@@ -131,7 +133,7 @@ namespace OpenRA
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
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,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -169,10 +171,10 @@ namespace OpenRA
|
||||
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
|
||||
{
|
||||
var dr = modData.DefaultRules;
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
var ts = modData.DefaultTileSets[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,
|
||||
@@ -190,7 +192,7 @@ namespace OpenRA
|
||||
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
|
||||
|
||||
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,
|
||||
k => new SoundInfo(k.Value));
|
||||
@@ -201,19 +203,19 @@ namespace OpenRA
|
||||
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
|
||||
k => new MusicInfo(k.Key, k.Value));
|
||||
|
||||
// TODO: Add support for merging custom terrain modifications
|
||||
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
|
||||
// TODO: Add support for merging custom tileset modifications
|
||||
var ts = modData.DefaultTileSets[tileSet];
|
||||
|
||||
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
|
||||
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
|
||||
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
|
||||
new SequenceProvider(fileSystem, modData, ts, mapSequences);
|
||||
|
||||
var modelSequences = dr.ModelSequences;
|
||||
if (mapModelSequences != null)
|
||||
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
|
||||
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)
|
||||
@@ -235,7 +237,7 @@ namespace OpenRA
|
||||
|
||||
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)
|
||||
@@ -248,7 +250,7 @@ namespace OpenRA
|
||||
{
|
||||
var traitName = traitNode.Key.Split('@')[0];
|
||||
var traitType = modData.ObjectCreator.FindType(traitName + "Info");
|
||||
if (traitType != null && traitType.GetInterface(nameof(ILobbyCustomRulesIgnore)) == null)
|
||||
if (traitType != null && traitType.GetInterface("ILobbyCustomRulesIgnore") == null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -33,28 +33,23 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
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"));
|
||||
}
|
||||
|
||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||
{
|
||||
var ret = new Dictionary<string, SoundPool>();
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
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)
|
||||
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 sp = new SoundPool(volumeModifier, interruptType, names);
|
||||
var sp = new SoundPool(volumeModifier, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
@@ -64,17 +59,13 @@ namespace OpenRA.GameRules
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public enum InterruptType { DoNotPlay, Interrupt, Overlap }
|
||||
public const InterruptType DefaultInterruptType = InterruptType.DoNotPlay;
|
||||
public readonly float VolumeModifier;
|
||||
public readonly InterruptType Type;
|
||||
readonly string[] clips;
|
||||
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;
|
||||
Type = interruptType;
|
||||
this.clips = clips;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -24,8 +24,8 @@ namespace OpenRA.GameRules
|
||||
public int[] DamageModifiers;
|
||||
public int[] InaccuracyModifiers;
|
||||
public int[] RangeModifiers;
|
||||
public WAngle Facing;
|
||||
public Func<WAngle> CurrentMuzzleFacing;
|
||||
public int Facing;
|
||||
public Func<int> CurrentMuzzleFacing;
|
||||
public WPos Source;
|
||||
public Func<WPos> CurrentSource;
|
||||
public Actor SourceActor;
|
||||
@@ -33,40 +33,6 @@ namespace OpenRA.GameRules
|
||||
public Target GuidedTarget;
|
||||
}
|
||||
|
||||
public class WarheadArgs
|
||||
{
|
||||
public WeaponInfo Weapon;
|
||||
public int[] DamageModifiers = Array.Empty<int>();
|
||||
public WPos? Source;
|
||||
public WRot ImpactOrientation;
|
||||
public WPos ImpactPosition;
|
||||
public Actor SourceActor;
|
||||
public Target WeaponTarget;
|
||||
|
||||
public WarheadArgs(ProjectileArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
ImpactPosition = args.PassiveTarget;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.GuidedTarget;
|
||||
}
|
||||
|
||||
// For places that only want to update some of the fields (usually DamageModifiers)
|
||||
public WarheadArgs(WarheadArgs args)
|
||||
{
|
||||
Weapon = args.Weapon;
|
||||
DamageModifiers = args.DamageModifiers;
|
||||
Source = args.Source;
|
||||
SourceActor = args.SourceActor;
|
||||
WeaponTarget = args.WeaponTarget;
|
||||
}
|
||||
|
||||
// Default empty constructor for callers that want to initialize fields themselves
|
||||
public WarheadArgs() { }
|
||||
}
|
||||
|
||||
public interface IProjectile : IEffect { }
|
||||
public interface IProjectileInfo { IProjectile Create(ProjectileArgs args); }
|
||||
|
||||
@@ -99,21 +65,12 @@ namespace OpenRA.GameRules
|
||||
[Desc("Number of shots in a single ammo magazine.")]
|
||||
public readonly int Burst = 1;
|
||||
|
||||
[Desc("Can this weapon target the attacker itself?")]
|
||||
public readonly bool CanTargetSelf = false;
|
||||
|
||||
[Desc("What types of targets are affected.")]
|
||||
public readonly BitSet<TargetableType> ValidTargets = new BitSet<TargetableType>("Ground", "Water");
|
||||
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
|
||||
|
||||
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
|
||||
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
|
||||
public readonly WDist AirThreshold = new WDist(128);
|
||||
|
||||
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
|
||||
"If multiple entries, their number needs to match Burst - 1.")]
|
||||
public readonly int[] BurstDelays = { 5 };
|
||||
@@ -124,18 +81,13 @@ namespace OpenRA.GameRules
|
||||
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
|
||||
public readonly bool TargetActorCenter = false;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadProjectile))]
|
||||
[FieldLoader.LoadUsing("LoadProjectile")]
|
||||
public readonly IProjectileInfo Projectile;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
|
||||
[FieldLoader.LoadUsing("LoadWarheads")]
|
||||
public readonly List<IWarhead> Warheads = new List<IWarhead>();
|
||||
|
||||
/// <summary>
|
||||
/// This constructor is used solely for documentation generation!
|
||||
/// </summary>
|
||||
public WeaponInfo() { }
|
||||
|
||||
public WeaponInfo(MiniYaml content)
|
||||
public WeaponInfo(string name, MiniYaml content)
|
||||
{
|
||||
// Resolve any weapon-level yaml inheritance or removals
|
||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||
@@ -145,13 +97,10 @@ namespace OpenRA.GameRules
|
||||
|
||||
static object LoadProjectile(MiniYaml yaml)
|
||||
{
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||
MiniYaml proj;
|
||||
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
|
||||
return null;
|
||||
|
||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||
if (ret == null)
|
||||
return null;
|
||||
|
||||
FieldLoader.Load(ret, proj);
|
||||
return ret;
|
||||
}
|
||||
@@ -162,9 +111,6 @@ namespace OpenRA.GameRules
|
||||
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
|
||||
{
|
||||
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||
if (ret == null)
|
||||
continue;
|
||||
|
||||
FieldLoader.Load(ret, node.Value);
|
||||
retList.Add(ret);
|
||||
}
|
||||
@@ -178,7 +124,7 @@ namespace OpenRA.GameRules
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
|
||||
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
|
||||
public bool IsValidAgainst(Target target, World world, Actor firedBy)
|
||||
{
|
||||
if (target.Type == TargetType.Actor)
|
||||
return IsValidAgainst(target.Actor, firedBy);
|
||||
@@ -188,10 +134,6 @@ namespace OpenRA.GameRules
|
||||
|
||||
if (target.Type == TargetType.Terrain)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
|
||||
if (dat > AirThreshold)
|
||||
return IsValidTarget(TargetTypeAir);
|
||||
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -209,58 +151,43 @@ namespace OpenRA.GameRules
|
||||
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
||||
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
if (!CanTargetSelf && victim == firedBy)
|
||||
return false;
|
||||
|
||||
var targetTypes = victim.GetEnabledTargetTypes();
|
||||
|
||||
return IsValidTarget(targetTypes);
|
||||
if (!IsValidTarget(targetTypes))
|
||||
return false;
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var warhead in Warheads)
|
||||
if (warhead.IsValidAgainst(victim, firedBy))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
||||
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
|
||||
{
|
||||
if (!victim.IsValid)
|
||||
if (!IsValidTarget(victim.TargetTypes))
|
||||
return false;
|
||||
|
||||
if (!CanTargetSelf && victim.Actor == firedBy)
|
||||
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
|
||||
return false;
|
||||
|
||||
return IsValidTarget(victim.TargetTypes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target.</summary>
|
||||
public void Impact(in Target target, WarheadArgs args)
|
||||
public void Impact(Target target, Actor firedBy, IEnumerable<int> damageModifiers)
|
||||
{
|
||||
var world = args.SourceActor.World;
|
||||
foreach (var warhead in Warheads)
|
||||
{
|
||||
if (warhead.Delay > 0)
|
||||
{
|
||||
// Lambdas can't use 'in' variables, so capture a copy for later
|
||||
var delayedTarget = target;
|
||||
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
|
||||
}
|
||||
var wh = warhead; // force the closure to bind to the current warhead
|
||||
|
||||
if (wh.Delay > 0)
|
||||
firedBy.World.AddFrameEndTask(w => w.Add(new DelayedImpact(wh.Delay, wh, target, firedBy, damageModifiers)));
|
||||
else
|
||||
warhead.DoImpact(target, args);
|
||||
wh.DoImpact(target, firedBy, damageModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
|
||||
public void Impact(in Target target, Actor firedBy)
|
||||
{
|
||||
// The impact will happen immediately at target.CenterPosition.
|
||||
var args = new WarheadArgs
|
||||
{
|
||||
Weapon = this,
|
||||
SourceActor = firedBy,
|
||||
WeaponTarget = target
|
||||
};
|
||||
|
||||
if (firedBy.OccupiesSpace != null)
|
||||
args.Source = firedBy.CenterPosition;
|
||||
|
||||
Impact(target, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,50 +10,27 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class GameSpeed
|
||||
{
|
||||
[TranslationReference]
|
||||
[FieldLoader.Require]
|
||||
public readonly string Name;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int Timestep;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly int OrderLatency;
|
||||
[Translate]
|
||||
public readonly string Name = "Default";
|
||||
public readonly int Timestep = 40;
|
||||
public readonly int OrderLatency = 3;
|
||||
}
|
||||
|
||||
public class GameSpeeds : IGlobalModData
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
public readonly string DefaultSpeed;
|
||||
|
||||
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
|
||||
[FieldLoader.LoadUsing("LoadSpeeds")]
|
||||
public readonly Dictionary<string, GameSpeed> Speeds;
|
||||
|
||||
static object LoadSpeeds(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, GameSpeed>();
|
||||
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
||||
if (speedsNode == null)
|
||||
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(", ")}");
|
||||
}
|
||||
}
|
||||
foreach (var node in y.Nodes)
|
||||
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Graphics
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<WAngle> facingFunc;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<bool> paused;
|
||||
|
||||
int frame;
|
||||
@@ -33,15 +33,15 @@ namespace OpenRA.Graphics
|
||||
Action tickFunc = () => { };
|
||||
|
||||
public Animation(World world, string name)
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
: this(world, name, () => 0) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
: this(world, name, () => 0, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
@@ -49,51 +49,25 @@ namespace OpenRA.Graphics
|
||||
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, in WVec offset, int zOffset, PaletteReference palette)
|
||||
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
|
||||
{
|
||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||
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);
|
||||
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||
true, rotation);
|
||||
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
|
||||
return new IRenderable[] { shadowRenderable, 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 Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
|
||||
{
|
||||
scale *= CurrentSequence.Scale;
|
||||
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 alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
|
||||
|
||||
if (CurrentSequence.ShadowStart >= 0)
|
||||
{
|
||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||
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);
|
||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||
}
|
||||
|
||||
return new IRenderable[] { imageRenderable };
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
|
||||
{
|
||||
var scale = CurrentSequence.Scale;
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
|
||||
var cb = CurrentSequence.Bounds;
|
||||
return Rectangle.FromLTRB(
|
||||
@@ -105,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
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)
|
||||
@@ -116,7 +90,7 @@ namespace OpenRA.Graphics
|
||||
int CurrentSequenceTickOrDefault()
|
||||
{
|
||||
const int DefaultTick = 40; // 25 fps == 40 ms
|
||||
return CurrentSequence?.Tick ?? DefaultTick;
|
||||
return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
|
||||
}
|
||||
|
||||
void PlaySequence(string sequenceName)
|
||||
@@ -165,7 +139,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
frame = CurrentSequence.Length - 1;
|
||||
tickFunc = () => { };
|
||||
after?.Invoke();
|
||||
if (after != null) after();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -35,21 +35,21 @@ namespace OpenRA.Graphics
|
||||
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 offset = OffsetFunc?.Invoke() ?? WVec.Zero;
|
||||
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
|
||||
|
||||
var z = ZOffset?.Invoke(center + offset) ?? 0;
|
||||
return Animation.Render(center, offset, z, pal);
|
||||
var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
|
||||
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 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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,292 +9,128 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[Flags]
|
||||
public enum PanelSides
|
||||
{
|
||||
Left = 1,
|
||||
Top = 2,
|
||||
Right = 4,
|
||||
Bottom = 8,
|
||||
Center = 16,
|
||||
|
||||
Edges = Left | Top | Right | Bottom,
|
||||
All = Edges | Center,
|
||||
}
|
||||
|
||||
public static class PanelSidesExts
|
||||
{
|
||||
public static bool HasSide(this PanelSides self, PanelSides m)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
return (self & m) == m;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChromeProvider
|
||||
{
|
||||
public class Collection
|
||||
struct Collection
|
||||
{
|
||||
public readonly string Image = null;
|
||||
public readonly string Image2x = null;
|
||||
public readonly string Image3x = null;
|
||||
|
||||
public readonly int[] PanelRegion = null;
|
||||
public readonly PanelSides PanelSides = PanelSides.All;
|
||||
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
|
||||
public string Src;
|
||||
public Dictionary<string, MappedImage> Regions;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, Collection> Collections => collections;
|
||||
static Dictionary<string, Collection> collections;
|
||||
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
|
||||
static Dictionary<string, Sheet> cachedSheets;
|
||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
|
||||
|
||||
static IReadOnlyFileSystem fileSystem;
|
||||
static float dpiScale = 1;
|
||||
|
||||
public static void Initialize(ModData modData)
|
||||
{
|
||||
Deinitialize();
|
||||
|
||||
// Load higher resolution images if available on HiDPI displays
|
||||
if (Game.Renderer != null)
|
||||
dpiScale = Game.Renderer.WindowScale;
|
||||
|
||||
fileSystem = modData.DefaultFileSystem;
|
||||
collections = new Dictionary<string, Collection>();
|
||||
cachedSheets = new Dictionary<string, (Sheet, int)>();
|
||||
cachedSheets = new Dictionary<string, Sheet>();
|
||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||
|
||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
||||
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
|
||||
|
||||
foreach (var c in chrome)
|
||||
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
||||
LoadCollection(c.Key, c.Value);
|
||||
LoadCollection(c.Key, c.Value);
|
||||
}
|
||||
|
||||
public static void Deinitialize()
|
||||
{
|
||||
if (cachedSheets != null)
|
||||
foreach (var sheet in cachedSheets.Values)
|
||||
sheet.Sheet.Dispose();
|
||||
sheet.Dispose();
|
||||
|
||||
collections = null;
|
||||
cachedSheets = null;
|
||||
cachedSprites = null;
|
||||
cachedPanelSprites = null;
|
||||
cachedCollectionSheets = null;
|
||||
}
|
||||
|
||||
public static void Save(string file)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collections)
|
||||
root.Add(new MiniYamlNode(kv.Key, SaveCollection(kv.Value)));
|
||||
|
||||
root.WriteToFile(file);
|
||||
}
|
||||
|
||||
static MiniYaml SaveCollection(Collection collection)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
foreach (var kv in collection.Regions)
|
||||
root.Add(new MiniYamlNode(kv.Key, kv.Value.Save(collection.Src)));
|
||||
|
||||
return new MiniYaml(collection.Src, root);
|
||||
}
|
||||
|
||||
static void LoadCollection(string name, MiniYaml yaml)
|
||||
{
|
||||
if (Game.ModData.LoadScreen != null)
|
||||
Game.ModData.LoadScreen.Display();
|
||||
|
||||
collections.Add(name, FieldLoader.Load<Collection>(yaml));
|
||||
}
|
||||
|
||||
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
|
||||
{
|
||||
// Outer cache avoids recalculating image names
|
||||
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
|
||||
var collection = new Collection()
|
||||
{
|
||||
var image = c.Image;
|
||||
var density = 1;
|
||||
if (dpiScale > 2 && !string.IsNullOrEmpty(c.Image3x))
|
||||
{
|
||||
image = c.Image3x;
|
||||
density = 3;
|
||||
}
|
||||
else if (dpiScale > 1 && !string.IsNullOrEmpty(c.Image2x))
|
||||
{
|
||||
image = c.Image2x;
|
||||
density = 2;
|
||||
}
|
||||
Src = yaml.Value,
|
||||
Regions = yaml.Nodes.ToDictionary(n => n.Key, n => new MappedImage(yaml.Value, n.Value))
|
||||
};
|
||||
|
||||
// Inner cache makes sure we share sheets between collections
|
||||
if (!cachedSheets.TryGetValue(image, out sheetDensity))
|
||||
{
|
||||
Sheet sheet;
|
||||
using (var stream = fileSystem.Open(image))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||
|
||||
sheetDensity = (sheet, density);
|
||||
cachedSheets.Add(image, sheetDensity);
|
||||
}
|
||||
|
||||
cachedCollectionSheets.Add(c, sheetDensity);
|
||||
}
|
||||
|
||||
return sheetDensity;
|
||||
collections.Add(name, collection);
|
||||
}
|
||||
|
||||
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))
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
return sprite;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
MappedImage mi;
|
||||
if (!collection.Regions.TryGetValue(imageName, out mi))
|
||||
return null;
|
||||
|
||||
if (!collection.Regions.TryGetValue(imageName, out var mi))
|
||||
return null;
|
||||
// Cached sheet
|
||||
Sheet sheet;
|
||||
if (cachedSheets.ContainsKey(mi.Src))
|
||||
sheet = cachedSheets[mi.Src];
|
||||
else
|
||||
{
|
||||
using (var stream = fileSystem.Open(mi.Src))
|
||||
sheet = new Sheet(SheetType.BGRA, stream);
|
||||
|
||||
cachedSheets.Add(mi.Src, sheet);
|
||||
}
|
||||
|
||||
// Cache the sprite
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
if (cachedCollection == null)
|
||||
{
|
||||
cachedCollection = new Dictionary<string, Sprite>();
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
|
||||
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
|
||||
var image = mi.GetImage(sheet);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
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))
|
||||
return null;
|
||||
|
||||
// Cached sprite
|
||||
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
|
||||
return cachedSprites;
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
return null;
|
||||
|
||||
Sprite[] sprites;
|
||||
if (collection.PanelRegion != null)
|
||||
{
|
||||
if (collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", $"Collection '{collectionName}' does not define a valid PanelRegion");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the sprites
|
||||
var sheetDensity = SheetForCollection(collection);
|
||||
var pr = collection.PanelRegion;
|
||||
var ps = collection.PanelSides;
|
||||
|
||||
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
|
||||
{
|
||||
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
|
||||
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
|
||||
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
|
||||
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
|
||||
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
|
||||
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
|
||||
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
|
||||
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
|
||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||
};
|
||||
|
||||
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||
.ToArray();
|
||||
}
|
||||
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
|
||||
sprites = new[]
|
||||
{
|
||||
TryGetImage(collectionName, "corner-tl"),
|
||||
TryGetImage(collectionName, "border-t"),
|
||||
TryGetImage(collectionName, "corner-tr"),
|
||||
TryGetImage(collectionName, "border-l"),
|
||||
TryGetImage(collectionName, "background"),
|
||||
TryGetImage(collectionName, "border-r"),
|
||||
TryGetImage(collectionName, "corner-bl"),
|
||||
TryGetImage(collectionName, "border-b"),
|
||||
TryGetImage(collectionName, "corner-br")
|
||||
};
|
||||
}
|
||||
|
||||
cachedPanelSprites.Add(collectionName, sprites);
|
||||
return sprites;
|
||||
}
|
||||
|
||||
public static Size GetMinimumPanelSize(string collectionName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(collectionName))
|
||||
return new Size(0, 0);
|
||||
|
||||
if (!collections.TryGetValue(collectionName, out var collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
if (collection.PanelRegion == null || collection.PanelRegion.Length != 8)
|
||||
{
|
||||
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
var pr = collection.PanelRegion;
|
||||
return new Size(pr[2] + pr[6], pr[3] + pr[7]);
|
||||
}
|
||||
|
||||
public static void SetDPIScale(float scale)
|
||||
{
|
||||
if (dpiScale == scale)
|
||||
return;
|
||||
|
||||
dpiScale = scale;
|
||||
|
||||
// Clear the sprite caches so the new artwork can be loaded
|
||||
// Sheets are not cleared: we assume that the extra memory overhead
|
||||
// of having the same sheet in memory in multiple DPIs is better than
|
||||
// the overhead of having to dispose and reload everything.
|
||||
// Changing the DPI scale is rare, but if it does happen then there
|
||||
// is a reasonable chance that it may happen again this session.
|
||||
cachedSprites.Clear();
|
||||
cachedPanelSprites.Clear();
|
||||
cachedCollectionSheets.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
public Rectangle Bounds;
|
||||
|
||||
public int Length;
|
||||
public Sprite[] Sprites;
|
||||
public IHardwareCursor[] Cursors;
|
||||
}
|
||||
|
||||
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly GraphicSettings graphicSettings;
|
||||
|
||||
Cursor cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
readonly bool hardwareCursorsDisabled = false;
|
||||
bool hardwareCursorsDoubled = false;
|
||||
|
||||
public CursorManager(CursorProvider cursorProvider)
|
||||
{
|
||||
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||
|
||||
graphicSettings = Game.Settings.Graphics;
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
|
||||
var c = new Cursor
|
||||
{
|
||||
Name = kv.Key,
|
||||
Bounds = Rectangle.FromLTRB(0, 0, 1, 1),
|
||||
|
||||
Length = 0,
|
||||
Sprites = new Sprite[frames.Length],
|
||||
Cursors = new IHardwareCursor[frames.Length]
|
||||
};
|
||||
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
foreach (var f in frames)
|
||||
{
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// Resolve indexed data to real colours
|
||||
var data = f.Data;
|
||||
var type = f.Type;
|
||||
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
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
c.PaddedSize = 8 * new int2((c.Bounds.Width + 7) / 8, (c.Bounds.Height + 7) / 8);
|
||||
|
||||
cursors.Add(kv.Key, c);
|
||||
}
|
||||
|
||||
CreateOrUpdateHardwareCursors();
|
||||
|
||||
foreach (var s in sheetBuilder.AllSheets)
|
||||
s.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CreateOrUpdateHardwareCursors()
|
||||
{
|
||||
if (hardwareCursorsDisabled)
|
||||
return;
|
||||
|
||||
// Dispose any existing cursors to avoid leaking native resources
|
||||
ClearHardwareCursors();
|
||||
|
||||
foreach (var kv in cursors)
|
||||
{
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
{
|
||||
if (template.Cursors[i] != null)
|
||||
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;
|
||||
|
||||
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}.");
|
||||
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (hardwareCursorsDoubled != graphicSettings.CursorDouble)
|
||||
{
|
||||
CreateOrUpdateHardwareCursors();
|
||||
Update();
|
||||
}
|
||||
|
||||
if (cursor == null || cursor.Cursors.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Cursors.Length)
|
||||
frame %= cursor.Cursors.Length;
|
||||
|
||||
var hardwareCursor = cursor?.Cursors[frame];
|
||||
if (hardwareCursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(hardwareCursor);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
// Cursor is hidden
|
||||
if (cursor == null)
|
||||
return;
|
||||
|
||||
// Hardware cursor is enabled
|
||||
if (!isLocked && cursor.Cursors[frame % cursor.Length] != null)
|
||||
return;
|
||||
|
||||
// Render cursor in software
|
||||
var doubleCursor = graphicSettings.CursorDouble;
|
||||
var cursorSprite = cursor.Sprites[frame % cursor.Length];
|
||||
var cursorScale = doubleCursor ? 2 : 1;
|
||||
|
||||
// Cursor is rendered in native window coordinates
|
||||
// Apply same scaling rules as hardware cursors
|
||||
if (Game.Renderer.NativeWindowScale > 1.5f)
|
||||
cursorScale *= 2;
|
||||
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos,
|
||||
cursorScale / Game.Renderer.WindowScale);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
if (frame.Type != SpriteFrameType.Indexed8)
|
||||
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
|
||||
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (palette == null)
|
||||
throw new InvalidOperationException($"Cursor sequence `{name}` attempted to load an indexed sprite but does not define Palette");
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
unsafe
|
||||
{
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &data[0])
|
||||
{
|
||||
var rgba = (uint*)bd;
|
||||
for (var j = 0; j < height; j++)
|
||||
for (var i = 0; i < width; i++)
|
||||
rgba[j * width + i] = palette[frame.Data[j * width + i]];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Sprite data, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
var size = data.Bounds.Size;
|
||||
var srcStride = data.Sheet.Size.Width;
|
||||
var srcData = data.Sheet.GetData();
|
||||
var newWidth = paddingTL.X + size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
|
||||
for (var j = 0; j < size.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < size.Width; i++)
|
||||
{
|
||||
var src = 4 * ((j + data.Bounds.Top) * srcStride + data.Bounds.Left + i);
|
||||
var dest = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
Array.Copy(srcData, src, rgbaData, dest, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot, graphicSettings.CursorDouble);
|
||||
}
|
||||
|
||||
void ClearHardwareCursors()
|
||||
{
|
||||
foreach (var c in cursors.Values)
|
||||
{
|
||||
for (var i = 0; i < c.Cursors.Length; i++)
|
||||
{
|
||||
if (c.Cursors[i] != null)
|
||||
{
|
||||
c.Cursors[i].Dispose();
|
||||
c.Cursors[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearHardwareCursors();
|
||||
|
||||
cursors.Clear();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -31,14 +31,14 @@ namespace OpenRA.Graphics
|
||||
|
||||
// Overwrite previous definitions if there are duplicates
|
||||
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)
|
||||
pals[p.Palette] = p;
|
||||
|
||||
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
|
||||
.Where(p => p != null)
|
||||
.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 cursors = new Dictionary<string, CursorSequence>();
|
||||
@@ -46,9 +46,11 @@ namespace OpenRA.Graphics
|
||||
foreach (var sequence in s.Value.Nodes)
|
||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||
|
||||
Cursors = cursors;
|
||||
Cursors = cursors.AsReadOnly();
|
||||
}
|
||||
|
||||
public static bool CursorViewportZoomed { get { return Game.Settings.Graphics.CursorDouble && Game.Settings.Graphics.PixelDouble; } }
|
||||
|
||||
public bool HasCursorSequence(string cursor)
|
||||
{
|
||||
return Cursors.ContainsKey(cursor);
|
||||
@@ -59,7 +61,7 @@ namespace OpenRA.Graphics
|
||||
try { return Cursors[cursor]; }
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`");
|
||||
throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -31,11 +31,8 @@ namespace OpenRA.Graphics
|
||||
Palette = palette;
|
||||
Name = name;
|
||||
|
||||
var cursorSprites = cache[cursorSrc];
|
||||
Frames = cursorSprites.Skip(Start).ToArray();
|
||||
|
||||
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
|
||||
Length = Frames.Length;
|
||||
Length = Frames.Length - Start;
|
||||
else if (d.ContainsKey("Length"))
|
||||
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
|
||||
else if (d.ContainsKey("End"))
|
||||
@@ -43,23 +40,22 @@ namespace OpenRA.Graphics
|
||||
else
|
||||
Length = 1;
|
||||
|
||||
Frames = Frames.Take(Length).ToArray();
|
||||
|
||||
if (Start > cursorSprites.Length)
|
||||
throw new YamlException($"Cursor {name}: {nameof(Start)} is greater than the length of the sprite sequence.");
|
||||
|
||||
if (Length > cursorSprites.Length)
|
||||
throw new YamlException($"Cursor {name}: {nameof(Length)} is greater than the length of the sprite sequence.");
|
||||
Frames = cache[cursorSrc]
|
||||
.Skip(Start)
|
||||
.Take(Length)
|
||||
.ToArray();
|
||||
|
||||
if (d.ContainsKey("X"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
|
||||
int x;
|
||||
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
|
||||
Hotspot = Hotspot.WithX(x);
|
||||
}
|
||||
|
||||
if (d.ContainsKey("Y"))
|
||||
{
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
|
||||
int y;
|
||||
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
|
||||
Hotspot = Hotspot.WithY(y);
|
||||
}
|
||||
}
|
||||
|
||||
192
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
192
OpenRA.Game/Graphics/HardwareCursor.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 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.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwareCursor : ICursor
|
||||
{
|
||||
readonly Dictionary<string, IHardwareCursor[]> hardwareCursors = new Dictionary<string, IHardwareCursor[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
readonly Dictionary<string, Sprite[]> sprites = new Dictionary<string, Sprite[]>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly HardwarePalette hardwarePalette = new HardwarePalette();
|
||||
readonly Cache<string, PaletteReference> paletteReferences;
|
||||
|
||||
CursorSequence cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
|
||||
public HardwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
paletteReferences = new Cache<string, PaletteReference>(CreatePaletteReference);
|
||||
foreach (var p in cursorProvider.Palettes)
|
||||
hardwarePalette.AddPalette(p.Key, p.Value, false);
|
||||
|
||||
hardwarePalette.Initialize();
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var palette = cursorProvider.Palettes[kv.Value.Palette];
|
||||
var hc = kv.Value.Frames
|
||||
.Select(f => CreateCursor(f, palette, kv.Key, kv.Value))
|
||||
.ToArray();
|
||||
|
||||
hardwareCursors.Add(kv.Key, hc);
|
||||
|
||||
var s = kv.Value.Frames.Select(a => sheetBuilder.Add(a)).ToArray();
|
||||
sprites.Add(kv.Key, s);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
PaletteReference CreatePaletteReference(string name)
|
||||
{
|
||||
var pal = hardwarePalette.GetPalette(name);
|
||||
return new PaletteReference(name, hardwarePalette.GetPaletteIndex(name), pal, hardwarePalette);
|
||||
}
|
||||
|
||||
IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence)
|
||||
{
|
||||
var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2;
|
||||
|
||||
// Expand the frame if required to include the hotspot
|
||||
var frameWidth = f.Size.Width;
|
||||
var dataWidth = f.Size.Width;
|
||||
var dataX = 0;
|
||||
if (hotspot.X < 0)
|
||||
{
|
||||
dataX = -hotspot.X;
|
||||
dataWidth += dataX;
|
||||
hotspot = hotspot.WithX(0);
|
||||
}
|
||||
else if (hotspot.X >= frameWidth)
|
||||
dataWidth = hotspot.X + 1;
|
||||
|
||||
var frameHeight = f.Size.Height;
|
||||
var dataHeight = f.Size.Height;
|
||||
var dataY = 0;
|
||||
if (hotspot.Y < 0)
|
||||
{
|
||||
dataY = -hotspot.Y;
|
||||
dataHeight += dataY;
|
||||
hotspot = hotspot.WithY(0);
|
||||
}
|
||||
else if (hotspot.Y >= frameHeight)
|
||||
dataHeight = hotspot.Y + 1;
|
||||
|
||||
var data = new byte[4 * dataWidth * dataHeight];
|
||||
for (var j = 0; j < frameHeight; j++)
|
||||
{
|
||||
for (var i = 0; i < frameWidth; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]);
|
||||
var start = 4 * ((j + dataY) * dataWidth + dataX + i);
|
||||
for (var k = 0; k < 4; k++)
|
||||
data[start + k] = bytes[k];
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, hotspot);
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursorProvider.Cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (cursor == null || cursor.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Length)
|
||||
frame %= cursor.Length;
|
||||
|
||||
if (cursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(hardwareCursors[cursor.Name][frame]);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
if (cursor.Name == null || !isLocked)
|
||||
return;
|
||||
|
||||
var cursorSequence = cursorProvider.GetCursorSequence(cursor.Name);
|
||||
var cursorSprite = sprites[cursor.Name][frame];
|
||||
|
||||
var cursorOffset = cursorSequence.Hotspot + (0.5f * cursorSprite.Size.XY).ToInt2();
|
||||
|
||||
renderer.SetPalette(hardwarePalette);
|
||||
renderer.SpriteRenderer.DrawSprite(cursorSprite,
|
||||
lockedPosition - cursorOffset,
|
||||
paletteReferences[cursorSequence.Palette],
|
||||
cursorSprite.Size);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cursors in hardwareCursors)
|
||||
foreach (var cursor in cursors.Value)
|
||||
cursor.Dispose();
|
||||
|
||||
sheetBuilder.Dispose();
|
||||
hardwareCursors.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -17,94 +17,76 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwarePalette : IDisposable
|
||||
{
|
||||
public ITexture Texture { get; }
|
||||
public ITexture ColorShifts { get; }
|
||||
|
||||
public ITexture Texture { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
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>();
|
||||
byte[] buffer = Array.Empty<byte>();
|
||||
float[] colorShiftBuffer = Array.Empty<float>();
|
||||
byte[] buffer = new byte[0];
|
||||
|
||||
public HardwarePalette()
|
||||
{
|
||||
Texture = Game.Renderer.Context.CreateTexture();
|
||||
ColorShifts = Game.Renderer.Context.CreateTexture();
|
||||
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
|
||||
}
|
||||
|
||||
public bool Contains(string name)
|
||||
{
|
||||
return mutablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
return modifiablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
|
||||
}
|
||||
|
||||
public IPalette GetPalette(string name)
|
||||
{
|
||||
if (mutablePalettes.TryGetValue(name, out var mutable))
|
||||
MutablePalette mutable;
|
||||
if (modifiablePalettes.TryGetValue(name, out mutable))
|
||||
return mutable.AsReadOnly();
|
||||
if (palettes.TryGetValue(name, out var immutable))
|
||||
ImmutablePalette immutable;
|
||||
if (palettes.TryGetValue(name, out 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)
|
||||
{
|
||||
if (!indices.TryGetValue(name, out var ret))
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
int ret;
|
||||
if (!indices.TryGetValue(name, out ret))
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void AddPalette(string name, ImmutablePalette p, bool allowModifiers)
|
||||
{
|
||||
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
|
||||
// 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;
|
||||
int index = palettes.Count;
|
||||
indices.Add(name, index);
|
||||
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 colorShiftBuffer, Height * 4);
|
||||
}
|
||||
|
||||
if (allowModifiers)
|
||||
mutablePalettes.Add(name, new MutablePalette(p));
|
||||
modifiablePalettes.Add(name, new MutablePalette(p));
|
||||
else
|
||||
CopyPaletteToBuffer(index, p);
|
||||
}
|
||||
|
||||
public void ReplacePalette(string name, IPalette p)
|
||||
{
|
||||
if (mutablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
||||
if (modifiablePalettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], modifiablePalettes[name] = new MutablePalette(p));
|
||||
else if (palettes.ContainsKey(name))
|
||||
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
||||
else
|
||||
throw new InvalidOperationException($"Palette `{name}` does not exist");
|
||||
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
|
||||
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()
|
||||
{
|
||||
CopyModifiablePalettesToBuffer();
|
||||
@@ -118,27 +100,26 @@ namespace OpenRA.Graphics
|
||||
|
||||
void CopyModifiablePalettesToBuffer()
|
||||
{
|
||||
foreach (var kvp in mutablePalettes)
|
||||
foreach (var kvp in modifiablePalettes)
|
||||
CopyPaletteToBuffer(indices[kvp.Key], kvp.Value);
|
||||
}
|
||||
|
||||
void CopyBufferToTexture()
|
||||
{
|
||||
Texture.SetData(buffer, Palette.Size, Height);
|
||||
ColorShifts.SetFloatData(colorShiftBuffer, 1, Height);
|
||||
}
|
||||
|
||||
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)
|
||||
{
|
||||
foreach (var mod in paletteMods)
|
||||
mod.AdjustPalette(mutablePalettes);
|
||||
mod.AdjustPalette(readOnlyModifiablePalettes);
|
||||
|
||||
// Update our texture with the changes.
|
||||
CopyModifiablePalettesToBuffer();
|
||||
CopyBufferToTexture();
|
||||
|
||||
// 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 modifiedPalette = kvp.Value;
|
||||
@@ -149,7 +130,6 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
Texture.Dispose();
|
||||
ColorShifts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
44
OpenRA.Game/Graphics/MappedImage.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 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.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
class MappedImage
|
||||
{
|
||||
readonly Rectangle rect = Rectangle.Empty;
|
||||
public readonly string Src;
|
||||
|
||||
public MappedImage(string defaultSrc, MiniYaml info)
|
||||
{
|
||||
FieldLoader.LoadField(this, "rect", info.Value);
|
||||
FieldLoader.Load(this, info);
|
||||
if (Src == null)
|
||||
Src = defaultSrc;
|
||||
}
|
||||
|
||||
public Sprite GetImage(Sheet s)
|
||||
{
|
||||
return new Sprite(s, rect, TextureChannel.RGBA);
|
||||
}
|
||||
|
||||
public MiniYaml Save(string defaultSrc)
|
||||
{
|
||||
var root = new List<MiniYamlNode>();
|
||||
if (defaultSrc != Src)
|
||||
root.Add(new MiniYamlNode("Src", Src));
|
||||
|
||||
return new MiniYaml(FieldSaver.FormatValue(this, GetType().GetField("rect")), root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,7 +10,6 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -30,7 +29,7 @@ namespace OpenRA.Graphics
|
||||
Rectangle AggregateBounds { get; }
|
||||
}
|
||||
|
||||
public readonly struct ModelRenderData
|
||||
public struct ModelRenderData
|
||||
{
|
||||
public readonly int Start;
|
||||
public readonly int Count;
|
||||
@@ -46,7 +45,6 @@ namespace OpenRA.Graphics
|
||||
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModel(string model);
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
@@ -64,15 +62,10 @@ namespace OpenRA.Graphics
|
||||
|
||||
class PlaceholderModelCache : IModelCache
|
||||
{
|
||||
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
|
||||
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModel(string model)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
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 IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,20 +10,21 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public readonly struct ModelAnimation
|
||||
public struct ModelAnimation
|
||||
{
|
||||
public readonly IModel Model;
|
||||
public readonly Func<WVec> OffsetFunc;
|
||||
public readonly Func<WRot> RotationFunc;
|
||||
public readonly Func<IEnumerable<WRot>> RotationFunc;
|
||||
public readonly Func<bool> DisableFunc;
|
||||
public readonly Func<uint> FrameFunc;
|
||||
public readonly bool ShowShadow;
|
||||
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
public ModelAnimation(IModel model, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
|
||||
{
|
||||
Model = model;
|
||||
OffsetFunc = offset;
|
||||
@@ -46,6 +47,12 @@ namespace OpenRA.Graphics
|
||||
xy.Y + (int)(r.Bottom * scale));
|
||||
}
|
||||
|
||||
public bool IsVisible => DisableFunc == null || !DisableFunc();
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisableFunc == null || !DisableFunc();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -42,14 +42,13 @@ namespace OpenRA.Graphics
|
||||
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
|
||||
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
|
||||
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
|
||||
static readonly float[] GroundNormal = { 0, 0, 1, 1 };
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
|
||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
|
||||
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
|
||||
readonly List<Pair<Sheet, Action>> doRender = new List<Pair<Sheet, Action>>();
|
||||
|
||||
SheetBuilder sheetBuilderForFrame;
|
||||
bool isInFrame;
|
||||
@@ -65,10 +64,10 @@ namespace OpenRA.Graphics
|
||||
shader.SetTexture("Palette", palette);
|
||||
}
|
||||
|
||||
public void SetViewportParams()
|
||||
public void SetViewportParams(Size screen, float zoom, int2 scroll)
|
||||
{
|
||||
var a = 2f / renderer.SheetSize;
|
||||
var view = new[]
|
||||
var view = new float[]
|
||||
{
|
||||
a, 0, 0, 0,
|
||||
0, -a, 0, 0,
|
||||
@@ -80,8 +79,8 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
public ModelRenderProxy RenderAsync(
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
|
||||
in WRot groundOrientation, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
|
||||
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
|
||||
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
|
||||
{
|
||||
if (!isInFrame)
|
||||
@@ -93,10 +92,7 @@ namespace OpenRA.Graphics
|
||||
// Correct for bogus light source definition
|
||||
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 ground = Util.MakeFloatMatrix(groundOrientation.AsMatrix());
|
||||
var shadowTransform = Util.MatrixMultiply(Util.MatrixMultiply(lightPitch, lightYaw), Util.MatrixInverse(ground));
|
||||
|
||||
var groundNormal = Util.MatrixVectorMultiply(ground, GroundNormal);
|
||||
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
|
||||
|
||||
var invShadowTransform = Util.MatrixInverse(shadowTransform);
|
||||
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
|
||||
@@ -118,7 +114,8 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var worldTransform = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
var worldTransform = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -164,8 +161,10 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// Shadows are rendered at twice the resolution to reduce artifacts
|
||||
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
|
||||
Size spriteSize, shadowSpriteSize;
|
||||
int2 spriteOffset, shadowSpriteOffset;
|
||||
CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
|
||||
CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
|
||||
|
||||
if (sheetBuilderForFrame == null)
|
||||
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
|
||||
@@ -182,7 +181,7 @@ namespace OpenRA.Graphics
|
||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||
|
||||
doRender.Add((sprite.Sheet, () =>
|
||||
doRender.Add(Pair.New<Sheet, Action>(sprite.Sheet, () =>
|
||||
{
|
||||
foreach (var m in models)
|
||||
{
|
||||
@@ -190,7 +189,8 @@ namespace OpenRA.Graphics
|
||||
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
|
||||
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
|
||||
|
||||
var rotations = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
|
||||
var rotations = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
|
||||
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
|
||||
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
|
||||
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace OpenRA.Graphics
|
||||
var t = m.Model.TransformationMatrix(i, frame);
|
||||
var it = Util.MatrixInverse(t);
|
||||
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
|
||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||
@@ -326,16 +326,16 @@ namespace OpenRA.Graphics
|
||||
foreach (var v in doRender)
|
||||
{
|
||||
// Change sheet
|
||||
if (v.Sheet != currentSheet)
|
||||
if (v.First != currentSheet)
|
||||
{
|
||||
if (fbo != null)
|
||||
DisableFrameBuffer(fbo);
|
||||
|
||||
currentSheet = v.Sheet;
|
||||
currentSheet = v.First;
|
||||
fbo = EnableFrameBuffer(currentSheet);
|
||||
}
|
||||
|
||||
v.Func();
|
||||
v.Second();
|
||||
}
|
||||
|
||||
if (fbo != null)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -42,10 +42,9 @@ namespace OpenRA.Graphics
|
||||
|
||||
class ReadOnlyPalette : IPalette
|
||||
{
|
||||
readonly IPalette palette;
|
||||
IPalette 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)
|
||||
{
|
||||
palette.CopyToArray(destination, destinationOffset);
|
||||
@@ -57,25 +56,28 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
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)
|
||||
{
|
||||
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))
|
||||
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))
|
||||
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);
|
||||
}
|
||||
|
||||
foreach (var i in remapTransparent)
|
||||
colors[i] = 0;
|
||||
|
||||
colors[0] = 0; // Convert black background to transparency.
|
||||
foreach (var i in remapShadow)
|
||||
colors[i] = 140u << 24;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public ImmutablePalette(IPalette p)
|
||||
{
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
for (int i = 0; i < Palette.Size; i++)
|
||||
colors[i] = p[i];
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public uint this[int index]
|
||||
{
|
||||
get => colors[index];
|
||||
set => colors[index] = value;
|
||||
get { return colors[index]; }
|
||||
set { colors[index] = value; }
|
||||
}
|
||||
|
||||
public void CopyToArray(Array destination, int destinationOffset)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public readonly string Name;
|
||||
public IPalette Palette { get; internal set; }
|
||||
public float TextureIndex => index / hardwarePalette.Height;
|
||||
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
|
||||
public float TextureIndex { get { return index / hardwarePalette.Height; } }
|
||||
public float TextureMidIndex { get { return (index + 0.5f) / hardwarePalette.Height; } }
|
||||
|
||||
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||
{
|
||||
@@ -28,7 +28,5 @@ namespace OpenRA.Graphics
|
||||
this.index = index;
|
||||
this.hardwarePalette = hardwarePalette;
|
||||
}
|
||||
|
||||
public bool HasColorShift => hardwarePalette.HasColorShift(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -15,18 +15,9 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Automatic,
|
||||
ANGLE,
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, int batchSize);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
@@ -41,27 +32,16 @@ namespace OpenRA
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
DoubleMultiplicative
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
{
|
||||
IGraphicsContext Context { get; }
|
||||
|
||||
Size NativeWindowSize { get; }
|
||||
Size EffectiveWindowSize { get; }
|
||||
float NativeWindowScale { get; }
|
||||
float EffectiveWindowScale { get; }
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
bool IsSuspended { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
Size WindowSize { get; }
|
||||
float WindowScale { get; }
|
||||
event Action<float, float> OnWindowScaleChanged;
|
||||
|
||||
void PumpInput(IInputHandler inputHandler);
|
||||
string GetClipboardText();
|
||||
@@ -70,27 +50,20 @@ namespace OpenRA
|
||||
void GrabWindowMouseFocus();
|
||||
void ReleaseWindowMouseFocus();
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
|
||||
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot);
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetWindowTitle(string title);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
{
|
||||
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||
Vertex[] CreateVertices(int size);
|
||||
ITexture CreateTexture();
|
||||
IFrameBuffer CreateFrameBuffer(Size s);
|
||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
||||
IShader CreateShader(string name);
|
||||
void EnableScissor(int x, int y, int width, int height);
|
||||
void EnableScissor(int left, int top, int width, int height);
|
||||
void DisableScissor();
|
||||
void SaveScreenshot(string path);
|
||||
void Present();
|
||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||
void Clear();
|
||||
@@ -98,7 +71,6 @@ namespace OpenRA
|
||||
void DisableDepthBuffer();
|
||||
void ClearDepthBuffer();
|
||||
void SetBlendMode(BlendMode mode);
|
||||
void SetVSyncEnabled(bool enabled);
|
||||
string GLVersion { get; }
|
||||
}
|
||||
|
||||
@@ -106,12 +78,8 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
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 start, int length);
|
||||
void SetData(IntPtr data, int start, int length);
|
||||
}
|
||||
|
||||
public interface IShader
|
||||
@@ -130,8 +98,8 @@ namespace OpenRA
|
||||
|
||||
public interface ITexture : IDisposable
|
||||
{
|
||||
void SetData(uint[,] colors);
|
||||
void SetData(byte[] colors, int width, int height);
|
||||
void SetFloatData(float[] data, int width, int height);
|
||||
byte[] GetData();
|
||||
Size Size { get; }
|
||||
TextureScaleFilter ScaleFilter { get; set; }
|
||||
@@ -141,8 +109,6 @@ namespace OpenRA
|
||||
{
|
||||
void Bind();
|
||||
void Unbind();
|
||||
void EnableScissor(Rectangle rect);
|
||||
void DisableScissor();
|
||||
ITexture Texture { get; }
|
||||
}
|
||||
|
||||
@@ -153,7 +119,7 @@ namespace OpenRA
|
||||
TriangleList,
|
||||
}
|
||||
|
||||
public readonly struct Range<T>
|
||||
public struct Range<T>
|
||||
{
|
||||
public readonly T Start, End;
|
||||
public Range(T start, T end) { Start = start; End = end; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -17,35 +18,44 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public class PlayerColorRemap : IPaletteRemap
|
||||
{
|
||||
readonly int[] remapIndices;
|
||||
readonly float hue;
|
||||
readonly float saturation;
|
||||
Dictionary<int, Color> remapColors;
|
||||
|
||||
public PlayerColorRemap(int[] remapIndices, float hue, float saturation)
|
||||
public static int GetRemapIndex(int[] ramp, int i)
|
||||
{
|
||||
this.remapIndices = remapIndices;
|
||||
this.hue = hue;
|
||||
this.saturation = saturation;
|
||||
return ramp[i];
|
||||
}
|
||||
|
||||
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) => Pair.New(baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
|
||||
.ToDictionary(u => u.First, u => u.Second);
|
||||
}
|
||||
|
||||
public Color GetRemappedColor(Color original, int index)
|
||||
{
|
||||
if (!remapIndices.Contains(index))
|
||||
return 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);
|
||||
Color c;
|
||||
return remapColors.TryGetValue(index, out c)
|
||||
? c : original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -17,40 +16,18 @@ namespace OpenRA.Graphics
|
||||
public interface IRenderable
|
||||
{
|
||||
WPos Pos { get; }
|
||||
PaletteReference Palette { get; }
|
||||
int ZOffset { get; }
|
||||
bool IsDecoration { get; }
|
||||
|
||||
IRenderable WithPalette(PaletteReference newPalette);
|
||||
IRenderable WithZOffset(int newOffset);
|
||||
IRenderable OffsetBy(in WVec offset);
|
||||
IRenderable OffsetBy(WVec offset);
|
||||
IRenderable AsDecoration();
|
||||
|
||||
IFinalizedRenderable PrepareRender(WorldRenderer wr);
|
||||
}
|
||||
|
||||
public interface IPalettedRenderable : IRenderable
|
||||
{
|
||||
PaletteReference Palette { get; }
|
||||
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
|
||||
{
|
||||
void Render(WorldRenderer wr);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
|
||||
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(float3 start, float3 end, float width, Color startColor, Color endColor)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
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[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(float3 start, float3 end, float width, Color color)
|
||||
{
|
||||
var delta = (end - start) / (end - start).XY.Length;
|
||||
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[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);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
|
||||
/// Will behave badly if the lines are parallel.
|
||||
/// Z position is the average of a and b (ignores actual intersection point if it exists)
|
||||
/// </summary>
|
||||
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
|
||||
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
|
||||
{
|
||||
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
|
||||
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
|
||||
@@ -90,7 +90,7 @@ namespace OpenRA.Graphics
|
||||
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())
|
||||
{
|
||||
@@ -101,13 +101,13 @@ namespace OpenRA.Graphics
|
||||
while (e.MoveNext())
|
||||
{
|
||||
var point = e.Current;
|
||||
DrawLine(lastPoint, point, width, color, blendMode);
|
||||
DrawLine(lastPoint, point, width, color);
|
||||
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
|
||||
if (points.Length < 2)
|
||||
@@ -116,7 +116,7 @@ namespace OpenRA.Graphics
|
||||
// Single segment
|
||||
if (points.Length == 2)
|
||||
{
|
||||
DrawLine(points[0], points[1], width, color, blendMode);
|
||||
DrawLine(points[0], points[1], width, color);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,8 +153,8 @@ namespace OpenRA.Graphics
|
||||
var nextCorner = width / 2 * new float3(-nextDir.Y, nextDir.X, nextDir.Z);
|
||||
|
||||
// Vertices for the corners joining start-end to end-next
|
||||
var cc = closed || i < limit - 1 ? IntersectionOf(end + corner, dir, end + nextCorner, nextDir) : end + corner;
|
||||
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||
var cc = closed || i < limit ? IntersectionOf(end + corner, dir, end + nextCorner, nextDir) : end + corner;
|
||||
var cd = closed || i < limit ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||
|
||||
// Fill segment
|
||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
@@ -163,7 +163,7 @@ namespace OpenRA.Graphics
|
||||
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[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
|
||||
// Advance line segment
|
||||
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)
|
||||
DrawDisconnectedLine(points, width, color, blendMode);
|
||||
DrawDisconnectedLine(points, width, color);
|
||||
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(float3 tl, float3 br, float width, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.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(float3 a, float3 b, float3 c, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
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[1] = new Vertex(b + 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(float3 tl, float3 br, Color color)
|
||||
{
|
||||
var tr = new float3(br.X, tl.Y, tl.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(float3 a, float3 b, float3 c, float3 d, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
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[4] = new Vertex(d + 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(float3 a, float3 b, float3 c, float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
|
||||
{
|
||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||
@@ -247,10 +247,10 @@ namespace OpenRA.Graphics
|
||||
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
|
||||
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
|
||||
|
||||
parent.DrawRGBAVertices(vertices, blendMode);
|
||||
parent.DrawRGBAVertices(vertices);
|
||||
}
|
||||
|
||||
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||
static Vertex VertexWithColor(float3 xyz, Color color)
|
||||
{
|
||||
color = Util.PremultiplyAlpha(color);
|
||||
var cr = color.R / 255.0f;
|
||||
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
|
||||
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(float3 tl, float3 br, Color color, int vertices = 32)
|
||||
{
|
||||
// TODO: Create an ellipse polygon instead
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -22,36 +22,28 @@ namespace OpenRA.Graphics
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 location, in float3 scale, float rotation = 0f)
|
||||
public void DrawSprite(Sprite s, float3 location, float3 size)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
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, float3 location)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
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, float3 a, float3 b, float3 c, float3 d)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
if (s.Channel != TextureChannel.RGBA)
|
||||
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
|
||||
|
||||
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha);
|
||||
parent.DrawSprite(s, a, b, c, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -26,39 +26,35 @@ namespace OpenRA.Graphics
|
||||
int Length { get; }
|
||||
int Stride { get; }
|
||||
int Facings { get; }
|
||||
int InterpolatedFacings { get; }
|
||||
int Tick { get; }
|
||||
int ZOffset { get; }
|
||||
int ShadowStart { get; }
|
||||
int ShadowZOffset { get; }
|
||||
int[] Frames { get; }
|
||||
Rectangle Bounds { get; }
|
||||
bool IgnoreWorldTint { get; }
|
||||
float Scale { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
(Sprite, WAngle) GetSpriteWithRotation(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
float GetAlpha(int frame);
|
||||
Sprite GetSprite(int frame, int facing);
|
||||
Sprite GetShadow(int frame, int facing);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
{
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
Action<string> OnMissingSpriteError { get; set; }
|
||||
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
|
||||
}
|
||||
|
||||
public class SequenceProvider : IDisposable
|
||||
{
|
||||
readonly ModData modData;
|
||||
readonly string tileSet;
|
||||
readonly TileSet tileSet;
|
||||
readonly Lazy<Sequences> sequences;
|
||||
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>();
|
||||
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
|
||||
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
|
||||
{
|
||||
this.modData = modData;
|
||||
this.tileSet = tileSet;
|
||||
@@ -68,22 +64,22 @@ namespace OpenRA.Graphics
|
||||
return Load(fileSystem, additionalSequences);
|
||||
});
|
||||
|
||||
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders));
|
||||
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed)));
|
||||
}
|
||||
|
||||
public ISpriteSequence GetSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
|
||||
ISpriteSequence seq;
|
||||
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Images => sequences.Value.Keys;
|
||||
|
||||
public bool HasSequence(string unitName)
|
||||
{
|
||||
return sequences.Value.ContainsKey(unitName);
|
||||
@@ -91,16 +87,18 @@ namespace OpenRA.Graphics
|
||||
|
||||
public bool HasSequence(string unitName, string sequenceName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.ContainsKey(sequenceName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Sequences(string unitName)
|
||||
{
|
||||
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
|
||||
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
|
||||
UnitSequences unitSeq;
|
||||
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
|
||||
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
|
||||
|
||||
return unitSeq.Value.Keys;
|
||||
}
|
||||
@@ -109,15 +107,15 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
|
||||
var items = new Dictionary<string, UnitSequences>();
|
||||
foreach (var node in nodes)
|
||||
foreach (var n in nodes)
|
||||
{
|
||||
// Nodes starting with ^ are inheritable but never loaded directly
|
||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
// Work around the loop closure issue in older versions of C#
|
||||
var node = n;
|
||||
|
||||
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||
|
||||
if (sequenceCache.TryGetValue(key, out var t))
|
||||
UnitSequences t;
|
||||
if (sequenceCache.TryGetValue(key, out t))
|
||||
items.Add(node.Key, t);
|
||||
else
|
||||
{
|
||||
@@ -127,26 +125,21 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return new ReadOnlyDictionary<string, UnitSequences>(items);
|
||||
}
|
||||
|
||||
public void Preload()
|
||||
{
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.CreateBuffer();
|
||||
|
||||
SpriteCache.SheetBuilder.Current.CreateBuffer();
|
||||
foreach (var unitSeq in sequences.Value.Values)
|
||||
foreach (var seq in unitSeq.Value.Values) { }
|
||||
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Current.ReleaseBuffer();
|
||||
SpriteCache.SheetBuilder.Current.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (spriteCache.IsValueCreated)
|
||||
foreach (var sb in SpriteCache.SheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
spriteCache.Value.SheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Graphics
|
||||
return data;
|
||||
}
|
||||
|
||||
public bool Buffered => data != null || texture == null;
|
||||
public bool Buffered { get { return data != null || texture == null; } }
|
||||
|
||||
public Sheet(SheetType type, Size size)
|
||||
{
|
||||
@@ -79,17 +79,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Png AsPng()
|
||||
{
|
||||
if (Type == SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
|
||||
|
||||
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
|
||||
return new Png(GetData(), Size.Width, Size.Height);
|
||||
}
|
||||
|
||||
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 plane = new byte[Size.Width * Size.Height];
|
||||
var dataStride = 4 * Size.Width;
|
||||
@@ -103,7 +97,7 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < Palette.Size; 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()
|
||||
@@ -142,7 +136,8 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
texture?.Dispose();
|
||||
if (texture != null)
|
||||
texture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -36,7 +36,6 @@ namespace OpenRA.Graphics
|
||||
public readonly SheetType Type;
|
||||
readonly List<Sheet> sheets = new List<Sheet>();
|
||||
readonly Func<Sheet> allocateSheet;
|
||||
readonly int margin;
|
||||
|
||||
Sheet current;
|
||||
TextureChannel channel;
|
||||
@@ -48,61 +47,52 @@ namespace OpenRA.Graphics
|
||||
return new Sheet(type, new Size(sheetSize, sheetSize));
|
||||
}
|
||||
|
||||
public static SheetType FrameTypeToSheetType(SpriteFrameType t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
return SheetType.Indexed;
|
||||
|
||||
// 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}");
|
||||
}
|
||||
}
|
||||
|
||||
public SheetBuilder(SheetType t)
|
||||
: this(t, Game.Settings.Graphics.SheetSize) { }
|
||||
|
||||
public SheetBuilder(SheetType t, int sheetSize, int margin = 1)
|
||||
: this(t, () => AllocateSheet(t, sheetSize), margin) { }
|
||||
public SheetBuilder(SheetType t, int sheetSize)
|
||||
: this(t, () => AllocateSheet(t, sheetSize)) { }
|
||||
|
||||
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
|
||||
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet)
|
||||
{
|
||||
channel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||
Type = t;
|
||||
current = allocateSheet();
|
||||
sheets.Add(current);
|
||||
this.allocateSheet = allocateSheet;
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, 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, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Png src, float scale = 1f)
|
||||
public Sprite Add(Png src)
|
||||
{
|
||||
var rect = Allocate(new Size(src.Width, src.Height), scale);
|
||||
var rect = Allocate(new Size(src.Width, src.Height));
|
||||
Util.FastCopyIntoSprite(rect, src);
|
||||
current.CommitBufferedData();
|
||||
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)
|
||||
{
|
||||
var nextChannel = (int)t + (int)Type;
|
||||
@@ -112,19 +102,19 @@ namespace OpenRA.Graphics
|
||||
return (TextureChannel)nextChannel;
|
||||
}
|
||||
|
||||
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
|
||||
public Sprite Allocate(Size imageSize) { return Allocate(imageSize, 0, float3.Zero); }
|
||||
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset)
|
||||
{
|
||||
if (imageSize.Width + p.X + margin > current.Size.Width)
|
||||
if (imageSize.Width + p.X > current.Size.Width)
|
||||
{
|
||||
p = new int2(0, p.Y + rowHeight + margin);
|
||||
p = new int2(0, p.Y + rowHeight);
|
||||
rowHeight = imageSize.Height;
|
||||
}
|
||||
|
||||
if (imageSize.Height > rowHeight)
|
||||
rowHeight = imageSize.Height;
|
||||
|
||||
if (p.Y + imageSize.Height + margin > current.Size.Height)
|
||||
if (p.Y + imageSize.Height > current.Size.Height)
|
||||
{
|
||||
var next = NextChannel(channel);
|
||||
if (next == null)
|
||||
@@ -141,15 +131,15 @@ namespace OpenRA.Graphics
|
||||
p = int2.Zero;
|
||||
}
|
||||
|
||||
var rect = new Sprite(current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, channel, BlendMode.Alpha, scale);
|
||||
p += new int2(imageSize.Width + margin, 0);
|
||||
var rect = new Sprite(current, new Rectangle(p.X, p.Y, imageSize.Width, imageSize.Height), zRamp, spriteOffset, channel, BlendMode.Alpha);
|
||||
p += new int2(imageSize.Width, 0);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sheet Current => current;
|
||||
public TextureChannel CurrentChannel => channel;
|
||||
public IEnumerable<Sheet> AllSheets => sheets;
|
||||
public Sheet Current { get { return current; } }
|
||||
public TextureChannel CurrentChannel { get { return channel; } }
|
||||
public IEnumerable<Sheet> AllSheets { get { return sheets; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
119
OpenRA.Game/Graphics/SoftwareCursor.cs
Normal file
119
OpenRA.Game/Graphics/SoftwareCursor.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 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.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ICursor : IDisposable
|
||||
{
|
||||
void Render(Renderer renderer);
|
||||
void SetCursor(string cursor);
|
||||
void Tick();
|
||||
void Lock();
|
||||
void Unlock();
|
||||
}
|
||||
|
||||
public sealed class SoftwareCursor : ICursor
|
||||
{
|
||||
readonly HardwarePalette palette = new HardwarePalette();
|
||||
readonly Cache<string, PaletteReference> paletteReferences;
|
||||
readonly Dictionary<string, Sprite[]> sprites = new Dictionary<string, Sprite[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
|
||||
public SoftwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
paletteReferences = new Cache<string, PaletteReference>(CreatePaletteReference);
|
||||
foreach (var p in cursorProvider.Palettes)
|
||||
palette.AddPalette(p.Key, p.Value, false);
|
||||
|
||||
palette.Initialize();
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var s = kv.Value.Frames.Select(a => sheetBuilder.Add(a)).ToArray();
|
||||
sprites.Add(kv.Key, s);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
}
|
||||
|
||||
PaletteReference CreatePaletteReference(string name)
|
||||
{
|
||||
var pal = palette.GetPalette(name);
|
||||
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
|
||||
}
|
||||
|
||||
string cursorName;
|
||||
public void SetCursor(string cursor)
|
||||
{
|
||||
cursorName = cursor;
|
||||
}
|
||||
|
||||
float cursorFrame;
|
||||
public void Tick()
|
||||
{
|
||||
cursorFrame += 0.5f;
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
if (cursorName == null)
|
||||
return;
|
||||
|
||||
var cursorSequence = cursorProvider.GetCursorSequence(cursorName);
|
||||
var cursorSprite = sprites[cursorName][(int)cursorFrame % cursorSequence.Length];
|
||||
var cursorSize = CursorProvider.CursorViewportZoomed ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
|
||||
var cursorOffset = CursorProvider.CursorViewportZoomed ?
|
||||
(2 * cursorSequence.Hotspot) + cursorSprite.Size.XY.ToInt2() :
|
||||
cursorSequence.Hotspot + (0.5f * cursorSprite.Size.XY).ToInt2();
|
||||
|
||||
renderer.SetPalette(palette);
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
renderer.SpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos - cursorOffset,
|
||||
paletteReferences[cursorSequence.Palette],
|
||||
cursorSize);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
isLocked = true;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
palette.Dispose();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -23,30 +23,28 @@ namespace OpenRA.Graphics
|
||||
public readonly float ZRamp;
|
||||
public readonly float3 Size;
|
||||
public readonly float3 Offset;
|
||||
public readonly float3 FractionalOffset;
|
||||
public readonly float Top, Left, Bottom, Right;
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
|
||||
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
|
||||
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel)
|
||||
: this(sheet, bounds, 0, float2.Zero, channel) { }
|
||||
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
|
||||
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha)
|
||||
{
|
||||
Sheet = sheet;
|
||||
Bounds = bounds;
|
||||
Offset = offset;
|
||||
ZRamp = zRamp;
|
||||
Channel = channel;
|
||||
Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
|
||||
Size = new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
|
||||
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
|
||||
// in rendering a line of texels that sample outside the sprite rectangle.
|
||||
// Insetting the texture coordinates by a small fraction of a pixel avoids this
|
||||
// with negligible impact on the 1:1 rendering case.
|
||||
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;
|
||||
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
|
||||
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
||||
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
|
||||
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,43 +10,43 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class SpriteFont : IDisposable
|
||||
{
|
||||
public int TopOffset { get; }
|
||||
public int TopOffset { get; private set; }
|
||||
readonly int size;
|
||||
readonly SheetBuilder builder;
|
||||
readonly Func<string, float> lineWidth;
|
||||
readonly IFont font;
|
||||
readonly Cache<char, GlyphInfo> glyphs;
|
||||
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
|
||||
readonly Cache<int, float[]> dilationElements;
|
||||
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
|
||||
|
||||
float deviceScale;
|
||||
|
||||
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
||||
{
|
||||
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;
|
||||
this.size = size;
|
||||
this.builder = builder;
|
||||
|
||||
font = Game.Renderer.CreateFont(data);
|
||||
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||
|
||||
// Pre-cache small font sizes so glyphs are immediately available when we need them
|
||||
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
|
||||
|
||||
// PERF: Cache these delegates for Measure calls.
|
||||
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
|
||||
lineWidth = line => line.Sum(characterWidth) / deviceScale;
|
||||
|
||||
if (size <= 24)
|
||||
using (new PerfTimer($"Precache {name} {size}px"))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[n] == null)
|
||||
throw new InvalidOperationException();
|
||||
PrecacheColor(Color.White, name);
|
||||
|
||||
TopOffset = size - ascender;
|
||||
}
|
||||
@@ -55,42 +55,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
deviceScale = scale;
|
||||
glyphs.Clear();
|
||||
contrastGlyphs.Clear();
|
||||
}
|
||||
|
||||
void DrawTextContrast(string text, float2 location, Color contrastColor, int contrastOffset)
|
||||
void PrecacheColor(Color c, string name)
|
||||
{
|
||||
// Offset from the baseline position to the top-left of the glyph for rendering
|
||||
location += new float2(0, size);
|
||||
|
||||
// Calculate positions in screen pixel coordinates
|
||||
var screenContrast = (int)(contrastOffset * deviceScale);
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var contrastVector = new float2(screenContrast, screenContrast);
|
||||
var tint = new float3(contrastColor.R / 255f, contrastColor.G / 255f, contrastColor.B / 255f);
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
{
|
||||
location += new float2(0, size);
|
||||
screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var contrastSprite = contrastGlyphs[(s, screenContrast)];
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
|
||||
(screen + g.Offset - contrastVector) / deviceScale,
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
}
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
}
|
||||
using (new PerfTimer("PrecacheColor {0} {1}px {2}".F(name, size, c)))
|
||||
for (var n = (char)0x20; n < (char)0x7f; n++)
|
||||
if (glyphs[Pair.New(n, c)] == null)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void DrawText(string text, float2 location, Color c)
|
||||
@@ -98,46 +70,42 @@ namespace OpenRA.Graphics
|
||||
// Offset from the baseline position to the top-left of the glyph for rendering
|
||||
location += new float2(0, size);
|
||||
|
||||
// Calculate positions in screen pixel coordinates
|
||||
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
var p = location;
|
||||
foreach (var s in text)
|
||||
{
|
||||
if (s == '\n')
|
||||
{
|
||||
location += new float2(0, size);
|
||||
screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
|
||||
p = location;
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[s];
|
||||
|
||||
// Convert screen coordinates back to UI coordinates for drawing
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
if (g.Sprite != null)
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
(screen + g.Offset).ToFloat2() / deviceScale,
|
||||
1f / deviceScale,
|
||||
tint, 1f);
|
||||
new float2(
|
||||
(int)Math.Round(p.X * deviceScale + g.Offset.X, 0) / deviceScale,
|
||||
p.Y + g.Offset.Y / deviceScale),
|
||||
g.Sprite.Size / deviceScale);
|
||||
|
||||
screen += new int2((int)(g.Advance + 0.5f), 0);
|
||||
p += new float2(g.Advance / deviceScale, 0);
|
||||
}
|
||||
}
|
||||
|
||||
float2 Rotate(float2 v, float sina, float cosa, float2 offset)
|
||||
float3 Rotate(float3 v, float sina, float cosa, float2 offset)
|
||||
{
|
||||
return new float2(
|
||||
return new float3(
|
||||
v.X * cosa - v.Y * sina + offset.X,
|
||||
v.X * sina + v.Y * cosa + offset.Y);
|
||||
v.X * sina + v.Y * cosa + offset.Y,
|
||||
0);
|
||||
}
|
||||
|
||||
public void DrawText(string text, float2 location, Color c, float angle)
|
||||
{
|
||||
// Offset from the baseline position to the top-left of the glyph for rendering
|
||||
// All positions are calculated in UI coordinates
|
||||
var offset = new float2(0, size);
|
||||
var cosa = (float)Math.Cos(-angle);
|
||||
var sina = (float)Math.Sin(-angle);
|
||||
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
|
||||
|
||||
var p = offset;
|
||||
foreach (var s in text)
|
||||
@@ -149,29 +117,21 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
var g = glyphs[s];
|
||||
var g = glyphs[Pair.New(s, c)];
|
||||
if (g.Sprite != null)
|
||||
{
|
||||
var tl = new float2(
|
||||
p.X + g.Offset.X / deviceScale,
|
||||
p.Y + g.Offset.Y / deviceScale);
|
||||
var br = tl + g.Sprite.Size.XY / deviceScale;
|
||||
var tr = new float2(br.X, tl.Y);
|
||||
var bl = new float2(tl.X, br.Y);
|
||||
var tl = new float3(
|
||||
(int)Math.Round(p.X * deviceScale + g.Offset.X, 0) / deviceScale,
|
||||
p.Y + g.Offset.Y / deviceScale, 0);
|
||||
var br = tl + g.Sprite.Size / deviceScale;
|
||||
var tr = new float3(br.X, tl.Y, 0);
|
||||
var bl = new float3(tl.X, br.Y, 0);
|
||||
|
||||
var ra = Rotate(tl, sina, cosa, location);
|
||||
var rb = Rotate(tr, sina, cosa, location);
|
||||
var rc = Rotate(br, sina, cosa, location);
|
||||
var rd = Rotate(bl, sina, cosa, location);
|
||||
|
||||
// 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;
|
||||
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
|
||||
ra + screenOffset,
|
||||
rb + screenOffset,
|
||||
rc + screenOffset,
|
||||
rd + screenOffset,
|
||||
tint, 1f);
|
||||
Rotate(tl, sina, cosa, location),
|
||||
Rotate(tr, sina, cosa, location),
|
||||
Rotate(br, sina, cosa, location),
|
||||
Rotate(bl, sina, cosa, location));
|
||||
}
|
||||
|
||||
p += new float2(g.Advance / deviceScale, 0);
|
||||
@@ -181,7 +141,12 @@ namespace OpenRA.Graphics
|
||||
public void DrawTextWithContrast(string text, float2 location, Color fg, Color bg, int offset)
|
||||
{
|
||||
if (offset > 0)
|
||||
DrawTextContrast(text, location, bg, offset);
|
||||
{
|
||||
DrawText(text, location + new float2(-offset / deviceScale, 0), bg);
|
||||
DrawText(text, location + new float2(offset / deviceScale, 0), bg);
|
||||
DrawText(text, location + new float2(0, -offset / deviceScale), bg);
|
||||
DrawText(text, location + new float2(0, offset / deviceScale), bg);
|
||||
}
|
||||
|
||||
DrawText(text, location, fg);
|
||||
}
|
||||
@@ -194,12 +159,7 @@ namespace OpenRA.Graphics
|
||||
public void DrawTextWithShadow(string text, float2 location, Color fg, Color bg, int offset)
|
||||
{
|
||||
if (offset != 0)
|
||||
{
|
||||
// Shadow offsets are rounded to an integer number of screen pixels.
|
||||
// This makes sure the shadow will be positioned consistently everywhere on the screen.
|
||||
var screenOffset = (int)(offset * deviceScale) / deviceScale;
|
||||
DrawText(text, location + new float2(screenOffset, screenOffset), bg);
|
||||
}
|
||||
DrawText(text, location + new float2(offset, offset), bg);
|
||||
|
||||
DrawText(text, location, fg);
|
||||
}
|
||||
@@ -212,12 +172,7 @@ namespace OpenRA.Graphics
|
||||
public void DrawTextWithShadow(string text, float2 location, Color fg, Color bg, int offset, float angle)
|
||||
{
|
||||
if (offset != 0)
|
||||
{
|
||||
// Shadow offsets are rounded to an integer number of screen pixels.
|
||||
// This makes sure the shadow will be positioned consistently everywhere on the screen.
|
||||
var screenOffset = (int)(offset * deviceScale) / deviceScale;
|
||||
DrawText(text, location + new float2(screenOffset, screenOffset), bg, angle);
|
||||
}
|
||||
DrawText(text, location + new float2(offset, offset), bg, angle);
|
||||
|
||||
DrawText(text, location, fg, angle);
|
||||
}
|
||||
@@ -232,31 +187,14 @@ namespace OpenRA.Graphics
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return new int2(0, size);
|
||||
|
||||
var lines = text.SplitLines('\n');
|
||||
|
||||
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);
|
||||
var lines = text.Split('\n');
|
||||
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
|
||||
}
|
||||
|
||||
float LineWidth(ReadOnlySpan<char> line)
|
||||
GlyphInfo CreateGlyph(Pair<char, Color> c)
|
||||
{
|
||||
var result = 0f;
|
||||
foreach (var c in line)
|
||||
result += glyphs[c].Advance;
|
||||
var glyph = font.CreateGlyph(c.First, size, deviceScale);
|
||||
|
||||
return result / deviceScale;
|
||||
}
|
||||
|
||||
GlyphInfo CreateGlyph(char c)
|
||||
{
|
||||
var glyph = font.CreateGlyph(c, size, deviceScale);
|
||||
if (glyph.Data == null)
|
||||
{
|
||||
return new GlyphInfo
|
||||
@@ -286,10 +224,12 @@ namespace OpenRA.Graphics
|
||||
if (p != 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
dest[q] = p;
|
||||
dest[q + 1] = p;
|
||||
dest[q + 2] = p;
|
||||
dest[q + 3] = p;
|
||||
var pmc = Util.PremultiplyAlpha(Color.FromArgb(p, c.Second));
|
||||
|
||||
dest[q] = pmc.B;
|
||||
dest[q + 1] = pmc.G;
|
||||
dest[q + 2] = pmc.R;
|
||||
dest[q + 3] = pmc.A;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,123 +239,6 @@ namespace OpenRA.Graphics
|
||||
return g;
|
||||
}
|
||||
|
||||
float[] CreateCircularWeightMap(int r)
|
||||
{
|
||||
// Create circular weight maps that are used by CreateContrastGlyph for
|
||||
// both the structuring element and to weight the resulting pixel value.
|
||||
// The output is a 2 * r + 1 square array giving the pixel intersection
|
||||
// with a circle of radius (r + 0.5).
|
||||
//
|
||||
// Example output for r=1:
|
||||
// 0.60 1.00 0.60
|
||||
// 1.00 1.00 1.00
|
||||
// 0.60 1.00 0.60
|
||||
//
|
||||
// Example output for r=3:
|
||||
// 0.00 0.44 0.80 1.00 0.80 0.44 0.00
|
||||
// 0.44 1.00 1.00 1.00 1.00 1.00 0.44
|
||||
// 0.80 1.00 1.00 1.00 1.00 1.00 0.80
|
||||
// 1.00 1.00 1.00 1.00 1.00 1.00 1.00
|
||||
// 0.80 1.00 1.00 1.00 1.00 1.00 0.80
|
||||
// 0.44 1.00 1.00 1.00 1.00 1.00 0.44
|
||||
// 0.00 0.44 0.80 1.00 0.80 0.44 0.00
|
||||
var stride = 2 * r + 1;
|
||||
var elem = new float[stride * stride];
|
||||
|
||||
for (var j = 0; j <= 2 * r; j++)
|
||||
{
|
||||
for (var i = 0; i <= 2 * r; i++)
|
||||
{
|
||||
var di = i - r;
|
||||
var dj = j - r;
|
||||
|
||||
// No intersection with circle
|
||||
if (di * di + dj * dj > (r + 1) * (r + 1))
|
||||
continue;
|
||||
|
||||
// Fully contained within circle
|
||||
if (di * di + dj * dj < (r - 1) * (r - 1))
|
||||
{
|
||||
elem[j * stride + i] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Approximate sub-pixel intersection using a 5x5 grid
|
||||
for (var jj = 0; jj < 5; jj++)
|
||||
{
|
||||
for (var ii = 0; ii < 5; ii++)
|
||||
{
|
||||
var si = di - (float)Math.Sign(di) * ii / 5;
|
||||
var sj = dj - (float)Math.Sign(dj) * jj / 5;
|
||||
if (si * si + sj * sj <= r * r)
|
||||
elem[j * stride + i] += 0.04f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
Sprite CreateContrastGlyph((char C, int Radius) c)
|
||||
{
|
||||
var glyph = glyphs[c.C];
|
||||
var r = c.Radius;
|
||||
|
||||
var s = builder.Allocate(new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r));
|
||||
var dest = s.Sheet.GetData();
|
||||
var destStride = s.Sheet.Size.Width * 4;
|
||||
|
||||
var glyphData = glyph.Sprite.Sheet.GetData();
|
||||
var glyphStride = glyph.Sprite.Sheet.Size.Width * 4;
|
||||
var glyphBounds = glyph.Sprite.Bounds;
|
||||
|
||||
var elem = dilationElements[r];
|
||||
var elemStride = 2 * r + 1;
|
||||
|
||||
// Expand the glyph by applying the greyscale dilation operator to the source glyph's alpha channel
|
||||
for (var j = 0; j < s.Size.Y; j++)
|
||||
{
|
||||
for (var i = 0; i < s.Size.X; i++)
|
||||
{
|
||||
// Apply the weight map to the source glyph and find the largest weighted alpha
|
||||
var first = true;
|
||||
var alpha = (byte)0;
|
||||
for (var wj = 0; wj <= 2 * r; wj++)
|
||||
{
|
||||
for (var wi = 0; wi <= 2 * r; wi++)
|
||||
{
|
||||
// Ignore pixels that are outside the source glyph bounds
|
||||
var ii = i + wi - 2 * r;
|
||||
var jj = j + wj - 2 * r;
|
||||
if (ii < 0 || ii >= glyphBounds.Width || jj < 0 || jj >= glyphBounds.Height)
|
||||
continue;
|
||||
|
||||
// Weighted alpha for this pixel
|
||||
var weighted = (byte)(elem[wj * elemStride + wi] * glyphData[glyphStride * (jj + glyphBounds.Top) + 4 * (ii + glyphBounds.Left) + 3]);
|
||||
if (first || weighted > alpha)
|
||||
{
|
||||
alpha = weighted;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alpha > 0)
|
||||
{
|
||||
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
|
||||
dest[q] = alpha;
|
||||
dest[q + 1] = alpha;
|
||||
dest[q + 2] = alpha;
|
||||
dest[q + 3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Sheet.CommitBufferedData();
|
||||
return s;
|
||||
}
|
||||
|
||||
static Color GetContrastColor(Color fgColor, Color bgDark, Color bgLight)
|
||||
{
|
||||
return fgColor == Color.White || fgColor.GetBrightness() > 0.33 ? bgDark : bgLight;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -18,39 +18,13 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
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
|
||||
{
|
||||
SpriteFrameType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the frame's `Data`.
|
||||
/// </summary>
|
||||
@@ -69,7 +43,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public class SpriteCache
|
||||
{
|
||||
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
|
||||
public readonly SheetBuilder SheetBuilder;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
@@ -77,10 +51,9 @@ namespace OpenRA.Graphics
|
||||
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
|
||||
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, SheetBuilder sheetBuilder)
|
||||
{
|
||||
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
|
||||
|
||||
SheetBuilder = sheetBuilder;
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
@@ -98,7 +71,8 @@ namespace OpenRA.Graphics
|
||||
var allSprites = sprites.GetOrAdd(filename);
|
||||
var sprite = allSprites.FirstOrDefault();
|
||||
|
||||
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
|
||||
ISpriteFrame[] unloaded;
|
||||
if (!unloadedFrames.TryGetValue(filename, out unloaded))
|
||||
unloaded = null;
|
||||
|
||||
// This is the first time that the file has been requested
|
||||
@@ -106,7 +80,8 @@ namespace OpenRA.Graphics
|
||||
// the loaded cache (initially empty)
|
||||
if (sprite == null)
|
||||
{
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
|
||||
TypeDictionary fileMetadata = null;
|
||||
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
unloadedFrames[filename] = unloaded;
|
||||
metadata[filename] = fileMetadata;
|
||||
|
||||
@@ -114,7 +89,7 @@ namespace OpenRA.Graphics
|
||||
allSprites.Add(sprite);
|
||||
}
|
||||
|
||||
// HACK: The sequence code relies on side-effects from getUsedFrames
|
||||
// HACK: The sequency code relies on side-effects from getUsedFrames
|
||||
var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
|
||||
Enumerable.Range(0, sprite.Length);
|
||||
|
||||
@@ -125,7 +100,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (unloaded[i] != null)
|
||||
{
|
||||
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
|
||||
sprite[i] = SheetBuilder.Add(unloaded[i]);
|
||||
unloaded[i] = null;
|
||||
}
|
||||
}
|
||||
@@ -145,7 +120,8 @@ namespace OpenRA.Graphics
|
||||
/// </summary>
|
||||
public TypeDictionary FrameMetadata(string filename)
|
||||
{
|
||||
if (!metadata.TryGetValue(filename, out var fileMetadata))
|
||||
TypeDictionary fileMetadata;
|
||||
if (!metadata.TryGetValue(filename, out fileMetadata))
|
||||
{
|
||||
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
|
||||
metadata[filename] = fileMetadata;
|
||||
@@ -161,10 +137,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
|
||||
TypeDictionary metadata;
|
||||
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out metadata));
|
||||
}
|
||||
|
||||
public ISpriteFrame[] this[string filename] => frames[filename];
|
||||
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
|
||||
}
|
||||
|
||||
public static class FrameLoader
|
||||
@@ -173,7 +150,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
using (var stream = fileSystem.Open(filename))
|
||||
{
|
||||
var spriteFrames = GetFrames(stream, loaders, filename, out metadata);
|
||||
var spriteFrames = GetFrames(stream, loaders, out metadata);
|
||||
if (spriteFrames == null)
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file!");
|
||||
|
||||
@@ -181,12 +158,13 @@ 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)
|
||||
{
|
||||
ISpriteFrame[] frames;
|
||||
metadata = null;
|
||||
|
||||
foreach (var loader in loaders)
|
||||
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
||||
if (loader.TryParseSprite(stream, out frames, out metadata))
|
||||
return frames;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,15 +9,14 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
|
||||
public struct SpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
public static readonly IEnumerable<IRenderable> None = Array.Empty<IRenderable>();
|
||||
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
|
||||
|
||||
readonly Sprite sprite;
|
||||
readonly WPos pos;
|
||||
@@ -25,14 +24,9 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
readonly float scale;
|
||||
readonly WAngle rotation = WAngle.Zero;
|
||||
readonly float3 tint;
|
||||
readonly TintModifiers tintModifiers;
|
||||
readonly float alpha;
|
||||
readonly bool isDecoration;
|
||||
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha,
|
||||
float3 tint, TintModifiers tintModifiers, bool isDecoration, WAngle rotation)
|
||||
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.pos = pos;
|
||||
@@ -40,100 +34,44 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
this.scale = scale;
|
||||
this.rotation = rotation;
|
||||
this.tint = tint;
|
||||
this.isDecoration = isDecoration;
|
||||
this.tintModifiers = tintModifiers;
|
||||
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,
|
||||
float3 tint, TintModifiers tintModifiers, bool isDecoration)
|
||||
: this(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration, WAngle.Zero) { }
|
||||
public WPos Pos { get { return pos + offset; } }
|
||||
public WVec Offset { get { return offset; } }
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public bool IsDecoration { get { return isDecoration; } }
|
||||
|
||||
public WPos Pos => pos + offset;
|
||||
public WVec Offset => offset;
|
||||
public PaletteReference Palette => palette;
|
||||
public int ZOffset => zOffset;
|
||||
public bool IsDecoration => isDecoration;
|
||||
|
||||
public float Alpha => alpha;
|
||||
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);
|
||||
}
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, isDecoration); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, isDecoration); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, isDecoration); }
|
||||
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, true); }
|
||||
|
||||
float3 ScreenPosition(WorldRenderer wr)
|
||||
{
|
||||
var s = 0.5f * scale * sprite.Size;
|
||||
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(offset) - new float3((int)s.X, (int)s.Y, s.Z);
|
||||
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset) - (0.5f * scale * sprite.Size.XY).ToInt2();
|
||||
|
||||
// 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 void Render(WorldRenderer wr)
|
||||
{
|
||||
var wsr = Game.Renderer.WorldSpriteRenderer;
|
||||
var t = alpha * tint;
|
||||
if (wr.TerrainLighting != null && (tintModifiers & TintModifiers.IgnoreWorldTint) == 0)
|
||||
t *= wr.TerrainLighting.TintAt(pos);
|
||||
|
||||
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
|
||||
var a = alpha;
|
||||
if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
|
||||
a *= -1;
|
||||
|
||||
wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
|
||||
Game.Renderer.WorldSpriteRenderer.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var pos = ScreenPosition(wr) + sprite.Offset;
|
||||
var tl = wr.Viewport.WorldToViewPx(pos);
|
||||
var br = wr.Viewport.WorldToViewPx(pos + sprite.Size);
|
||||
if (rotation == WAngle.Zero)
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red);
|
||||
else
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(Util.RotateQuad(tl, br - tl, rotation.RendererRadians()), 1, Color.Red);
|
||||
var screenOffset = ScreenPosition(wr) + sprite.Offset;
|
||||
Game.Renderer.WorldRgbaColorRenderer.DrawRect(screenOffset, screenOffset + sprite.Size, 1 / wr.Viewport.Zoom, Color.Red);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -10,21 +10,17 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class SpriteRenderer : Renderer.IBatchRenderer
|
||||
{
|
||||
public const int SheetCount = 8;
|
||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||
|
||||
readonly Renderer renderer;
|
||||
readonly IShader shader;
|
||||
|
||||
Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||
readonly Vertex[] vertices;
|
||||
readonly Sheet[] sheets = new Sheet[7];
|
||||
|
||||
BlendMode currentBlend = BlendMode.Alpha;
|
||||
int nv = 0;
|
||||
@@ -34,7 +30,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.shader = shader;
|
||||
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
|
||||
vertices = new Vertex[renderer.TempBufferSize];
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
@@ -43,15 +39,13 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < ns; i++)
|
||||
{
|
||||
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
|
||||
sheets[i] = null;
|
||||
}
|
||||
|
||||
renderer.Context.SetBlendMode(currentBlend);
|
||||
shader.PrepareRender();
|
||||
|
||||
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
|
||||
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
|
||||
renderer.DrawBatch(vertices, nv, PrimitiveType.TriangleList);
|
||||
renderer.Context.SetBlendMode(BlendMode.None);
|
||||
|
||||
nv = 0;
|
||||
@@ -83,175 +77,100 @@ namespace OpenRA.Graphics
|
||||
for (; secondarySheetIndex < ns; secondarySheetIndex++)
|
||||
if (sheets[secondarySheetIndex] == secondarySheet)
|
||||
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
|
||||
if (Math.Max(sheetIndex, secondarySheetIndex) >= sheets.Length)
|
||||
var needSamplers = (sheetIndex == ns ? 1 : 0) + (secondarySheetIndex == ns ? 1 : 0);
|
||||
if (ns + needSamplers >= sheets.Length)
|
||||
{
|
||||
Flush();
|
||||
sheetIndex = 0;
|
||||
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
||||
if (ss != null)
|
||||
secondarySheetIndex = 1;
|
||||
}
|
||||
|
||||
if (sheetIndex >= ns)
|
||||
{
|
||||
sheets[sheetIndex] = sheet;
|
||||
ns++;
|
||||
ns += 1;
|
||||
}
|
||||
|
||||
if (secondarySheetIndex >= ns && ss != null)
|
||||
{
|
||||
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
||||
ns++;
|
||||
ns += 1;
|
||||
}
|
||||
|
||||
return new int2(sheetIndex, secondarySheetIndex);
|
||||
}
|
||||
|
||||
float ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||
{
|
||||
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)
|
||||
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, s.Size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
|
||||
{
|
||||
DrawSprite(s, location, pal.TextureIndex, size);
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
|
||||
1f, rotation);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
|
||||
{
|
||||
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);
|
||||
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
|
||||
rotation);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
||||
float rotation = 0f)
|
||||
{
|
||||
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
|
||||
}
|
||||
|
||||
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
|
||||
{
|
||||
var samplers = SetRenderStateForSprite(s);
|
||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||
nv += 6;
|
||||
}
|
||||
|
||||
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var s in sheets)
|
||||
{
|
||||
if (i >= SheetCount)
|
||||
ThrowSheetOverflow(nameof(sheets));
|
||||
|
||||
if (s != null)
|
||||
shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture());
|
||||
}
|
||||
|
||||
shader.SetTexture("Texture0", sheet.GetTexture());
|
||||
renderer.Context.SetBlendMode(blendMode);
|
||||
shader.PrepareRender();
|
||||
renderer.DrawBatch(buffer, start, length, type);
|
||||
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
|
||||
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
|
||||
internal void DrawRGBAVertices(Vertex[] v)
|
||||
{
|
||||
renderer.CurrentBatchRenderer = this;
|
||||
|
||||
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
|
||||
if (currentBlend != BlendMode.Alpha || nv + v.Length > renderer.TempBufferSize)
|
||||
Flush();
|
||||
|
||||
currentBlend = blendMode;
|
||||
currentBlend = BlendMode.Alpha;
|
||||
Array.Copy(v, 0, vertices, nv, v.Length);
|
||||
nv += v.Length;
|
||||
}
|
||||
|
||||
public void SetPalette(ITexture palette, ITexture colorShifts)
|
||||
public void SetPalette(ITexture 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, float zoom, int2 scroll)
|
||||
{
|
||||
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
|
||||
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
|
||||
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
|
||||
var width = 2f / (downscale * sheetSize.Width);
|
||||
var height = 2f / (downscale * sheetSize.Height);
|
||||
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
|
||||
shader.SetVec("r1",
|
||||
zoom * 2f / screen.Width,
|
||||
-zoom * 2f / screen.Height,
|
||||
-depthScale * zoom / screen.Height);
|
||||
shader.SetVec("r2", -1, 1, 1 - depthOffset);
|
||||
|
||||
// Depth is more complicated:
|
||||
// * The OpenGL z axis is inverted (negative is closer) relative to OpenRA (positive is closer).
|
||||
// * 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);
|
||||
// Texture index is sampled as a float, so convert to pixels then scale
|
||||
shader.SetVec("DepthTextureScale", 128 * depthScale * zoom / screen.Height);
|
||||
}
|
||||
|
||||
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
||||
public void SetDepthPreviewEnabled(bool enabled)
|
||||
{
|
||||
shader.SetBool("EnableDepthPreview", enabled);
|
||||
shader.SetVec("DepthPreviewParams", contrast, offset);
|
||||
}
|
||||
|
||||
public void SetAntialiasingPixelsPerTexel(float pxPerTx)
|
||||
{
|
||||
shader.SetVec("AntialiasPixelsPerTexel", pxPerTx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -15,14 +15,14 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly IEnumerable<WPos> waypoints;
|
||||
readonly Color color;
|
||||
readonly int width;
|
||||
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.color = color;
|
||||
@@ -30,19 +30,14 @@ namespace OpenRA.Graphics
|
||||
this.markerSize = markerSize;
|
||||
}
|
||||
|
||||
public WPos Pos => waypoints.First();
|
||||
public int ZOffset => 0;
|
||||
public bool IsDecoration => 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 WPos Pos { get { return waypoints.First(); } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return 0; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
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 IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
@@ -51,24 +46,26 @@ namespace OpenRA.Graphics
|
||||
if (!waypoints.Any())
|
||||
return;
|
||||
|
||||
var first = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(waypoints.First()));
|
||||
var sw = width / wr.Viewport.Zoom;
|
||||
var first = wr.Screen3DPosition(waypoints.First());
|
||||
var a = first;
|
||||
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.Screen3DPosition(pos)))
|
||||
{
|
||||
Game.Renderer.RgbaColorRenderer.DrawLine(a, b, width, color);
|
||||
DrawTargetMarker(color, b, markerSize);
|
||||
Game.Renderer.WorldRgbaColorRenderer.DrawLine(a, b, sw, color);
|
||||
DrawTargetMarker(wr, color, b, markerSize);
|
||||
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, float3 location, int size = 1)
|
||||
{
|
||||
var offset = new int2(size, size);
|
||||
var tl = screenPos - offset;
|
||||
var br = screenPos + offset;
|
||||
Game.Renderer.RgbaColorRenderer.FillRect(tl, br, color);
|
||||
var sw = size / wr.Viewport.Zoom;
|
||||
var offset = new float2(sw, sw);
|
||||
var tl = location - offset;
|
||||
var br = location + offset;
|
||||
Game.Renderer.WorldRgbaColorRenderer.FillRect(tl, br, color);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -12,21 +12,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class TerrainSpriteLayer : IDisposable
|
||||
{
|
||||
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
public readonly Sheet Sheet;
|
||||
public readonly BlendMode BlendMode;
|
||||
|
||||
readonly Sheet[] sheets;
|
||||
readonly Sprite emptySprite;
|
||||
|
||||
readonly IVertexBuffer<Vertex> vertexBuffer;
|
||||
readonly Vertex[] vertices;
|
||||
readonly bool[] ignoreTint;
|
||||
readonly HashSet<int> dirtyRows = new HashSet<int>();
|
||||
readonly int rowStride;
|
||||
readonly bool restrictToBounds;
|
||||
@@ -34,161 +32,67 @@ namespace OpenRA.Graphics
|
||||
readonly WorldRenderer worldRenderer;
|
||||
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;
|
||||
this.restrictToBounds = restrictToBounds;
|
||||
this.emptySprite = emptySprite;
|
||||
sheets = new Sheet[SpriteRenderer.SheetCount];
|
||||
Sheet = sheet;
|
||||
BlendMode = blendMode;
|
||||
this.palette = palette;
|
||||
|
||||
map = world.Map;
|
||||
rowStride = 6 * map.MapSize.X;
|
||||
|
||||
vertices = new Vertex[rowStride * map.MapSize.Y];
|
||||
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
|
||||
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
|
||||
|
||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||
|
||||
if (wr.TerrainLighting != null)
|
||||
{
|
||||
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||
}
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
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, p, v.C, v.R, v.G, v.B, v.A);
|
||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C);
|
||||
}
|
||||
|
||||
for (var row = 0; row < map.MapSize.Y; row++)
|
||||
dirtyRows.Add(row);
|
||||
}
|
||||
|
||||
public void Clear(CPos cell)
|
||||
public void Update(CPos cell, Sprite sprite)
|
||||
{
|
||||
Update(cell, null, null, 1f, 1f, true);
|
||||
var xyz = sprite == null ? float3.Zero :
|
||||
worldRenderer.Screen3DPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size;
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, xyz);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame)
|
||||
public void Update(MPos uv, Sprite sprite, float3 pos)
|
||||
{
|
||||
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint);
|
||||
}
|
||||
|
||||
public void Update(CPos cell, Sprite sprite, PaletteReference palette, float scale = 1f, float alpha = 1f, bool ignoreTint = false)
|
||||
{
|
||||
var xyz = float3.Zero;
|
||||
if (sprite != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (sprite.Sheet != Sheet)
|
||||
throw new InvalidDataException("Attempted to add sprite from a different sheet");
|
||||
|
||||
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint);
|
||||
}
|
||||
|
||||
void UpdateTint(MPos uv)
|
||||
{
|
||||
var offset = rowStride * uv.V + 6 * uv.U;
|
||||
if (ignoreTint[offset])
|
||||
{
|
||||
for (var i = 0; i < 6; 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);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow the terrain tint to vary linearly across the cell to smooth out the staircase effect
|
||||
// This is done by sampling the lighting the corners of the sprite, even though those pixels are
|
||||
// transparent for isometric tiles
|
||||
var tl = worldRenderer.TerrainLighting;
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
|
||||
var weights = new[]
|
||||
{
|
||||
tl.TintAt(pos + new WVec(-step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, -step, 0)),
|
||||
tl.TintAt(pos + new WVec(step, step, 0)),
|
||||
tl.TintAt(pos + new WVec(-step, step, 0))
|
||||
};
|
||||
|
||||
// Apply tint directly to the underlying vertices
|
||||
// This saves us from having to re-query the sprite information, which has not changed
|
||||
for (var i = 0; i < 6; 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);
|
||||
}
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
|
||||
int GetOrAddSheetIndex(Sheet sheet)
|
||||
{
|
||||
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.BlendMode != BlendMode)
|
||||
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
|
||||
{
|
||||
sprite = emptySprite;
|
||||
samplers = int2.Zero;
|
||||
}
|
||||
|
||||
// The vertex buffer does not have geometry for cells outside the map
|
||||
if (!map.Tiles.Contains(uv))
|
||||
return;
|
||||
|
||||
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);
|
||||
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
||||
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
{
|
||||
this.ignoreTint[offset] = ignoreTint;
|
||||
UpdateTint(uv);
|
||||
}
|
||||
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size);
|
||||
|
||||
dirtyRows.Add(uv.V);
|
||||
}
|
||||
@@ -210,12 +114,20 @@ namespace OpenRA.Graphics
|
||||
continue;
|
||||
|
||||
var rowOffset = rowStride * row;
|
||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
||||
|
||||
unsafe
|
||||
{
|
||||
// The compiler / language spec won't let us calculate a pointer to
|
||||
// an offset inside a generic array T[], and so we are forced to
|
||||
// calculate the start-of-row pointer here to pass in to SetData.
|
||||
fixed (Vertex* vPtr = &vertices[0])
|
||||
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
|
||||
}
|
||||
}
|
||||
|
||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||
PrimitiveType.TriangleList, sheets, BlendMode);
|
||||
PrimitiveType.TriangleList, Sheet, BlendMode);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
@@ -223,9 +135,6 @@ namespace OpenRA.Graphics
|
||||
public void Dispose()
|
||||
{
|
||||
worldRenderer.PaletteInvalidated -= UpdatePaletteIndices;
|
||||
if (worldRenderer.TerrainLighting != null)
|
||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
159
OpenRA.Game/Graphics/Theater.cs
Normal file
159
OpenRA.Game/Graphics/Theater.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 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.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>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
readonly Sprite missingTile;
|
||||
readonly MersenneTwister random;
|
||||
TileSet tileset;
|
||||
|
||||
public Theater(TileSet tileset)
|
||||
{
|
||||
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));
|
||||
};
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
|
||||
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)
|
||||
{
|
||||
var allFrames = frameCache[i];
|
||||
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
|
||||
var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount);
|
||||
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 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));
|
||||
|
||||
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
|
||||
}
|
||||
|
||||
// 1x1px transparent tile
|
||||
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));
|
||||
|
||||
Sheet.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public Sprite TileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
TheaterTemplate template;
|
||||
if (!templates.TryGetValue(r.Type, out 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -13,7 +13,7 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
|
||||
public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly Sprite sprite;
|
||||
readonly WPos effectiveWorldPos;
|
||||
@@ -21,10 +21,8 @@ namespace OpenRA.Graphics
|
||||
readonly int zOffset;
|
||||
readonly PaletteReference palette;
|
||||
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.effectiveWorldPos = effectiveWorldPos;
|
||||
@@ -32,48 +30,37 @@ namespace OpenRA.Graphics
|
||||
this.zOffset = zOffset;
|
||||
this.palette = palette;
|
||||
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
|
||||
public WPos Pos => effectiveWorldPos;
|
||||
public WVec Offset => WVec.Zero;
|
||||
public bool IsDecoration => true;
|
||||
public WPos Pos { get { return effectiveWorldPos; } }
|
||||
public WVec Offset { get { return WVec.Zero; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public PaletteReference Palette => palette;
|
||||
public int ZOffset => zOffset;
|
||||
public PaletteReference Palette { get { return palette; } }
|
||||
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 OffsetBy(in WVec vec) { return this; }
|
||||
public IRenderable OffsetBy(WVec vec) { return this; }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
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)
|
||||
{
|
||||
var offset = screenPos + sprite.Offset.XY;
|
||||
if (rotation == 0f)
|
||||
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);
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(offset, offset + sprite.Size.XY, 1, Color.Red);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -20,60 +20,26 @@ namespace OpenRA.Graphics
|
||||
// yes, our channel order is nuts.
|
||||
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,
|
||||
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size)
|
||||
{
|
||||
float3 a, b, c, d;
|
||||
|
||||
// Rotate sprite if rotation angle is not equal to 0
|
||||
if (rotation != 0f)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
|
||||
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, nv);
|
||||
}
|
||||
|
||||
public static void FastCreateQuad(Vertex[] vertices,
|
||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||
in float3 tint, float alpha, int nv)
|
||||
public static void FastCreateQuad(Vertex[] vertices, float3 a, float3 b, float3 c, float3 d, Sprite r, int2 samplers, float paletteTextureIndex, int nv)
|
||||
{
|
||||
float sl = 0;
|
||||
float st = 0;
|
||||
float sr = 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;
|
||||
attribC |= samplers.X << 6;
|
||||
if (r is SpriteWithSecondaryData ss)
|
||||
var ss = r as SpriteWithSecondaryData;
|
||||
if (ss != null)
|
||||
{
|
||||
sl = ss.SecondaryLeft;
|
||||
st = ss.SecondaryTop;
|
||||
@@ -85,88 +51,33 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
var fAttribC = (float)attribC;
|
||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 2] = 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, alpha);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||
vertices[nv + 5] = 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);
|
||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC);
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
|
||||
}
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
|
||||
{
|
||||
var destData = dest.Sheet.GetData();
|
||||
var width = dest.Bounds.Width;
|
||||
var data = dest.Sheet.GetData();
|
||||
var srcStride = dest.Bounds.Width;
|
||||
var destStride = dest.Sheet.Size.Width * 4;
|
||||
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
|
||||
var destSkip = destStride - 4 * srcStride;
|
||||
var height = dest.Bounds.Height;
|
||||
|
||||
if (dest.Channel == TextureChannel.RGBA)
|
||||
var srcOffset = 0;
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
var destStride = dest.Sheet.Size.Width;
|
||||
unsafe
|
||||
for (var i = 0; i < srcStride; i++, srcOffset++)
|
||||
{
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &destData[0])
|
||||
{
|
||||
var data = (int*)bd;
|
||||
var x = dest.Bounds.Left;
|
||||
var y = dest.Bounds.Top;
|
||||
|
||||
var k = 0;
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
byte r, g, b, a;
|
||||
switch (srcType)
|
||||
{
|
||||
case SpriteFrameType.Bgra32:
|
||||
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);
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
}
|
||||
}
|
||||
data[destOffset] = src[srcOffset];
|
||||
destOffset += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var destStride = dest.Sheet.Size.Width * 4;
|
||||
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
|
||||
var destSkip = destStride - 4 * width;
|
||||
|
||||
var srcOffset = 0;
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++, srcOffset++)
|
||||
{
|
||||
destData[destOffset] = src[srcOffset];
|
||||
destOffset += 4;
|
||||
}
|
||||
|
||||
destOffset += destSkip;
|
||||
}
|
||||
destOffset += destSkip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,29 +103,16 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
Color cc;
|
||||
switch (src.Type)
|
||||
if (src.Palette == null)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
{
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
break;
|
||||
}
|
||||
|
||||
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}");
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Data[k++];
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
else
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
@@ -223,69 +121,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)
|
||||
{
|
||||
if (c.A == byte.MaxValue)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -14,36 +14,19 @@ using System.Runtime.InteropServices;
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct Vertex
|
||||
public struct Vertex
|
||||
{
|
||||
// 3d position
|
||||
public readonly float X, Y, Z;
|
||||
public readonly float X, Y, Z, S, T, U, V, P, C;
|
||||
|
||||
// Primary and secondary texture coordinates or RGBA color
|
||||
public readonly float S, T, U, V;
|
||||
public Vertex(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) { }
|
||||
|
||||
// Palette and channel flags
|
||||
public readonly float P, C;
|
||||
|
||||
// Color tint
|
||||
public readonly float R, G, B, A;
|
||||
|
||||
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) { }
|
||||
|
||||
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
||||
|
||||
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)
|
||||
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, 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, float a)
|
||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
|
||||
{
|
||||
X = x; Y = y; Z = z;
|
||||
S = s; T = t;
|
||||
U = u; V = v;
|
||||
P = p; C = c;
|
||||
R = r; G = g; B = b; A = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -19,11 +19,6 @@ namespace OpenRA.Graphics
|
||||
[Flags]
|
||||
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
|
||||
|
||||
public interface INotifyViewportZoomExtentsChanged
|
||||
{
|
||||
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
|
||||
}
|
||||
|
||||
public static class ViewportExts
|
||||
{
|
||||
public static bool Includes(this ScrollDirection d, ScrollDirection s)
|
||||
@@ -41,8 +36,6 @@ namespace OpenRA.Graphics
|
||||
public class Viewport
|
||||
{
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly WorldViewportSizes viewportSizes;
|
||||
readonly GraphicSettings graphicSettings;
|
||||
|
||||
// Map bounds (world-px)
|
||||
readonly Rectangle mapBounds;
|
||||
@@ -51,73 +44,42 @@ namespace OpenRA.Graphics
|
||||
// Viewport geometry (world-px)
|
||||
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 int2 TopLeft => CenterLocation - viewportSize / 2;
|
||||
public int2 BottomRight => CenterLocation + viewportSize / 2;
|
||||
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
|
||||
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
|
||||
int2 viewportSize;
|
||||
ProjectedCellRegion cells;
|
||||
bool cellsDirty = true;
|
||||
|
||||
ProjectedCellRegion allCells;
|
||||
bool allCellsDirty = true;
|
||||
|
||||
WorldViewport lastViewportDistance;
|
||||
readonly float[] availableZoomSteps = new[] { 2f, 1f, 0.5f, 0.25f };
|
||||
|
||||
float zoom = 1f;
|
||||
float minZoom = 1f;
|
||||
float maxZoom = 2f;
|
||||
|
||||
bool unlockMinZoom;
|
||||
float unlockedMinZoomScale;
|
||||
float unlockedMinZoom = 1f;
|
||||
public float[] AvailableZoomSteps
|
||||
{
|
||||
get { return availableZoomSteps; }
|
||||
}
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get => zoom;
|
||||
|
||||
private set
|
||||
get
|
||||
{
|
||||
zoom = value;
|
||||
viewportSize = (1f / zoom * new float2(Game.Renderer.NativeResolution)).ToInt2();
|
||||
return zoom;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
var newValue = ClosestTo(AvailableZoomSteps, value);
|
||||
zoom = newValue;
|
||||
viewportSize = (1f / zoom * new float2(Game.Renderer.Resolution)).ToInt2();
|
||||
cellsDirty = true;
|
||||
allCellsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public float MinZoom => minZoom;
|
||||
|
||||
public void AdjustZoom(float dz)
|
||||
{
|
||||
// Exponential ensures that equal positive and negative steps have the same effect
|
||||
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public void AdjustZoom(float dz, int2 center)
|
||||
{
|
||||
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
AdjustZoom(dz);
|
||||
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
|
||||
CenterLocation += oldCenter - newCenter;
|
||||
}
|
||||
|
||||
public void ToggleZoom()
|
||||
{
|
||||
// Unlocked zooms always reset to the default zoom
|
||||
if (zoom < minZoom)
|
||||
Zoom = minZoom;
|
||||
else
|
||||
Zoom = zoom > minZoom ? minZoom : maxZoom;
|
||||
}
|
||||
|
||||
public void UnlockMinimumZoom(float scale)
|
||||
{
|
||||
unlockMinZoom = true;
|
||||
unlockedMinZoomScale = scale;
|
||||
UpdateViewportZooms(false);
|
||||
}
|
||||
|
||||
public static long LastMoveRunTime = 0;
|
||||
public static int2 LastMousePos;
|
||||
|
||||
@@ -157,8 +119,6 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
worldRenderer = wr;
|
||||
var grid = Game.ModData.Manifest.Get<MapGrid>();
|
||||
viewportSizes = Game.ModData.Manifest.Get<WorldViewportSizes>();
|
||||
graphicSettings = Game.Settings.Graphics;
|
||||
|
||||
// Calculate map bounds in world-px
|
||||
if (wr.World.Type == WorldType.Editor)
|
||||
@@ -180,81 +140,8 @@ namespace OpenRA.Graphics
|
||||
CenterLocation = (tl + br) / 2;
|
||||
}
|
||||
|
||||
Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1;
|
||||
tileSize = grid.TileSize;
|
||||
|
||||
UpdateViewportZooms();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (lastViewportDistance != graphicSettings.ViewportDistance)
|
||||
UpdateViewportZooms();
|
||||
}
|
||||
|
||||
float CalculateMinimumZoom(float minHeight, float maxHeight)
|
||||
{
|
||||
var h = Game.Renderer.NativeResolution.Height;
|
||||
|
||||
// Check the easy case: the native resolution is within the maximum limit
|
||||
// Also catches the case where the user may force a resolution smaller than the minimum window size
|
||||
if (h <= maxHeight)
|
||||
return 1;
|
||||
|
||||
// Find a clean fraction that brings us within the desired range to reduce aliasing
|
||||
var step = 1f;
|
||||
while (true)
|
||||
{
|
||||
var testZoom = 1f;
|
||||
while (true)
|
||||
{
|
||||
var nextZoom = testZoom + step;
|
||||
if (h < minHeight * nextZoom)
|
||||
break;
|
||||
|
||||
testZoom = nextZoom;
|
||||
}
|
||||
|
||||
if (h < maxHeight * testZoom)
|
||||
return testZoom;
|
||||
|
||||
step /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateViewportZooms(bool resetCurrentZoom = true)
|
||||
{
|
||||
lastViewportDistance = graphicSettings.ViewportDistance;
|
||||
|
||||
var vd = graphicSettings.ViewportDistance;
|
||||
if (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native)
|
||||
minZoom = viewportSizes.DefaultScale;
|
||||
else
|
||||
{
|
||||
var range = viewportSizes.GetSizeRange(vd);
|
||||
minZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
||||
}
|
||||
|
||||
maxZoom = Math.Min(minZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
||||
|
||||
if (unlockMinZoom)
|
||||
{
|
||||
// Spectators and the map editor support zooming out by an extra factor of two.
|
||||
// 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
|
||||
// before this will work well enough to enable
|
||||
unlockedMinZoom = minZoom * unlockedMinZoomScale;
|
||||
}
|
||||
|
||||
if (resetCurrentZoom)
|
||||
Zoom = minZoom;
|
||||
else
|
||||
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>())
|
||||
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public CPos ViewToWorld(int2 view)
|
||||
@@ -262,6 +149,7 @@ namespace OpenRA.Graphics
|
||||
var world = worldRenderer.Viewport.ViewToWorldPx(view);
|
||||
var map = worldRenderer.World.Map;
|
||||
var candidates = CandidateMouseoverCells(world).ToList();
|
||||
var tileSet = worldRenderer.World.Map.Rules.TileSet;
|
||||
|
||||
foreach (var uv in candidates)
|
||||
{
|
||||
@@ -270,9 +158,18 @@ namespace OpenRA.Graphics
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height)
|
||||
{
|
||||
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
|
||||
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
var ramp = 0;
|
||||
if (map.Contains(uv))
|
||||
{
|
||||
var ti = tileSet.GetTileInfo(map.Tiles[uv]);
|
||||
if (ti != null)
|
||||
ramp = ti.RampType;
|
||||
}
|
||||
|
||||
var corners = map.Grid.CellCorners[ramp];
|
||||
var pos = map.CenterOfCell(uv.ToCPos(map));
|
||||
var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
|
||||
|
||||
if (screen.PolygonContains(world))
|
||||
return uv.ToCPos(map);
|
||||
}
|
||||
@@ -282,7 +179,7 @@ namespace OpenRA.Graphics
|
||||
// Try and find the closest cell
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
return candidates.MinBy(uv =>
|
||||
return candidates.OrderBy(uv =>
|
||||
{
|
||||
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
|
||||
var s = worldRenderer.ScreenPxPosition(p);
|
||||
@@ -290,7 +187,7 @@ namespace OpenRA.Graphics
|
||||
var dy = Math.Abs(s.Y - world.Y);
|
||||
|
||||
return dx * dx + dy * dy;
|
||||
}).ToCPos(map);
|
||||
}).First().ToCPos(map);
|
||||
}
|
||||
|
||||
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
|
||||
@@ -312,9 +209,8 @@ namespace OpenRA.Graphics
|
||||
yield return new MPos(u, v);
|
||||
}
|
||||
|
||||
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
|
||||
public int2 ViewToWorldPx(int2 view) { return (1f / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
|
||||
public int2 WorldToViewPx(int2 world) { return (Zoom * (world - TopLeft).ToFloat2()).ToInt2(); }
|
||||
|
||||
public void Center(IEnumerable<Actor> actors)
|
||||
{
|
||||
@@ -343,6 +239,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// Rectangle (in viewport coords) that contains things to be drawn
|
||||
static readonly Rectangle ScreenClip = Rectangle.FromLTRB(0, 0, Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height);
|
||||
public Rectangle GetScissorBounds(bool insideBounds)
|
||||
{
|
||||
// Visible rectangle in world coordinates (expanded to the corners of the cells)
|
||||
@@ -352,12 +249,12 @@ namespace OpenRA.Graphics
|
||||
var cbr = map.CenterOfCell(((MPos)bounds.BottomRight).ToCPos(map)) + new WVec(512, 512, 0);
|
||||
|
||||
// Convert to screen coordinates
|
||||
var tl = worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z)) - TopLeft;
|
||||
var br = worldRenderer.ScreenPxPosition(cbr - new WVec(0, 0, cbr.Z)) - TopLeft;
|
||||
var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z))).Clamp(ScreenClip);
|
||||
var br = WorldToViewPx(worldRenderer.ScreenPxPosition(cbr - new WVec(0, 0, cbr.Z))).Clamp(ScreenClip);
|
||||
|
||||
// Add an extra half-cell fudge to avoid clipping isometric tiles
|
||||
return Rectangle.FromLTRB(tl.X - tileSize.Width / 2, tl.Y - tileSize.Height / 2,
|
||||
br.X + tileSize.Width / 2, br.Y + tileSize.Height / 2);
|
||||
// Add an extra one cell fudge in each direction for safety
|
||||
return Rectangle.FromLTRB(tl.X - tileSize.Width, tl.Y - tileSize.Height,
|
||||
br.X + tileSize.Width, br.Y + tileSize.Height);
|
||||
}
|
||||
|
||||
ProjectedCellRegion CalculateVisibleCells(bool insideBounds)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -20,14 +20,14 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class WorldRenderer : IDisposable
|
||||
{
|
||||
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
|
||||
r => r.Pos.Y + r.Pos.Z + r.ZOffset;
|
||||
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
|
||||
r => ZPosition(r.Pos, r.ZOffset);
|
||||
|
||||
public readonly Size TileSize;
|
||||
public readonly int TileScale;
|
||||
public readonly World World;
|
||||
public Viewport Viewport { get; }
|
||||
public readonly ITerrainLighting TerrainLighting;
|
||||
public readonly Theater Theater;
|
||||
public Viewport Viewport { get; private set; }
|
||||
|
||||
public event Action PaletteInvalidated = null;
|
||||
|
||||
@@ -39,11 +39,7 @@ namespace OpenRA.Graphics
|
||||
readonly Func<string, PaletteReference> createPaletteReference;
|
||||
readonly bool enableDepthBuffer;
|
||||
|
||||
readonly List<IFinalizedRenderable> preparedRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
|
||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
|
||||
|
||||
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
|
||||
bool lastDepthPreviewEnabled;
|
||||
|
||||
internal WorldRenderer(ModData modData, World world)
|
||||
{
|
||||
@@ -65,7 +61,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
palette.Initialize();
|
||||
|
||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||
Theater = new Theater(world.Map.Rules.TileSet);
|
||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||
|
||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
||||
@@ -83,13 +79,7 @@ namespace OpenRA.Graphics
|
||||
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
|
||||
}
|
||||
|
||||
public PaletteReference Palette(string name)
|
||||
{
|
||||
// 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 PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
|
||||
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
|
||||
{
|
||||
if (allowOverwrite && palette.Contains(name))
|
||||
@@ -99,8 +89,8 @@ namespace OpenRA.Graphics
|
||||
var oldHeight = palette.Height;
|
||||
palette.AddPalette(name, pal, allowModifiers);
|
||||
|
||||
if (oldHeight != palette.Height)
|
||||
PaletteInvalidated?.Invoke();
|
||||
if (oldHeight != palette.Height && PaletteInvalidated != null)
|
||||
PaletteInvalidated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,138 +103,61 @@ namespace OpenRA.Graphics
|
||||
palettes[name].Palette = pal;
|
||||
}
|
||||
|
||||
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
|
||||
List<IFinalizedRenderable> GenerateRenderables()
|
||||
{
|
||||
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateRenderables()
|
||||
{
|
||||
foreach (var actor in onScreenActors)
|
||||
renderablesBuffer.AddRange(actor.Render(this));
|
||||
|
||||
renderablesBuffer.AddRange(World.WorldActor.Render(this));
|
||||
|
||||
var actors = onScreenActors.Append(World.WorldActor);
|
||||
if (World.RenderPlayer != null)
|
||||
renderablesBuffer.AddRange(World.RenderPlayer.PlayerActor.Render(this));
|
||||
actors = actors.Append(World.RenderPlayer.PlayerActor);
|
||||
|
||||
var worldRenderables = actors.SelectMany(a => a.Render(this));
|
||||
if (World.OrderGenerator != null)
|
||||
renderablesBuffer.AddRange(World.OrderGenerator.Render(this, World));
|
||||
worldRenderables = worldRenderables.Concat(World.OrderGenerator.Render(this, World));
|
||||
|
||||
// Unpartitioned effects
|
||||
foreach (var e in World.UnpartitionedEffects)
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
worldRenderables = worldRenderables.Concat(World.UnpartitionedEffects.SelectMany(e => e.Render(this)));
|
||||
|
||||
// Partitioned, currently on-screen effects
|
||||
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
|
||||
renderablesBuffer.AddRange(e.Render(this));
|
||||
var effectRenderables = World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
|
||||
worldRenderables = worldRenderables.Concat(effectRenderables.SelectMany(e => e.Render(this)));
|
||||
|
||||
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
|
||||
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
|
||||
preparedRenderables.Add(renderable.PrepareRender(this));
|
||||
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
|
||||
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
renderablesBuffer.Clear();
|
||||
Game.Renderer.WorldModelRenderer.BeginFrame();
|
||||
var renderables = worldRenderables.Select(r => r.PrepareRender(this)).ToList();
|
||||
Game.Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
return renderables;
|
||||
}
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateOverlayRenderables()
|
||||
List<IFinalizedRenderable> GenerateOverlayRenderables()
|
||||
{
|
||||
World.ApplyToActorsWithTrait<IRenderAboveShroud>((actor, trait) =>
|
||||
{
|
||||
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
|
||||
return;
|
||||
var aboveShroud = World.ActorsWithTrait<IRenderAboveShroud>()
|
||||
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
|
||||
.SelectMany(a => a.Trait.RenderAboveShroud(a.Actor, this));
|
||||
|
||||
foreach (var renderable in trait.RenderAboveShroud(actor, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
});
|
||||
var aboveShroudSelected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
|
||||
.SelectMany(a => a.TraitsImplementing<IRenderAboveShroudWhenSelected>()
|
||||
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
|
||||
.SelectMany(t => t.RenderAboveShroud(a, this)));
|
||||
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAboveShroudWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderable in t.RenderAboveShroud(a, this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
if (!(e is IEffectAboveShroud ea))
|
||||
continue;
|
||||
|
||||
foreach (var renderable in ea.RenderAboveShroud(this))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
var aboveShroudEffects = World.Effects.Select(e => e as IEffectAboveShroud)
|
||||
.Where(e => e != null)
|
||||
.SelectMany(e => e.RenderAboveShroud(this));
|
||||
|
||||
var aboveShroudOrderGenerator = SpriteRenderable.None;
|
||||
if (World.OrderGenerator != null)
|
||||
foreach (var renderable in World.OrderGenerator.RenderAboveShroud(this, World))
|
||||
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
|
||||
}
|
||||
aboveShroudOrderGenerator = World.OrderGenerator.RenderAboveShroud(this, World);
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
void GenerateAnnotationRenderables()
|
||||
{
|
||||
World.ApplyToActorsWithTrait<IRenderAnnotations>((actor, trait) =>
|
||||
{
|
||||
if (!actor.IsInWorld || actor.Disposed || (trait.SpatiallyPartitionable && !onScreenActors.Contains(actor)))
|
||||
return;
|
||||
var overlayRenderables = aboveShroud
|
||||
.Concat(aboveShroudSelected)
|
||||
.Concat(aboveShroudEffects)
|
||||
.Concat(aboveShroudOrderGenerator);
|
||||
|
||||
foreach (var renderAnnotation in trait.RenderAnnotations(actor, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
});
|
||||
Game.Renderer.WorldModelRenderer.BeginFrame();
|
||||
var finalOverlayRenderables = overlayRenderables.Select(r => r.PrepareRender(this)).ToList();
|
||||
Game.Renderer.WorldModelRenderer.EndFrame();
|
||||
|
||||
foreach (var a in World.Selection.Actors)
|
||||
{
|
||||
if (!a.IsInWorld || a.Disposed)
|
||||
continue;
|
||||
|
||||
foreach (var t in a.TraitsImplementing<IRenderAnnotationsWhenSelected>())
|
||||
{
|
||||
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in t.RenderAnnotations(a, this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in World.Effects)
|
||||
{
|
||||
if (!(e is IEffectAnnotation ea))
|
||||
continue;
|
||||
|
||||
foreach (var renderAnnotation in ea.RenderAnnotation(this))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
if (World.OrderGenerator != null)
|
||||
foreach (var renderAnnotation in World.OrderGenerator.RenderAnnotations(this, World))
|
||||
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
|
||||
}
|
||||
|
||||
public void PrepareRenderables()
|
||||
{
|
||||
if (World.WorldActor.Disposed)
|
||||
return;
|
||||
|
||||
RefreshPalette();
|
||||
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
onScreenActors.UnionWith(World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight));
|
||||
|
||||
GenerateRenderables();
|
||||
GenerateOverlayRenderables();
|
||||
GenerateAnnotationRenderables();
|
||||
|
||||
onScreenActors.Clear();
|
||||
return finalOverlayRenderables;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
@@ -252,96 +165,87 @@ namespace OpenRA.Graphics
|
||||
if (World.WorldActor.Disposed)
|
||||
return;
|
||||
|
||||
debugVis.Value?.UpdateDepthBuffer();
|
||||
if (debugVis.Value != null && lastDepthPreviewEnabled != debugVis.Value.DepthBuffer)
|
||||
{
|
||||
lastDepthPreviewEnabled = debugVis.Value.DepthBuffer;
|
||||
Game.Renderer.WorldSpriteRenderer.SetDepthPreviewEnabled(lastDepthPreviewEnabled);
|
||||
}
|
||||
|
||||
RefreshPalette();
|
||||
|
||||
onScreenActors.UnionWith(World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight));
|
||||
var renderables = GenerateRenderables();
|
||||
var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
|
||||
Game.Renderer.EnableScissor(bounds);
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.EnableDepthBuffer();
|
||||
|
||||
terrainRenderer?.RenderTerrain(this, Viewport);
|
||||
if (terrainRenderer != null)
|
||||
terrainRenderer.RenderTerrain(this, Viewport);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
|
||||
for (var i = 0; i < preparedRenderables.Count; i++)
|
||||
preparedRenderables[i].Render(this);
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
renderables[i].Render(this);
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
||||
{
|
||||
if (actor.IsInWorld && !actor.Disposed)
|
||||
trait.RenderAboveWorld(actor, this);
|
||||
});
|
||||
foreach (var a in World.ActorsWithTrait<IRenderAboveWorld>())
|
||||
if (a.Actor.IsInWorld && !a.Actor.Disposed)
|
||||
a.Trait.RenderAboveWorld(a.Actor, this);
|
||||
|
||||
var renderShroud = World.RenderPlayer != null ? World.RenderPlayer.Shroud : null;
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.ClearDepthBuffer();
|
||||
|
||||
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
||||
foreach (var a in World.ActorsWithTrait<IRenderShroud>())
|
||||
a.Trait.RenderShroud(renderShroud, this);
|
||||
|
||||
if (enableDepthBuffer)
|
||||
Game.Renderer.Context.DisableDepthBuffer();
|
||||
|
||||
Game.Renderer.DisableScissor();
|
||||
|
||||
var finalOverlayRenderables = GenerateOverlayRenderables();
|
||||
|
||||
// HACK: Keep old grouping behaviour
|
||||
var groupedOverlayRenderables = preparedOverlayRenderables.GroupBy(prs => prs.GetType());
|
||||
var groupedOverlayRenderables = finalOverlayRenderables.GroupBy(prs => prs.GetType());
|
||||
foreach (var g in groupedOverlayRenderables)
|
||||
foreach (var r in g)
|
||||
r.Render(this);
|
||||
|
||||
Game.Renderer.Flush();
|
||||
}
|
||||
|
||||
public void DrawAnnotations()
|
||||
{
|
||||
Game.Renderer.EnableAntialiasingFilter();
|
||||
for (var i = 0; i < preparedAnnotationRenderables.Count; i++)
|
||||
preparedAnnotationRenderables[i].Render(this);
|
||||
Game.Renderer.DisableAntialiasingFilter();
|
||||
|
||||
// Engine debugging overlays
|
||||
if (debugVis.Value != null && debugVis.Value.RenderGeometry)
|
||||
{
|
||||
for (var i = 0; i < preparedRenderables.Count; i++)
|
||||
preparedRenderables[i].RenderDebugGeometry(this);
|
||||
for (var i = 0; i < renderables.Count; i++)
|
||||
renderables[i].RenderDebugGeometry(this);
|
||||
|
||||
for (var i = 0; i < preparedOverlayRenderables.Count; i++)
|
||||
preparedOverlayRenderables[i].RenderDebugGeometry(this);
|
||||
|
||||
for (var i = 0; i < preparedAnnotationRenderables.Count; i++)
|
||||
preparedAnnotationRenderables[i].RenderDebugGeometry(this);
|
||||
foreach (var g in groupedOverlayRenderables)
|
||||
foreach (var r in g)
|
||||
r.RenderDebugGeometry(this);
|
||||
}
|
||||
|
||||
if (debugVis.Value != null && debugVis.Value.ScreenMap)
|
||||
{
|
||||
foreach (var r in World.ScreenMap.RenderBounds(World.RenderPlayer))
|
||||
{
|
||||
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
|
||||
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
||||
}
|
||||
Game.Renderer.WorldRgbaColorRenderer.DrawRect(
|
||||
new float3(r.Left, r.Top, r.Bottom),
|
||||
new float3(r.Right, r.Bottom, r.Bottom),
|
||||
1 / Viewport.Zoom, Color.MediumSpringGreen);
|
||||
|
||||
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
{
|
||||
var points = new float2[b.Vertices.Length];
|
||||
for (var index = 0; index < b.Vertices.Length; index++)
|
||||
{
|
||||
var vertex = b.Vertices[index];
|
||||
points[index] = Viewport.WorldToViewPx(vertex).ToFloat2();
|
||||
}
|
||||
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
|
||||
}
|
||||
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
Game.Renderer.WorldRgbaColorRenderer.DrawRect(
|
||||
new float3(r.Left, r.Top, r.Bottom),
|
||||
new float3(r.Right, r.Bottom, r.Bottom),
|
||||
1 / Viewport.Zoom, Color.OrangeRed);
|
||||
}
|
||||
|
||||
Game.Renderer.Flush();
|
||||
|
||||
preparedRenderables.Clear();
|
||||
preparedOverlayRenderables.Clear();
|
||||
preparedAnnotationRenderables.Clear();
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
onScreenActors.Clear();
|
||||
}
|
||||
|
||||
public void RefreshPalette()
|
||||
@@ -358,13 +262,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public float3 Screen3DPosition(WPos pos)
|
||||
{
|
||||
// The projection from world coordinates to screen coordinates has
|
||||
// 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;
|
||||
var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
|
||||
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
|
||||
}
|
||||
|
||||
@@ -383,7 +281,7 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// For scaling vectors to pixel sizes in the model renderer
|
||||
public float3 ScreenVectorComponents(in WVec vec)
|
||||
public float3 ScreenVectorComponents(WVec vec)
|
||||
{
|
||||
return new float3(
|
||||
(float)TileSize.Width * vec.X / TileScale,
|
||||
@@ -392,19 +290,29 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// 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);
|
||||
return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
|
||||
}
|
||||
|
||||
public int2 ScreenPxOffset(in WVec vec)
|
||||
public int2 ScreenPxOffset(WVec vec)
|
||||
{
|
||||
// Round to nearest pixel
|
||||
var xyz = ScreenVectorComponents(vec);
|
||||
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>
|
||||
/// 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.
|
||||
@@ -423,6 +331,7 @@ namespace OpenRA.Graphics
|
||||
World.Dispose();
|
||||
|
||||
palette.Dispose();
|
||||
Theater.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -20,9 +20,6 @@ namespace OpenRA
|
||||
public readonly Hotkey Default = Hotkey.Invalid;
|
||||
public readonly string Description = "";
|
||||
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 HotkeyDefinition(string name, MiniYaml node)
|
||||
{
|
||||
@@ -38,22 +35,6 @@ namespace OpenRA
|
||||
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
|
||||
if (typesNode != null)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -35,12 +35,9 @@ namespace OpenRA
|
||||
|
||||
foreach (var kv in settings)
|
||||
{
|
||||
if (definitions.ContainsKey(kv.Key) && !definitions[kv.Key].Readonly)
|
||||
if (definitions.ContainsKey(kv.Key))
|
||||
keys[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
foreach (var hd in definitions)
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
|
||||
}
|
||||
|
||||
internal Func<Hotkey> GetHotkeyReference(string name)
|
||||
@@ -50,7 +47,8 @@ namespace OpenRA
|
||||
return () => keys[name];
|
||||
|
||||
// Try and parse as a hardcoded definition
|
||||
if (!Hotkey.TryParse(name, out var key))
|
||||
Hotkey key;
|
||||
if (!Hotkey.TryParse(name, out key))
|
||||
key = Hotkey.Invalid;
|
||||
|
||||
return () => key;
|
||||
@@ -58,10 +56,8 @@ namespace OpenRA
|
||||
|
||||
public void Set(string name, Hotkey value)
|
||||
{
|
||||
if (!definitions.TryGetValue(name, out var definition))
|
||||
return;
|
||||
|
||||
if (definition.Readonly)
|
||||
HotkeyDefinition definition;
|
||||
if (!definitions.TryGetValue(name, out definition))
|
||||
return;
|
||||
|
||||
keys[name] = value;
|
||||
@@ -69,41 +65,30 @@ namespace OpenRA
|
||||
settings[name] = value;
|
||||
else
|
||||
settings.Remove(name);
|
||||
|
||||
var hadDuplicates = definition.HasDuplicates;
|
||||
definition.HasDuplicates = GetFirstDuplicate(definition, this[definition.Name].GetValue()) != null;
|
||||
|
||||
if (hadDuplicates || definition.HasDuplicates)
|
||||
{
|
||||
foreach (var hd in definitions)
|
||||
{
|
||||
if (hd.Value == definition)
|
||||
continue;
|
||||
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != 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)
|
||||
{
|
||||
if (kv.Key == definition.Name)
|
||||
if (kv.Key == name)
|
||||
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 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; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -9,11 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public readonly struct Hotkey : IEquatable<Hotkey>
|
||||
public struct Hotkey
|
||||
{
|
||||
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
|
||||
public bool IsValid()
|
||||
@@ -32,9 +30,11 @@ namespace OpenRA
|
||||
|
||||
var parts = s.Split(' ');
|
||||
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out var key))
|
||||
Keycode key;
|
||||
if (!Enum<Keycode>.TryParse(parts[0], true, out key))
|
||||
{
|
||||
if (!int.TryParse(parts[0], out var c))
|
||||
int c;
|
||||
if (!int.TryParse(parts[0], out c))
|
||||
return false;
|
||||
key = (Keycode)c;
|
||||
}
|
||||
@@ -74,17 +74,13 @@ namespace OpenRA
|
||||
|
||||
public override int GetHashCode() { return Key.GetHashCode() ^ Modifiers.GetHashCode(); }
|
||||
|
||||
public bool Equals(Hotkey other)
|
||||
{
|
||||
return other == this;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* Copyright 2007-2019 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
|
||||
@@ -37,24 +37,36 @@ namespace OpenRA
|
||||
|
||||
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)
|
||||
{
|
||||
Sync.RunUnsynced(world, () => Ui.HandleTextInput(text));
|
||||
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user