Compare commits

..

77 Commits

Author SHA1 Message Date
Paul Chote
253e02ae08 Remove dependency on VCRUNTIME140.dll.
(cherry picked from commit bb6400f6f8585523c31dfeb7ab95073263aadd3c)
2023-02-13 21:27:35 +02:00
penev92
f707b9c975 Fixed Makefile check of MacOS architecture 2023-02-05 00:19:18 +01:00
abcdefg30
8629e578e3 Throw an ArgumentException when trying to translate null keys
(cherry picked from commit e4bb13ea07)
2023-02-03 08:36:24 +02:00
abcdefg30
c65cfd3528 Fix an NRE in ConnectionLogic
(cherry picked from commit 84add8a03d)
2023-02-03 08:34:57 +02:00
Azarus
7ad6cfda40 Add an option to check if an actor can be captured via Lua 2023-01-30 13:47:02 +02:00
Dean Simmons
393fd585b1 Only return file paths when returning zip contents
(cherry picked from commit e1b78c4821)
2023-01-26 19:26:59 +02:00
penev92
ea221f9bfb Fixed RA parabomb/paradrop support power tooltips
The AFLD's AirstrikePower@parabombs' description wasn't updated to the latest balance patch (3 Badgers -> 1 Badger)
2023-01-26 09:32:54 +01:00
N.N
b4ca81d1bd Fix deviator GrantExternalCondition Warhead
Warhead now applies only to enemy and neutral units.
2023-01-18 23:43:12 +01:00
RoosterDragon
49590bb5f7 Fix Locomotor cache coherency for disabled or paused mobile actors.
The cache in Locomotor that is populated via the UpdateCellBlocking method disagreed with the non-cached logic of IsBlockedBy when dealing with Mobile actors. The cache determined an actor to be moving if it was both movable and had horizontal movement types. IsBlockedBy determined an actor to be moving if it had horizontal movement types, but did not check if it was movable. This difference in checks could allow a mobile trait that was disabled or paused and which had horizontal movement to be treated differently be the two methods. UpdateCellBlocking would consider it not moving due to the disabled/paused trait. IsBlockedBy would consider it moving as it didn't care about the disabled/paused state of the trait.

Now, we unify the two methods to consider a mobile trait that is disabled/paused as not moving. This prevents HierarchicalPathFinder from crashing on the inconsistent state, i.e. when asked to path search through a cell of a mobile unit which has disabled or paused movement, but which has horizontal movement types from prior movement.
2023-01-17 18:58:34 +01:00
David Wilson
2d059c0d95 Fix to ensure Windows uninstaller removes reg entries from the 64 bit registry 2023-01-14 13:50:04 +01:00
penev92
d5dd6a9086 Added ITilesetSpecificPaletteInfo for linting 2023-01-10 16:37:56 +00:00
Gustas
a7ca0f0699 Fixed UnhardcodeBaseBuilderBotModule update rule
Update rules should should not read `modData.DefaultRules`

(cherry picked from commit 80b92fb667)
2023-01-10 18:13:01 +02:00
Gustas
4b02d9b700 Fixed UnhardcodeSquadManager update rule
Update rules should should not read `modData.DefaultRules`

(cherry picked from commit 29d21545a6)
2023-01-10 18:12:58 +02:00
Gustas
ebf55c8d86 Exclude Plugs from TS protection types
(cherry picked from commit 326f8115a0)
2023-01-10 18:12:51 +02:00
Paul Chote
f928922f3a Add BeforeUpdate* methods for update rules.
These make it possible to write more advanced update
rules that query state across multiple actors, or
based on resolved state.

(cherry picked from commit 129db98a2f)
2023-01-10 18:05:11 +02:00
penev92
bc3743d9ff Added an update rule for adding ControlGroups 2023-01-10 00:14:45 +01:00
penev92
8aa6600655 Added an update rule for DomainIndex removal
Also for adding the new HPF-related PathFinderOverlay and HierarchicalPathFinderOverlay that were added at the same time.
2023-01-10 00:14:04 +01:00
Ivaylo Draganov
215b3d8ab3 Clarify the wording and explain the meaning of some labels in settings
(cherry picked from commit 04648a66e6)
2023-01-09 21:59:41 +02:00
Matthias Mailänder
a397670dce Fix URL button blocking the party button. 2023-01-08 23:07:44 +01:00
Matthias Mailänder
43d7f9c6df Bump DiscordRichPresence library. 2023-01-08 23:07:42 +01:00
Gustas
6904ea8c6b Fixed a spelling error in Discovery ant mission 2023-01-07 17:02:19 +01:00
Gustas
d702d58d1a Remove bogus bridge tiles from temperate tileset 2023-01-07 17:02:18 +01:00
abcdefg30
e0b0c5624f Remove custom network frame counting from ReplayConnection 2023-01-04 09:35:06 +00:00
abcdefg30
33773f895c Specify targetable offsets for the repair pad in Dune 2000 2023-01-03 13:33:20 +01:00
Matthias Mailänder
9f759f3cc2 Enforce use of 'var' instead of explicit type.
(cherry picked from commit 19ecddcd86)
2022-12-29 10:05:20 +02:00
Paul Chote
9fb4302b2f Downgrade OpenAL-soft to 1.10.1.
1.12.2 introduces noticeable behaviour changes
to source positioning and cash ticks. This also
fixes missing sound backends on Linux.
2022-12-28 00:19:39 +01:00
abcdefg30
4685c1a6b1 Change into the correct directory before trying to push
Each workflow step starts back in the default directory,
so we need to checkout the repository folder again
2022-12-24 16:04:51 +01:00
abcdefg30
70bc5b097d Let the documentation workflow fetch the required git branch 2022-12-24 16:04:45 +01:00
Paul Chote
7691507baf Fix pseudo-fullscreen window size on macs with a notch. 2022-12-24 12:12:12 +01:00
abcdefg30
8d6cebe654 Fix map actors not being spawned with the correct owner 2022-12-24 11:20:58 +02:00
Matthias Mailänder
daf10c34a6 Fix invalid actors not spawning in-game. 2022-12-23 18:33:50 +01:00
Matthias Mailänder
d96ec21b95 In map editor replace invalid actor owners with neutral. 2022-12-23 18:33:49 +01:00
Paul Chote
aea1182bb5 Implement state prediction for lobby ready checkbox. 2022-12-23 16:34:05 +02:00
Paul Chote
b413b97a52 Implement state prediction for debug menu checkboxes. 2022-12-23 16:34:05 +02:00
Paul Chote
7daa27f123 Implement state prediction for lobby checkboxes. 2022-12-23 16:34:05 +02:00
Paul Chote
f3f98d8750 Package macOS releases as a universal binary.
* Minimum macOS version is raised to 10.11.
* App bundles ship 3 versions of the runtime and engine binaries,
  and a fat launcher that selects the appropriate runtime/apphost.
* Mono is used for macOS 10.11 - 10.14, or if OPENRA_PREFER_MONO
  environment variable has been set.
2022-12-23 12:55:53 +02:00
Paul Chote
69eeb2dc84 Update native deps to include macOS and Linux arm64 binaries. 2022-12-23 12:55:53 +02:00
Paul Chote
7cee29ff70 Report CPU arch in logs and sysinfo. 2022-12-23 12:54:29 +02:00
Paul Chote
5e80fee913 Fix MiniYaml source locations being lost when merging. 2022-12-23 10:45:24 +02:00
Gustas
c1474204e2 Added carryalls to spectator Economy statistics 2022-12-21 21:09:16 +01:00
N.N
223f9408fd Change tile 9 to Rough TerrainType 2022-12-21 19:57:19 +02:00
Gustas
674ca8555b Fix contrail using the wrong colors 2022-12-21 19:36:12 +02:00
abcdefg30
275325b0cf Run the CI workflow on PRs targeting prep 2022-12-21 13:28:48 +02:00
Paul Chote
06e399d3ce Bubble unhandled double-click events to OnClick. 2022-12-20 23:06:19 +01:00
RoosterDragon
d6d77eab7c HPF is aware of map projection changes.
An event is added to Map to indicate when the cell projection is changed. This is important as this can mean Map.Contains(CPos) could now return different results for the cell. The HierarchicalPathFinder is made aware of these changes so it can rebuild any out-of-date information. This fixes prevent a crash if a cell that was previously outside the map changes height and becomes inside the map. The local path search will explore the cell as it is inside the map - but if the HPF was unaware if had been updated, it will still consider the cell to be outside the map and unreachable, resulting in a crash.
2022-12-20 09:48:44 +13:00
RoosterDragon
2392566c1d HPF handles searches from unreachable source cells into cut off areas.
If a path search is performed by the HierarchicalPathFinder when the source cell is unreachable location, a path is still allowed and starts from one of the cells adjacent to the location. If moving into one of these cells results in the actor moving into an isolated area that cannot reach the target this would previously crash as no abstract path could be found. Now we handle such locations by giving them a unreachable cost so the path search will not attempt to explore them.

Imagine a map split into two by a one tile wide line of impassable cliffs. If an actor is on top of these cliffs it is allowed to path because it can "jump off" the cliff and move into the cell immediately either side of it. However it is important which side it chooses to jump into, as one it has moved off the cliff it loses access to the other side. The previous error came about because the path might try and search on the side that couldn't reach the target location. Now we avoid that being considered.
2022-12-20 09:48:36 +13:00
Matthias Mailänder
028467f150 Fix documentation deployment. 2022-12-18 12:57:21 +13:00
abcdefg30
eee4b04248 Fix the actor edit panel not always getting closed properly 2022-12-18 12:25:21 +13:00
Gustas
f551b542f3 Fix map editor sliders stealing focus 2022-12-15 21:06:25 +01:00
Smittytron
5d5419702b Remove MustDestroy from longbows in Soviet 11 2022-12-10 16:16:24 +02:00
Smittytron
47e89b7d76 Fix Siberian Conflict 3 win trigger 2022-12-10 16:16:13 +02:00
N.N
9bb0409f19 Update D2k map pool 2022-12-07 23:39:44 +01:00
abcdefg30
80ac494948 Fix carryables vanishing when another unit is picked up 2022-12-04 17:41:44 +02:00
abcdefg30
eb82f2b975 Remove a stray $ from infront of the version string when writing logs 2022-12-03 23:49:24 +02:00
abcdefg30
a5c8db82da Mark the script as executable 2022-12-02 23:32:41 +01:00
abcdefg30
2a26ff6818 Revert "Update Ubuntu"
This reverts commit ac351f6e65.
2022-12-02 23:32:39 +01:00
Matthias Mailänder
5a11d54956 Execute self-contained binaries with runtime as backfall 2022-12-02 23:03:58 +01:00
Gustas
26c5a8b76c Add cell reference HPF crash messages 2022-12-02 17:50:13 +01:00
Matthias Mailänder
efcfab78dc Unify file extensions. 2022-12-01 19:54:07 +02:00
Matthias Mailänder
ac351f6e65 Update Ubuntu 2022-12-01 19:54:07 +02:00
abcdefg30
42bdc9e53e Replace influence instead of throwing an exception in AddInfluence 2022-12-01 18:13:16 +01:00
Gustas
3097efc5b6 Fix UnreserveCarryable unreserving carryables of other carriers 2022-12-01 18:13:16 +01:00
Gustas
853422409f Fix AutoCaryall not unreserving when cargo dies 2022-12-01 18:13:16 +01:00
abcdefg30
f097250394 Make sure PickupUnit runs a TakeOff activity before ending 2022-12-01 18:13:16 +01:00
Paul Chote
96e0f96c12 Remove redundant order copies. 2022-11-27 23:00:53 +01:00
Paul Chote
0a4c4162be Always serialize orders. 2022-11-27 23:00:52 +01:00
Paul Chote
10ff24f24e Fix frame number not being included in pre-serialized order packets. 2022-11-27 23:00:51 +01:00
Matthias Mailänder
aeb96d98f3 Update notarisation to XCode 13 tooling. 2022-11-27 20:32:57 +01:00
Paul Chote
cf4bfdcdfb Fix display bounds when running on macbooks with a notch. 2022-11-26 20:45:22 +01:00
dnqbob
d55af8d75d HarvesterBotModule should not command harvesters that cannot be ordered 2022-11-24 23:22:04 +01:00
Matthias Mailänder
dd9e75d017 Show the host in the download failed error message. 2022-11-23 23:36:10 +01:00
Matthias Mailänder
1b6220962e Fix unknown host not getting translated. 2022-11-23 23:36:08 +01:00
Vapre
f70f2acb39 ExceptionHandler, fix npe. 2022-11-22 12:35:33 +01:00
Matthias Mailänder
32e2507bff Revert "Scripts: Check exit status of background process"
This reverts commit 3f106bef72.
2022-11-20 20:36:58 +02:00
Matthias Mailänder
22a42b7dc9 Manually add game speeds to the linter. 2022-11-18 23:14:54 +01:00
Gustas
c01e4043e8 Introduce MinDistance to AreaBeam projectile 2022-11-17 20:51:28 +01:00
Thomas Christlieb
c8665c98a6 fix misclicks through sidebar 2022-11-16 23:22:39 +01:00
2928 changed files with 58860 additions and 83697 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,41 +5,37 @@ on:
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
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Check Code
run: |
make check
make tests
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make TREAT_WARNINGS_AS_ERRORS=true test
make test
linux-mono:
name: Linux (mono)
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Check Code
run: |
@@ -49,7 +45,7 @@ jobs:
- name: Check Mods
run: |
# check-scripts does not depend on .net/mono, so is not needed here
make RUNTIME=mono TREAT_WARNINGS_AS_ERRORS=true test
make RUNTIME=mono test
windows:
name: Windows (.NET 6.0)
@@ -57,10 +53,10 @@ jobs:
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
@@ -70,12 +66,12 @@ jobs:
# 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
.\make.ps1 tests
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: |
choco install lua --version 5.1.5.52
chocolatey install lua --version 5.1.5.52
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
$ENV:TREAT_WARNINGS_AS_ERRORS = "true"
.\make.ps1 check-scripts
.\make.ps1 test

View File

@@ -1,9 +1,6 @@
name: Deploy Documentation
on:
push:
branches: [ bleed ]
tags: [ 'release-*', 'playtest-*' ]
workflow_dispatch:
inputs:
tag:
@@ -11,53 +8,19 @@ on:
required: true
default: 'release-xxxxxxxx'
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
prepare:
name: Prepare version strings
if: github.repository == 'openra/openra'
runs-on: ubuntu-22.04
steps:
- name: Prepare environment variables
run: |
if [ "${{ github.event_name }}" = "push" ]; then
if [ "${{ github.ref_type }}" = "tag" ]; then
VERSION_TYPE=`echo "${GITHUB_REF#refs/tags/}" | cut -d"-" -f1`
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
else
echo "GIT_TAG=bleed" >> $GITHUB_ENV
echo "VERSION_TYPE=bleed" >> $GITHUB_ENV
fi
else
VERSION_TYPE=`echo "${{ github.event.inputs.tag }}" | cut -d"-" -f1`
echo "GIT_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
echo "VERSION_TYPE=$VERSION_TYPE" >> $GITHUB_ENV
fi
outputs:
git_tag: ${{ env.GIT_TAG }}
version_type: ${{ env.VERSION_TYPE }}
wiki:
name: Update Wiki
needs: prepare
if: github.repository == 'openra/openra' && needs.prepare.outputs.version_type != 'bleed'
runs-on: ubuntu-22.04
if: github.repository == 'openra/openra'
runs-on: ubuntu-20.04
steps:
- name: Debug output
run: |
echo ${{ needs.prepare.outputs.git_tag }}
echo ${{ needs.prepare.outputs.version_type }}
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
ref: ${{ needs.prepare.outputs.git_tag }}
ref: ${{ github.event.inputs.tag }}
- name: Install .NET 6
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
@@ -66,53 +29,49 @@ jobs:
make all
- name: Clone Wiki
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
repository: openra/openra.wiki
token: ${{ secrets.DOCS_TOKEN }}
path: wiki
- name: Update Wiki (Playtest)
if: startsWith(needs.prepare.outputs.git_tag, 'playtest-')
if: startsWith(github.event.inputs.tag, 'playtest-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings (playtest).md"
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
- name: Update Wiki (Release)
if: startsWith(needs.prepare.outputs.git_tag, 'release-')
if: startsWith(github.event.inputs.tag, 'release-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --settings-docs "${{ needs.prepare.outputs.git_tag }}" > "wiki/Settings.md"
./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 status
git diff-index --quiet HEAD || \
(
git add --all && \
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
git push origin master
)
git add --all
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
git push origin master
docs:
name: Update docs.openra.net
needs: prepare
if: github.repository == 'openra/openra'
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Debug output
run: |
echo ${{ needs.prepare.outputs.git_tag }}
echo ${{ needs.prepare.outputs.version_type }}
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
ref: ${{ needs.prepare.outputs.git_tag }}
ref: ${{ github.event.inputs.tag }}
- name: Install .NET 6
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
@@ -120,31 +79,62 @@ jobs:
run: |
make all
# version_type is release/playtest/bleed - the name of the target branch.
- name: Clone docs.openra.net
uses: actions/checkout@v4
- name: Clone docs.openra.net (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-')
uses: actions/checkout@v2
with:
repository: openra/docs
token: ${{ secrets.DOCS_TOKEN }}
path: docs
ref: ${{ needs.prepare.outputs.version_type }}
ref: playtest
- name: Generate docs files
- name: Clone docs.openra.net (Release)
if: startsWith(github.event.inputs.tag, 'release-')
uses: actions/checkout@v2
with:
repository: openra/docs
token: ${{ secrets.DOCS_TOKEN }}
path: docs
ref: release
- name: Update docs.openra.net (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
./utility.sh all --weapon-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/weapons.md"
./utility.sh all --sprite-sequence-docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/sprite-sequences.md"
./utility.sh all --lua-docs "${{ needs.prepare.outputs.git_tag }}" > "docs/api/lua.md"
./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
- 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 status
git diff-index --quiet HEAD || \
(
git add api/*.md && \
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
git push origin ${{ needs.prepare.outputs.version_type }}
)
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

View File

@@ -8,11 +8,10 @@ on:
required: true
default: 'release-xxxxxxxx'
permissions: {}
jobs:
itch:
name: Deploy to itch.io
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
if: github.repository == 'openra/openra'
steps:
- name: Download Packages

View File

@@ -7,16 +7,13 @@ on:
- 'playtest-*'
- 'devtest-*'
permissions:
contents: write # for release creation (svenstaro/upload-release-action)
jobs:
source:
name: Source Tarball
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
@@ -37,13 +34,13 @@ jobs:
linux:
name: Linux AppImages
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
@@ -53,7 +50,6 @@ jobs:
- name: Package AppImages
run: |
mkdir -p build/linux
sudo apt install libfuse2
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
- name: Upload Packages
@@ -70,10 +66,10 @@ jobs:
runs-on: macos-11
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
@@ -102,13 +98,13 @@ jobs:
windows:
name: Windows Installers
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install .NET 6.0
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'

View File

@@ -74,7 +74,6 @@ Also thanks to:
* Glenn Martin Jensen (Baxxster)
* Gordon Martin (Happy0)
* Guido Lipke (LipkeGu)
* Guillermo Cuenca (Wylli)
* Hervé Matysiak (Herve-M)
* Huw Pascoe
* Ian T. Jacobsen (Smilex)
@@ -120,7 +119,6 @@ Also thanks to:
* Mike Gagné (AngryBirdz)
* Muh
* Mustafa Alperen Seki (MustaphaTR)
* Nathan Nichols (cracksmoka420)
* Neil Shivkar (havok13888)
* Nikolay Fomin (netnazgul)
* Nooze
@@ -150,7 +148,6 @@ Also thanks to:
* Teemu Nieminen (Temeez)
* Thomas Christlieb (ThomasChr)
* Tim Mylemans (gecko)
* Tinix
* Tirili
* Tomas Einarsson (Mesacer)
* Tom van Leth (tovl)

View File

@@ -5,16 +5,15 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<LangVersion>9</LangVersion>
<LangVersion>7.3</LangVersion>
<DebugSymbols>true</DebugSymbols>
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
<OutputPath>$(EngineRootPath)/bin</OutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>$(EngineRootPath)/OpenRA.ruleset</CodeAnalysisRuleSet>
<Nullable>disable</Nullable>
<Product>OpenRA</Product>
<Copyright>Copyright (c) The OpenRA Developers and Contributors</Copyright>
</PropertyGroup>
<PropertyGroup>
@@ -31,12 +30,6 @@
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
Enable only for Debug builds to improve compile-time performance for Release builds -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
@@ -51,10 +44,9 @@
</ItemGroup>
</Target>
<!-- StyleCop/Roslynator -->
<!-- StyleCop -->
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<!-- Roslynator analyzers fail to run under Mono (AD0001) -->
<PackageReference Include="Roslynator.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -17,7 +17,7 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
Linux
=====
.NET 6 or Mono (version 6.12 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.
.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.
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.
@@ -78,6 +78,6 @@ Type `sudo make install` for system-wide installation. Run `sudo make install-li
macOS
=====
[.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0) or [Mono](https://www.mono-project.com/download/stable/#download-mac) (version 6.12 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).
[.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).
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.

View File

@@ -3,7 +3,7 @@
# to compile, run:
# make
#
# to compile using Mono (version 6.12 or greater) instead of .NET 6, run:
# 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:
@@ -92,7 +92,7 @@ endif
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.12."; exit 1)
@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)
@@ -112,10 +112,11 @@ check:
@echo
@echo "Compiling in Debug mode..."
ifeq ($(RUNTIME), mono)
@$(MSBUILD) -t:clean\;build -restore -p:Configuration=Debug -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
# 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
@$(DOTNET) clean -c Debug --nologo --verbosity minimal
@$(DOTNET) build -c Debug -nologo -warnaserror -p:TargetPlatform=$(TARGETPLATFORM)
# 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
@@ -130,7 +131,7 @@ endif
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@find mods/*/maps/ mods/*/scripts/ -iname "*.lua" -print0 | xargs -0n1 luac -p
@find lua/ mods/*/{maps,scripts}/ -iname "*.lua" -print0 | xargs -0n1 luac -p
test: all
@echo
@@ -146,11 +147,6 @@ test: all
@echo "Testing Red Alert mod MiniYAML..."
@./utility.sh ra --check-yaml
tests:
@dotnet build OpenRA.Test/OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=$(TARGETPLATFORM)
@echo
@dotnet test bin/OpenRA.Test.dll --test-adapter-path:.
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
#
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
@@ -178,14 +174,14 @@ help:
@echo 'to compile, run:'
@echo ' make'
@echo
@echo 'to compile using Mono (version 6.12 or greater) instead of .NET 6, run:'
@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
@echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make [RUNTIME=net6] [TREAT_WARNINGS_AS_ERRORS=false] test'
@echo ' make [RUNTIME=net6] test'
@echo
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make [RUNTIME=net6] check'

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -86,7 +86,7 @@ namespace OpenRA.Activities
bool firstRunCompleted;
bool lastRun;
protected Activity()
public Activity()
{
IsInterruptible = true;
ChildHasPriority = true;
@@ -146,22 +146,18 @@ namespace OpenRA.Activities
}
/// <summary>
/// <para>
/// Called every tick to run activity logic. Returns false if the activity should
/// remain active, or true if it is complete. Cancelled activities must ensure they
/// return the actor to a consistent state before returning true.
/// </para>
/// <para>
///
/// Child activities can be queued using QueueChild, and these will be ticked
/// instead of the parent while they are active. Activities that need to run logic
/// in parallel with child activities should set ChildHasPriority to false and
/// manually call TickChildren.
/// </para>
/// <para>
///
/// Queuing one or more child activities and returning true is valid, and causes
/// the activity to be completed immediately (without ticking again) once the
/// children have completed.
/// </para>
/// </summary>
public virtual bool Tick(Actor self)
{
@@ -226,11 +222,10 @@ namespace OpenRA.Activities
}
/// <summary>
/// <para>Prints the activity tree, starting from the top or optionally from a given origin.</para>
/// <para>
/// Prints the activity tree, starting from the top or optionally from a given origin.
///
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
/// </para>
/// </summary>
/// <param name="self">The actor performing this activity.</param>
/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -35,9 +35,6 @@ namespace OpenRA
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
{
/// <summary>Value used to represent an invalid token.</summary>
public const int InvalidConditionToken = -1;
internal readonly struct SyncHash
{
public readonly ISync Trait;
@@ -71,12 +68,7 @@ namespace OpenRA
public IEffectiveOwner EffectiveOwner { get; }
public IOccupySpace OccupiesSpace { get; }
public ITargetable[] Targetables { get; }
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; }
readonly ICrushable[] crushables;
public ICrushable[] Crushables
{
get => crushables ?? throw new InvalidOperationException($"Crushables for {Info.Name} are not initialized.");
}
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
public bool IsIdle => CurrentActivity == null;
public bool IsDead => Disposed || (health != null && health.IsDead);
@@ -86,24 +78,27 @@ namespace OpenRA
public WRot Orientation => facing?.Orientation ?? WRot.None;
sealed class ConditionState
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class ConditionState
{
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
public readonly List<VariableObserverNotifier> Notifiers = new();
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new();
public readonly HashSet<int> Tokens = new HashSet<int>();
}
readonly Dictionary<string, ConditionState> conditionStates = new();
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();
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();
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;
@@ -160,7 +155,6 @@ namespace OpenRA
var targetablesList = new List<ITargetable>();
var targetablePositionsList = new List<ITargetablePositions>();
var syncHashesList = new List<SyncHash>();
var crushablesList = new List<ICrushable>();
foreach (var traitInfo in Info.TraitsInConstructOrder())
{
@@ -187,7 +181,6 @@ namespace OpenRA
{ 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)); }
{ if (trait is ICrushable t) crushablesList.Add(t); }
}
resolveOrders = resolveOrdersList.ToArray();
@@ -202,7 +195,6 @@ namespace OpenRA
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
SyncHashes = syncHashesList.ToArray();
crushables = crushablesList.ToArray();
}
}
@@ -595,7 +587,7 @@ namespace OpenRA
return InvalidConditionToken;
}
/// <summary>Returns whether the specified token is valid for RevokeCondition.</summary>
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
public bool TokenValid(int token)
{
return conditionTokens.ContainsKey(token);
@@ -608,7 +600,8 @@ namespace OpenRA
Lazy<ScriptActorInterface> luaInterface;
public void OnScriptBind(ScriptContext context)
{
luaInterface ??= Exts.Lazy(() => new ScriptActorInterface(context, this));
if (luaInterface == null)
luaInterface = Exts.Lazy(() => new ScriptActorInterface(context, this));
}
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -41,7 +41,7 @@ namespace OpenRA
Bits = (x & 0xFFF) << 20 | (y & 0xFFF) << 8 | layer;
}
public static readonly CPos Zero = new(0, 0, 0);
public static readonly CPos Zero = new CPos(0, 0, 0);
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
@@ -56,7 +56,7 @@ namespace OpenRA
public override int GetHashCode() { return Bits.GetHashCode(); }
public bool Equals(CPos other) { return Bits == other.Bits; }
public override bool Equals(object obj) { return obj is CPos cell && Equals(cell); }
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
public override string ToString()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,7 +22,7 @@ namespace OpenRA
public readonly int X, Y;
public CVec(int x, int y) { X = x; Y = y; }
public static readonly CVec Zero = new(0, 0);
public static readonly CVec Zero = new CVec(0, 0);
public static CVec operator +(CVec a, CVec b) { return new CVec(a.X + b.X, a.Y + b.Y); }
public static CVec operator -(CVec a, CVec b) { return new CVec(a.X - b.X, a.Y - b.Y); }
@@ -55,20 +55,20 @@ namespace OpenRA
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
public bool Equals(CVec other) { return other == this; }
public override bool Equals(object obj) { return obj is CVec vec && Equals(vec); }
public override bool Equals(object obj) { return obj is CVec && Equals((CVec)obj); }
public override string ToString() { return X + "," + Y; }
public static readonly CVec[] Directions =
{
new(-1, -1),
new(-1, 0),
new(-1, 1),
new(0, -1),
new(0, 1),
new(1, -1),
new(1, 0),
new(1, 1),
new CVec(-1, -1),
new CVec(-1, 0),
new CVec(-1, 1),
new CVec(0, -1),
new CVec(0, 1),
new CVec(1, -1),
new CVec(1, 0),
new CVec(1, 1),
};
#region Scripting interface

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,9 +22,6 @@ namespace OpenRA
// Fixed byte pattern for the OID header
static readonly byte[] OIDHeader = { 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0 };
static readonly char[] HexUpperAlphabet = "0123456789ABCDEF".ToArray();
static readonly char[] HexLowerAlphabet = "0123456789abcdef".ToArray();
public static string PublicKeyFingerprint(RSAParameters parameters)
{
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
@@ -56,33 +53,33 @@ namespace OpenRA
using (var s = new MemoryStream(data))
{
// SEQUENCE
s.ReadUInt8();
s.ReadByte();
ReadTLVLength(s);
// SEQUENCE -> fixed header junk
s.ReadUInt8();
s.ReadByte();
var headerLength = ReadTLVLength(s);
s.Position += headerLength;
// SEQUENCE -> BIT_STRING
s.ReadUInt8();
s.ReadByte();
ReadTLVLength(s);
s.ReadUInt8();
s.ReadByte();
// SEQUENCE -> BIT_STRING -> SEQUENCE
s.ReadUInt8();
s.ReadByte();
ReadTLVLength(s);
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
s.ReadUInt8();
s.ReadByte();
var modulusLength = ReadTLVLength(s);
s.ReadUInt8();
s.ReadByte();
var modulus = s.ReadBytes(modulusLength - 1);
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
s.ReadUInt8();
s.ReadByte();
var exponentLength = ReadTLVLength(s);
s.ReadUInt8();
s.ReadByte();
var exponent = s.ReadBytes(exponentLength - 1);
return new RSAParameters
@@ -161,13 +158,13 @@ namespace OpenRA
static int ReadTLVLength(Stream s)
{
var length = s.ReadUInt8();
var length = s.ReadByte();
if (length < 0x80)
return length;
Span<byte> data = stackalloc byte[4];
s.ReadBytes(data[..Math.Min(length & 0x7F, 4)]);
return BitConverter.ToInt32(data);
var data = new byte[4];
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
return BitConverter.ToInt32(data.ToArray(), 0);
}
static int TripletFullLength(int dataLength)
@@ -190,10 +187,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to decrypt string with exception:");
Log.Write("debug", e);
Console.WriteLine("String decryption failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to decrypt string with exception: {0}", e);
Console.WriteLine("String decryption failed: {0}", e);
return null;
}
}
@@ -216,10 +211,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to sign string with exception");
Log.Write("debug", e);
Console.WriteLine("String signing failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to sign string with exception: {0}", e);
Console.WriteLine("String signing failed: {0}", e);
return null;
}
}
@@ -242,54 +235,27 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to verify signature with exception:");
Log.Write("debug", e);
Console.WriteLine("Signature validation failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to verify signature with exception: {0}", e);
Console.WriteLine("Signature validation failed: {0}", e);
return false;
}
}
public static string SHA1Hash(Stream data)
{
using var csp = SHA1.Create();
return ToHex(csp.ComputeHash(data), true);
using (var csp = SHA1.Create())
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
}
public static string SHA1Hash(byte[] data)
{
using var csp = SHA1.Create();
return ToHex(csp.ComputeHash(data), true);
using (var csp = SHA1.Create())
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
}
public static string SHA1Hash(string data)
{
return SHA1Hash(Encoding.UTF8.GetBytes(data));
}
public static string ToHex(ReadOnlySpan<byte> source, bool lowerCase = false)
{
if (source.Length == 0)
return string.Empty;
// excessively avoid stack overflow if source is too large (considering that we're allocating a new string)
var buffer = source.Length <= 256 ? stackalloc char[source.Length * 2] : new char[source.Length * 2];
return ToHexInternal(source, buffer, lowerCase);
}
static string ToHexInternal(ReadOnlySpan<byte> source, Span<char> buffer, bool lowerCase)
{
var sourceIndex = 0;
var alphabet = lowerCase ? HexLowerAlphabet : HexUpperAlphabet;
for (var i = 0; i < buffer.Length; i += 2)
{
var b = source[sourceIndex++];
buffer[i] = alphabet[b >> 4];
buffer[i + 1] = alphabet[b & 0xF];
}
return new string(buffer);
}
}
}

View File

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

View File

@@ -0,0 +1,39 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Graphics;
namespace OpenRA.Effects
{
public class AsyncAction : IEffect
{
readonly Action a;
readonly IAsyncResult ar;
public AsyncAction(IAsyncResult ar, Action a)
{
this.a = a;
this.ar = ar;
}
public void Tick(World world)
{
if (ar.IsCompleted)
{
world.AddFrameEndTask(w => { w.Remove(this); a(); });
}
}
public IEnumerable<IRenderable> Render(WorldRenderer r) { yield break; }
}
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -41,7 +41,7 @@ namespace OpenRA
public class ExternalMods : IReadOnlyDictionary<string, ExternalMod>
{
readonly Dictionary<string, ExternalMod> mods = new();
readonly Dictionary<string, ExternalMod> mods = new Dictionary<string, ExternalMod>();
readonly SheetBuilder sheetBuilder;
Sheet CreateSheet()
@@ -66,7 +66,6 @@ namespace OpenRA
// 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
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
{
var metadataPath = Path.Combine(source, "ModMetadata");
@@ -77,13 +76,13 @@ namespace OpenRA
{
try
{
var yaml = MiniYaml.FromFile(path, stringPool: stringPool).First().Value;
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
LoadMod(yaml, path);
}
catch (Exception e)
{
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
@@ -95,17 +94,17 @@ namespace OpenRA
if (sheetBuilder != null)
{
var iconNode = yaml.NodeWithKeyOrDefault("Icon");
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.NodeWithKeyOrDefault("Icon2x");
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.NodeWithKeyOrDefault("Icon3x");
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);
@@ -123,7 +122,7 @@ namespace OpenRA
return;
var key = ExternalMod.MakeKey(mod);
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new[]
var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List<MiniYamlNode>()
{
new MiniYamlNode("Id", mod.Id),
new MiniYamlNode("Version", mod.Metadata.Version),
@@ -132,21 +131,17 @@ namespace OpenRA
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
}));
var iconNodes = new List<MiniYamlNode>();
using (var stream = mod.Package.GetStream("icon.png"))
if (stream != null)
iconNodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes())));
using (var stream = mod.Package.GetStream("icon-2x.png"))
if (stream != null)
iconNodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes())));
using (var stream = mod.Package.GetStream("icon-3x.png"))
if (stream != null)
iconNodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
yaml = yaml.WithValue(yaml.Value.WithNodesAppended(iconNodes));
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
var sources = new HashSet<string>();
if (registration.HasFlag(ModRegistration.System))
@@ -179,19 +174,17 @@ namespace OpenRA
catch (Exception e)
{
Log.Write("debug", "Failed to register current mod metadata");
Log.Write("debug", e);
Log.Write("debug", e.ToString());
}
}
}
/// <summary>
/// Removes invalid mod registrations:
/// <list type="bullet">
/// <item>LaunchPath no longer exists.</item>
/// <item>LaunchPath and mod id matches the active mod, but the version is different.</item>
/// <item>Filename doesn't match internal key.</item>
/// <item>Fails to parse as a mod registration.</item>
/// </list>
/// * LaunchPath no longer exists
/// * LaunchPath and mod id matches the active mod, but the version is different
/// * Filename doesn't match internal key
/// * Fails to parse as a mod registration
/// </summary>
internal void ClearInvalidRegistrations(ModRegistration registration)
{
@@ -206,7 +199,7 @@ namespace OpenRA
string modKey = null;
try
{
var yaml = MiniYaml.FromFile(path).First().Value;
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
var m = FieldLoader.Load<ExternalMod>(yaml);
modKey = ExternalMod.MakeKey(m);
@@ -218,8 +211,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", $"Failed to parse mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
// Remove from the ingame mod switcher
@@ -230,12 +223,12 @@ namespace OpenRA
try
{
File.Delete(path);
Log.Write("debug", $"Removed invalid mod metadata file '{path}'");
Log.Write("debug", "Removed invalid mod metadata file '{0}'", path);
}
catch (Exception e)
{
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
@@ -256,13 +249,13 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", $"Failed to remove mod metadata file '{path}'");
Log.Write("debug", e);
Log.Write("debug", "Failed to remove mod metadata file '{0}'", path);
Log.Write("debug", e.ToString());
}
}
}
static IEnumerable<string> GetSupportDirs(ModRegistration registration)
IEnumerable<string> GetSupportDirs(ModRegistration registration)
{
var sources = new HashSet<string>(4);
if (registration.HasFlag(ModRegistration.System))

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,14 +22,20 @@ namespace OpenRA
{
public static class Exts
{
public static string FormatInvariant(this string format, params object[] args)
public static bool IsUppercase(this string str)
{
return string.Format(CultureInfo.InvariantCulture, format, args);
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
}
public static string FormatCurrent(this string format, params object[] args)
public static string F(this string fmt, params object[] args)
{
return string.Format(CultureInfo.CurrentCulture, format, args);
return string.Format(fmt, args);
}
public static T WithDefault<T>(T def, Func<T> f)
{
try { return f(); }
catch { return def; }
}
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
@@ -39,16 +45,21 @@ namespace OpenRA
return a.GetTypes().Select(t => t.Namespace).Distinct().Where(n => n != null);
}
public static bool HasAttribute<TAttribute>(this MemberInfo mi)
where TAttribute : Attribute
public static bool HasAttribute<T>(this MemberInfo mi)
{
return Attribute.IsDefined(mi, typeof(TAttribute));
return Attribute.IsDefined(mi, typeof(T));
}
public static TAttribute[] GetCustomAttributes<TAttribute>(this MemberInfo mi, bool inherit)
where TAttribute : Attribute
public static T[] GetCustomAttributes<T>(this MemberInfo mi, bool inherit)
where T : class
{
return (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), inherit);
return (T[])mi.GetCustomAttributes(typeof(T), inherit);
}
public static T[] GetCustomAttributes<T>(this ParameterInfo mi)
where T : class
{
return (T[])mi.GetCustomAttributes(typeof(T), true);
}
public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
@@ -108,57 +119,23 @@ namespace OpenRA
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
{
#if NET5_0_OR_GREATER
// SAFETY: Dictionary cannot be modified whilst the ref is alive.
ref var value = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(d, k, out var exists);
if (!exists)
value = v;
return value;
#else
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = v);
return ret;
#endif
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
{
// Cannot use CollectionsMarshal.GetValueRefOrAddDefault here,
// the creation function could mutate the dictionary which would invalidate the ref.
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = createFn(k));
return ret;
}
public static T GetOrAdd<T>(this HashSet<T> set, T value)
{
if (!set.TryGetValue(value, out var ret))
set.Add(ret = value);
return ret;
}
public static T GetOrAdd<T>(this HashSet<T> set, T value, Func<T, T> createFn)
{
if (!set.TryGetValue(value, out var ret))
set.Add(ret = createFn(value));
return ret;
}
public static int IndexOf<T>(this T[] array, T value)
{
return Array.IndexOf(array, value);
}
public static T FirstOrDefault<T>(this T[] array, Predicate<T> match)
{
return Array.Find(array, match);
}
public static T FirstOrDefault<T>(this List<T> list, Predicate<T> match)
{
return list.Find(match);
}
public static T Random<T>(this IEnumerable<T> ts, MersenneTwister r)
{
return Random(ts, r, true);
@@ -171,8 +148,8 @@ namespace OpenRA
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
{
var xs = ts as IReadOnlyCollection<T>;
xs ??= ts.ToList();
var xs = ts as ICollection<T>;
xs = xs ?? ts.ToList();
if (xs.Count == 0)
{
if (throws)
@@ -326,9 +303,9 @@ namespace OpenRA
// Adjust for other rounding modes
if (round == ISqrtRoundMode.Nearest && remainder > root)
root++;
root += 1;
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
root++;
root += 1;
return root;
}
@@ -367,9 +344,9 @@ namespace OpenRA
// Adjust for other rounding modes
if (round == ISqrtRoundMode.Nearest && remainder > root)
root++;
root += 1;
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
root++;
root += 1;
return root;
}
@@ -379,11 +356,6 @@ namespace OpenRA
return number * 46341 / 32768;
}
public static int MultiplyBySqrtTwoOverTwo(int number)
{
return (int)(number * 23170L / 32768L);
}
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
{
var quotient = Math.DivRem(dividend, divisor, out var remainder);
@@ -424,8 +396,8 @@ namespace OpenRA
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
{
// Fall back on ToString() if null functions are provided:
logKey ??= s => s.ToString();
logValue ??= s => s.ToString();
logKey = logKey ?? (s => s.ToString());
logValue = logValue ?? (s => s.ToString());
// Try to build a dictionary and log all duplicates found (if any):
var dupKeys = new Dictionary<TKey, List<string>>();
@@ -521,22 +493,17 @@ namespace OpenRA
return result;
}
public static byte ParseByteInvariant(string s)
{
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static short ParseInt16Invariant(string s)
{
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static int ParseInt32Invariant(string s)
public static int ParseIntegerInvariant(string s)
{
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static bool TryParseInt32Invariant(string s, out int i)
public static byte ParseByte(string s)
{
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static bool TryParseIntegerInvariant(string s, out int i)
{
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
}
@@ -546,29 +513,9 @@ namespace OpenRA
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
}
public static string ToStringInvariant(this byte i)
{
return i.ToString(NumberFormatInfo.InvariantInfo);
}
public static string ToStringInvariant(this byte i, string format)
{
return i.ToString(format, NumberFormatInfo.InvariantInfo);
}
public static string ToStringInvariant(this int i)
{
return i.ToString(NumberFormatInfo.InvariantInfo);
}
public static string ToStringInvariant(this int i, string format)
{
return i.ToString(format, NumberFormatInfo.InvariantInfo);
}
public static bool IsTraitEnabled<T>(this T trait)
{
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
return !(trait is IDisabledTrait disabledTrait) || !disabledTrait.IsTraitDisabled;
}
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
@@ -629,7 +576,7 @@ namespace OpenRA
Current = default;
}
public readonly LineSplitEnumerator GetEnumerator() => this;
public LineSplitEnumerator GetEnumerator() => this;
public bool MoveNext()
{
@@ -648,8 +595,8 @@ namespace OpenRA
return true;
}
Current = span[..index];
str = span[(index + 1)..];
Current = span.Slice(0, index);
str = span.Slice(index + 1);
return true;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -56,20 +56,24 @@ namespace OpenRA
}
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
{
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` ");
};
public static Action<string, Type> UnknownFieldAction = (s, f) =>
{
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
};
static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
new(BuildTypeLoadInfo);
new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
new(expression => new BooleanExpression(expression));
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
new(expression => new IntegerExpression(expression));
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers =
new()
new Dictionary<Type, Func<string, Type, string, MemberInfo, object>>()
{
{ typeof(int), ParseInt },
{ typeof(ushort), ParseUShort },
@@ -87,7 +91,6 @@ namespace OpenRA
{ typeof(WAngle), ParseWAngle },
{ typeof(WRot), ParseWRot },
{ typeof(CPos), ParseCPos },
{ typeof(CPos[]), ParseCPosArray },
{ typeof(CVec), ParseCVec },
{ typeof(CVec[]), ParseCVecArray },
{ typeof(BooleanExpression), ParseBooleanExpression },
@@ -104,7 +107,7 @@ namespace OpenRA
};
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
new()
new Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>>()
{
{ typeof(HashSet<>), ParseHashSetOrList },
{ typeof(List<>), ParseHashSetOrList },
@@ -113,19 +116,10 @@ namespace OpenRA
{ typeof(Nullable<>), ParseNullable },
};
static readonly object BoxedTrue = true;
static readonly object BoxedFalse = false;
static readonly object[] BoxedInts = Exts.MakeArray(33, i => (object)i);
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseInt32Invariant(value, out var res))
{
if (res >= 0 && res < BoxedInts.Length)
return BoxedInts[res];
if (Exts.TryParseIntegerInvariant(value, out var res))
return res;
}
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -165,7 +159,7 @@ namespace OpenRA
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && Color.TryParse(value, out var color))
return color;
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -196,11 +190,11 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3
&& WDist.TryParse(parts[0], out var rx)
&& WDist.TryParse(parts[1], out var ry)
&& WDist.TryParse(parts[2], out var rz))
return new WVec(rx, ry, rz);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WVec(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -220,8 +214,8 @@ namespace OpenRA
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx)
&& WDist.TryParse(parts[3 * i + 1], out var ry)
&& WDist.TryParse(parts[3 * i + 2], out var rz))
&& WDist.TryParse(parts[3 * i + 1], out var ry)
&& WDist.TryParse(parts[3 * i + 2], out var rz))
vecs[i] = new WVec(rx, ry, rz);
}
@@ -236,11 +230,13 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3
&& WDist.TryParse(parts[0], out var rx)
&& WDist.TryParse(parts[1], out var ry)
&& WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx)
&& WDist.TryParse(parts[1], out var ry)
&& WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -248,7 +244,7 @@ namespace OpenRA
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseInt32Invariant(value, out var res))
if (Exts.TryParseIntegerInvariant(value, out var res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -258,11 +254,13 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
&& Exts.TryParseInt32Invariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -275,33 +273,10 @@ namespace OpenRA
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
return new CPos(
Exts.ParseInt32Invariant(parts[0]),
Exts.ParseInt32Invariant(parts[1]),
Exts.ParseByteInvariant(parts[2]));
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCPosArray(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new CPos[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx)
&& int.TryParse(parts[2 * i + 1], out var ry))
vecs[i] = new CPos(rx, ry);
}
return vecs;
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseByte(parts[2]));
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -312,7 +287,7 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new CVec(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -390,7 +365,7 @@ namespace OpenRA
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
return result ? BoxedTrue : BoxedFalse;
return result;
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -405,7 +380,7 @@ namespace OpenRA
var ints = new int2[parts.Length / 2];
for (var i = 0; i < ints.Length; i++)
ints[i] = new int2(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
return ints;
}
@@ -418,7 +393,7 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Size(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -432,7 +407,7 @@ namespace OpenRA
if (parts.Length != 2)
return InvalidValueAction(value, fieldType, fieldName);
return new int2(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -480,10 +455,10 @@ namespace OpenRA
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Rectangle(
Exts.ParseInt32Invariant(parts[0]),
Exts.ParseInt32Invariant(parts[1]),
Exts.ParseInt32Invariant(parts[2]),
Exts.ParseInt32Invariant(parts[3]));
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseIntegerInvariant(parts[2]),
Exts.ParseIntegerInvariant(parts[3]));
}
return InvalidValueAction(value, fieldType, fieldName);
@@ -498,11 +473,11 @@ namespace OpenRA
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
return Activator.CreateInstance(fieldType);
return set;
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
var set = Activator.CreateInstance(fieldType, parts.Length);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod(nameof(List<object>.Add), arguments);
var addArgs = new object[1];
@@ -517,10 +492,7 @@ namespace OpenRA
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
if (yaml == null)
return Activator.CreateInstance(fieldType);
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Length);
var dict = Activator.CreateInstance(fieldType);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
var addArgs = new object[2];
@@ -551,7 +523,7 @@ namespace OpenRA
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments()[0];
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
@@ -559,7 +531,7 @@ namespace OpenRA
public static void Load(object self, MiniYaml my)
{
var loadInfo = TypeLoadInfo[self.GetType()];
List<string> missing = null;
var missing = new List<string>();
Dictionary<string, MiniYaml> md = null;
@@ -567,14 +539,14 @@ namespace OpenRA
{
object val;
md ??= my.ToDictionary();
if (md == null)
md = my.ToDictionary();
if (fli.Loader != null)
{
if (!fli.Attribute.Required || md.ContainsKey(fli.YamlName))
val = fli.Loader(my);
else
{
missing ??= new List<string>();
missing.Add(fli.YamlName);
continue;
}
@@ -584,11 +556,7 @@ namespace OpenRA
if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val))
{
if (fli.Attribute.Required)
{
missing ??= new List<string>();
missing.Add(fli.YamlName);
}
continue;
}
}
@@ -596,7 +564,7 @@ namespace OpenRA
fli.Field.SetValue(self, val);
}
if (missing != null)
if (missing.Count > 0)
throw new MissingFieldsException(missing.ToArray());
}
@@ -657,17 +625,12 @@ namespace OpenRA
public static object GetValue(string fieldName, Type fieldType, string value, MemberInfo field)
{
return GetValue(fieldName, fieldType, value, null, field);
return GetValue(fieldName, fieldType, new MiniYaml(value), field);
}
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
{
return GetValue(fieldName, fieldType, yaml.Value, yaml, field);
}
static object GetValue(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
value = value?.Trim();
var value = yaml.Value?.Trim();
if (fieldType.IsGenericType)
{
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
@@ -791,7 +754,7 @@ namespace OpenRA
[AttributeUsage(AttributeTargets.Field)]
public class SerializeAttribute : Attribute
{
public static readonly SerializeAttribute Default = new(true);
public static readonly SerializeAttribute Default = new SerializeAttribute(true);
public bool IsDefault => this == Default;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -15,7 +15,6 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using OpenRA.Primitives;
namespace OpenRA
@@ -59,7 +58,7 @@ namespace OpenRA
return new MiniYaml(
null,
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))));
fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList());
}
public static MiniYamlNode SaveField(object o, string field)
@@ -85,7 +84,7 @@ namespace OpenRA
// This is only for documentation generation
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
var result = new StringBuilder();
var result = "";
var dict = (System.Collections.IDictionary)v;
foreach (var kvp in dict)
{
@@ -95,10 +94,10 @@ namespace OpenRA
var formattedKey = FormatValue(key);
var formattedValue = FormatValue(value);
result.Append($"{formattedKey}: {formattedValue}{Environment.NewLine}");
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
}
return result.ToString();
return result;
}
if (v is DateTime d)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -16,7 +16,6 @@ using System.Linq;
using System.Net;
using System.Text;
using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using OpenRA.Graphics;
using OpenRA.Primitives;
@@ -32,7 +31,7 @@ namespace OpenRA.FileFormats
public Color[] Palette { get; }
public byte[] Data { get; }
public SpriteFrameType Type { get; }
public Dictionary<string, string> EmbeddedData = new();
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
@@ -46,13 +45,12 @@ namespace OpenRA.FileFormats
var data = new List<byte>();
Type = SpriteFrameType.Rgba32;
byte bitDepth = 8;
while (true)
{
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
var type = s.ReadASCII(4);
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
var content = s.ReadBytes(length);
s.ReadInt32(); // crc
/*var crc = */s.ReadInt32();
if (!headerParsed && type != "IHDR")
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
@@ -68,8 +66,8 @@ namespace OpenRA.FileFormats
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadUInt8();
var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadByte();
if (IsPaletted(bitDepth, colorType))
Type = SpriteFrameType.Indexed8;
else if (colorType == PngColorType.Color)
@@ -77,9 +75,9 @@ namespace OpenRA.FileFormats
Data = new byte[Width * Height * PixelStride];
var compression = ms.ReadUInt8();
ms.ReadUInt8(); // filter
var interlace = ms.ReadUInt8();
var compression = ms.ReadByte();
/*var filter = */ms.ReadByte();
var interlace = ms.ReadByte();
if (compression != 0)
throw new InvalidDataException("Compression method not supported");
@@ -94,10 +92,10 @@ namespace OpenRA.FileFormats
case "PLTE":
{
Palette = new Color[length / 3];
for (var i = 0; i < Palette.Length; i++)
Palette = new Color[256];
for (var i = 0; i < length / 3; i++)
{
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
Palette[i] = Color.FromArgb(r, g, b);
}
@@ -110,7 +108,7 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
for (var i = 0; i < length; i++)
Palette[i] = Color.FromArgb(ms.ReadUInt8(), Palette[i]);
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
break;
}
@@ -138,77 +136,22 @@ namespace OpenRA.FileFormats
{
var pxStride = PixelStride;
var rowStride = Width * pxStride;
var pixelsPerByte = 8 / bitDepth;
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
Span<byte> prevLine = new byte[rowStride];
var prevLine = new byte[rowStride];
for (var y = 0; y < Height; y++)
{
var filter = (PngFilter)ds.ReadUInt8();
ds.ReadBytes(Data, y * rowStride, sourceRowStride);
var line = Data.AsSpan(y * rowStride, rowStride);
var filter = (PngFilter)ds.ReadByte();
var line = ds.ReadBytes(rowStride);
// If the source has a bit depth of 1, 2 or 4 it packs multiple pixels per byte.
// Unpack to bit depth of 8, yielding 1 pixel per byte.
// This makes life easier for consumers of palleted data.
if (bitDepth < 8)
{
var mask = 0xFF >> (8 - bitDepth);
for (var i = sourceRowStride - 1; i >= 0; i--)
{
var packed = line[i];
for (var j = 0; j < pixelsPerByte; j++)
{
var dest = i * pixelsPerByte + j;
if (dest < line.Length) // Guard against last byte being only partially packed
line[dest] = (byte)(packed >> (8 - (j + 1) * bitDepth) & mask);
}
}
}
for (var i = 0; i < rowStride; 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]);
switch (filter)
{
case PngFilter.None:
break;
case PngFilter.Sub:
for (var i = pxStride; i < rowStride; i++)
line[i] += line[i - pxStride];
break;
case PngFilter.Up:
for (var i = 0; i < rowStride; i++)
line[i] += prevLine[i];
break;
case PngFilter.Average:
for (var i = 0; i < pxStride; i++)
line[i] += Average(0, prevLine[i]);
for (var i = pxStride; i < rowStride; i++)
line[i] += Average(line[i - pxStride], prevLine[i]);
break;
case PngFilter.Paeth:
for (var i = 0; i < pxStride; i++)
line[i] += Paeth(0, prevLine[i], 0);
for (var i = pxStride; i < rowStride; i++)
line[i] += Paeth(line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
break;
default:
throw new InvalidOperationException("Unsupported Filter");
}
Array.Copy(line, 0, Data, y * rowStride, rowStride);
prevLine = line;
}
static byte Average(byte a, byte b) => (byte)((a + b) / 2);
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
}
}
@@ -285,13 +228,38 @@ namespace OpenRA.FileFormats
return isPng;
}
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
{
switch (f)
{
case PngFilter.None: return x;
case PngFilter.Sub: return (byte)(x + a);
case PngFilter.Up: return (byte)(x + b);
case PngFilter.Average: return (byte)(x + (a + b) / 2);
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
default:
throw new InvalidOperationException("Unsupported Filter");
}
}
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
[Flags]
enum PngColorType : byte { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter : byte { None, Sub, Up, Average, Paeth }
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter { None, Sub, Up, Average, Paeth }
static bool IsPaletted(byte bitDepth, PngColorType colorType)
{
if (bitDepth <= 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
return true;
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
@@ -303,16 +271,16 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Unknown pixel format");
}
static void WritePngChunk(Stream output, string type, Stream input)
void WritePngChunk(Stream output, string type, Stream input)
{
input.Position = 0;
var typeBytes = Encoding.ASCII.GetBytes(type);
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
output.Write(typeBytes);
output.WriteArray(typeBytes);
var data = input.ReadAllBytes();
output.Write(data);
output.WriteArray(data);
var crc32 = new Crc32();
crc32.Update(typeBytes);
@@ -324,7 +292,7 @@ namespace OpenRA.FileFormats
{
using (var output = new MemoryStream())
{
output.Write(Signature);
output.WriteArray(Signature);
using (var header = new MemoryStream())
{
header.Write(IPAddress.HostToNetworkOrder(Width));
@@ -372,14 +340,13 @@ namespace OpenRA.FileFormats
using (var data = new MemoryStream())
{
using (var compressed = new DeflaterOutputStream(data, new Deflater(Deflater.BEST_COMPRESSION)))
using (var compressed = new DeflaterOutputStream(data))
{
var rowStride = Width * PixelStride;
for (var y = 0; y < Height; y++)
{
// Assuming no filtering for simplicity
const byte FilterType = 0;
compressed.WriteByte(FilterType);
// Write uncompressed scanlines for simplicity
compressed.WriteByte(0);
compressed.Write(Data, y * rowStride, rowStride);
}
@@ -394,7 +361,7 @@ namespace OpenRA.FileFormats
{
using (var text = new MemoryStream())
{
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
text.WriteArray(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
WritePngChunk(output, "tEXt", text);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -47,8 +47,8 @@ namespace OpenRA.FileFormats
throw new NotSupportedException($"Metadata version {version} is not supported");
// Read game info (max 100K limit as a safeguard against corrupted files)
var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100);
GameInfo = GameInformation.Deserialize(data, path);
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
GameInfo = GameInformation.Deserialize(data);
}
public void Write(BinaryWriter writer)
@@ -62,7 +62,7 @@ namespace OpenRA.FileFormats
{
// Write lobby info data
writer.Flush();
dataLength += writer.BaseStream.WriteLengthPrefixedString(Encoding.UTF8, GameInfo.Serialize());
dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize());
}
// Write total length & end marker

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -29,16 +29,16 @@ namespace OpenRA.FileSystem
public class FileSystem : IReadOnlyFileSystem
{
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new();
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
readonly string modID;
// Mod packages that should not be disposed
readonly List<IReadOnlyPackage> modPackages = new();
readonly List<IReadOnlyPackage> modPackages = new List<IReadOnlyPackage>();
readonly IReadOnlyDictionary<string, Manifest> installedMods;
readonly IPackageLoader[] packageLoaders;
Cache<string, List<IReadOnlyPackage>> fileIndex = new(_ => new List<IReadOnlyPackage>());
Cache<string, List<IReadOnlyPackage>> fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
public FileSystem(string modID, IReadOnlyDictionary<string, Manifest> installedMods, IPackageLoader[] packageLoaders)
{
@@ -83,16 +83,16 @@ namespace OpenRA.FileSystem
public void Mount(string name, string explicitName = null)
{
var optional = name.StartsWith('~');
var optional = name.StartsWith("~", StringComparison.Ordinal);
if (optional)
name = name[1..];
name = name.Substring(1);
try
{
IReadOnlyPackage package;
if (name.StartsWith('$'))
if (name.StartsWith("$", StringComparison.Ordinal))
{
name = name[1..];
name = name.Substring(1);
if (!installedMods.TryGetValue(name, out var mod))
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
@@ -109,8 +109,10 @@ namespace OpenRA.FileSystem
Mount(package, explicitName);
}
catch when (optional)
catch
{
if (!optional)
throw;
}
}
@@ -159,7 +161,9 @@ namespace OpenRA.FileSystem
explicitMounts.Remove(key);
// Mod packages aren't owned by us, so we shouldn't dispose them
if (!modPackages.Remove(package))
if (modPackages.Contains(package))
modPackages.Remove(package);
else
package.Dispose();
}
else
@@ -186,12 +190,6 @@ namespace OpenRA.FileSystem
UnmountAll();
foreach (var kv in manifest.Packages)
Mount(kv.Key, kv.Value);
mountedPackages.TrimExcess();
explicitMounts.TrimExcess();
modPackages.TrimExcess();
foreach (var packages in fileIndex.Values)
packages.TrimExcess();
}
Stream GetFromCache(string filename)
@@ -213,9 +211,9 @@ namespace OpenRA.FileSystem
public bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename)
{
var explicitSplit = path.IndexOf('|');
if (explicitSplit > 0 && explicitMounts.TryGetValue(path[..explicitSplit], out package))
if (explicitSplit > 0 && explicitMounts.TryGetValue(path.Substring(0, explicitSplit), out package))
{
filename = path[(explicitSplit + 1)..];
filename = path.Substring(explicitSplit + 1);
return true;
}
@@ -228,11 +226,14 @@ namespace OpenRA.FileSystem
public bool TryOpen(string filename, out Stream s)
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0 && explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
if (explicitSplit > 0)
{
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
if (s != null)
return true;
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
{
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
if (s != null)
return true;
}
}
s = GetFromCache(filename);
@@ -261,16 +262,16 @@ namespace OpenRA.FileSystem
public bool Exists(string filename)
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0 &&
explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage) &&
explicitPackage.Contains(filename[(explicitSplit + 1)..]))
return true;
if (explicitSplit > 0)
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
return true;
return fileIndex.ContainsKey(filename);
}
/// <summary>
/// Returns true if the given filename references an external mod via an explicit mount.
/// Returns true if the given filename references an external mod via an explicit mount
/// </summary>
public bool IsExternalModFile(string filename)
{
@@ -278,7 +279,7 @@ namespace OpenRA.FileSystem
if (explicitSplit < 0)
return false;
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
return false;
if (installedMods[modID].Package == explicitPackage)
@@ -294,21 +295,21 @@ 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 && !path.StartsWith("^"))
{
var parent = path[..explicitSplit];
var filename = path[(explicitSplit + 1)..];
var parent = path.Substring(0, explicitSplit);
var filename = path.Substring(explicitSplit + 1);
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
if (parentPath == null)
return null;
if (parentPath.StartsWith('$'))
if (parentPath.StartsWith("$", StringComparison.Ordinal))
{
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
return null;
if (mod.Package is not Folder)
if (!(mod.Package is Folder))
return null;
path = Path.Combine(mod.Package.Name, filename);
@@ -321,28 +322,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[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;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -18,22 +18,24 @@ namespace OpenRA.FileSystem
{
public sealed class Folder : IReadWritePackage
{
public string Name { get; }
readonly string path;
public Folder(string path)
{
Name = path;
this.path = path;
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
public string Name => path;
public IEnumerable<string> Contents
{
get
{
// Order may vary on different file systems and it matters for hashing.
return Directory.GetFiles(Name, "*", SearchOption.TopDirectoryOnly)
.Concat(Directory.GetDirectories(Name))
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
.Concat(Directory.GetDirectories(path))
.Select(Path.GetFileName)
.OrderBy(f => f);
}
@@ -41,18 +43,14 @@ namespace OpenRA.FileSystem
public Stream GetStream(string filename)
{
var combined = Path.Combine(Name, filename);
if (!File.Exists(combined))
return null;
try { return File.OpenRead(combined); }
try { return File.OpenRead(Path.Combine(path, filename)); }
catch { return null; }
}
public bool Contains(string filename)
{
var combined = Path.Combine(Name, filename);
return combined.StartsWith(Name, StringComparison.Ordinal) && File.Exists(combined);
var combined = Path.Combine(path, filename);
return combined.StartsWith(path, StringComparison.Ordinal) && File.Exists(combined);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
@@ -84,7 +82,7 @@ namespace OpenRA.FileSystem
// in FileSystem.OpenPackage. Their internal name therefore contains the
// full parent path too. We need to be careful to not add a second path
// prefix to these hacked packages.
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var s = File.Create(filePath))
@@ -98,7 +96,7 @@ namespace OpenRA.FileSystem
// in FileSystem.OpenPackage. Their internal name therefore contains the
// full parent path too. We need to be careful to not add a second path
// prefix to these hacked packages.
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
var filePath = filename.StartsWith(path) ? filename : Path.Combine(path, filename);
if (Directory.Exists(filePath))
Directory.Delete(filePath, true);
else if (File.Exists(filePath))

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -19,9 +19,9 @@ namespace OpenRA.FileSystem
{
public class ZipFileLoader : IPackageLoader
{
const uint ZipSignature = 0x04034b50;
static readonly string[] Extensions = { ".zip", ".oramap" };
public class ReadOnlyZipFile : IReadOnlyPackage
class ReadOnlyZipFile : IReadOnlyPackage
{
public string Name { get; protected set; }
protected ZipFile pkg;
@@ -68,7 +68,6 @@ namespace OpenRA.FileSystem
public void Dispose()
{
pkg?.Close();
GC.SuppressFinalize(this);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
@@ -94,9 +93,9 @@ namespace OpenRA.FileSystem
}
}
public sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
{
readonly MemoryStream pkgStream = new();
readonly MemoryStream pkgStream = new MemoryStream();
public ReadWriteZipFile(string filename, bool create = false)
{
@@ -118,7 +117,10 @@ namespace OpenRA.FileSystem
void Commit()
{
File.WriteAllBytes(Name, pkgStream.ToArray());
var pos = pkgStream.Position;
pkgStream.Position = 0;
File.WriteAllBytes(Name, pkgStream.ReadBytes((int)pkgStream.Length));
pkgStream.Position = pos;
}
public void Update(string filename, byte[] contents)
@@ -140,22 +142,23 @@ namespace OpenRA.FileSystem
sealed class ZipFolder : IReadOnlyPackage
{
public string Name { get; }
public string Name => path;
public ReadOnlyZipFile Parent { get; }
readonly string path;
public ZipFolder(ReadOnlyZipFile parent, string path)
{
if (path.EndsWith('/'))
path = path[..^1];
if (path.EndsWith("/", StringComparison.Ordinal))
path = path.Substring(0, path.Length - 1);
Name = path;
Parent = parent;
this.path = path;
}
public Stream GetStream(string filename)
{
// Zip files use '/' as a path separator
return Parent.GetStream(Name + '/' + filename);
return Parent.GetStream(path + '/' + filename);
}
public IEnumerable<string> Contents
@@ -164,9 +167,9 @@ namespace OpenRA.FileSystem
{
foreach (var entry in Parent.Contents)
{
if (entry.StartsWith(Name, StringComparison.Ordinal) && entry != Name)
if (entry.StartsWith(path, StringComparison.Ordinal) && entry != path)
{
var filename = entry[(Name.Length + 1)..];
var filename = entry.Substring(path.Length + 1);
var dirLevels = filename.Split('/').Count(c => !string.IsNullOrEmpty(c));
if (dirLevels == 1)
yield return filename;
@@ -177,18 +180,18 @@ namespace OpenRA.FileSystem
public bool Contains(string filename)
{
return Parent.Contains(Name + '/' + filename);
return Parent.Contains(path + '/' + filename);
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
{
return Parent.OpenPackage(Name + '/' + filename, context);
return Parent.OpenPackage(path + '/' + filename, context);
}
public void Dispose() { /* nothing to do */ }
}
sealed class StaticStreamDataSource : IStaticDataSource
class StaticStreamDataSource : IStaticDataSource
{
readonly Stream s;
public StaticStreamDataSource(Stream s)
@@ -204,10 +207,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;
@@ -219,13 +219,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);

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -29,9 +29,6 @@ namespace OpenRA
{
public static class Game
{
[TranslationReference("filename")]
const string SavedScreenshot = "notification-saved-screenshot";
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
public static InstalledMods Mods { get; private set; }
@@ -48,7 +45,7 @@ namespace OpenRA
internal static OrderManager OrderManager;
static Server.Server server;
public static MersenneTwister CosmeticRandom = new(); // not synced
public static MersenneTwister CosmeticRandom = new MersenneTwister(); // not synced
public static Renderer Renderer;
public static Sound Sound;
@@ -65,7 +62,7 @@ namespace OpenRA
{
var newConnection = new NetworkConnection(endpoint);
if (recordReplay)
newConnection.StartRecording(() => TimestampedFilename());
newConnection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(newConnection);
JoinInner(om);
@@ -86,9 +83,8 @@ namespace OpenRA
static void JoinInner(OrderManager om)
{
// Refresh static classes before the game starts.
// Refresh TextNotificationsManager before the game starts.
TextNotificationsManager.Clear();
UnitOrders.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
@@ -180,7 +176,6 @@ namespace OpenRA
}
public static event Action BeforeGameStart = () => { };
public static event Action AfterGameStart = () => { };
internal static void StartGame(string mapUID, WorldType type)
{
// Dispose of the old world before creating a new one.
@@ -189,8 +184,13 @@ namespace OpenRA
Cursor.SetCursor(null);
BeforeGameStart();
Map map;
using (new PerfTimer("PrepareMap"))
map = ModData.PrepareMap(mapUID);
using (new PerfTimer("NewWorld"))
OrderManager.World = new World(mapUID, ModData, OrderManager, type);
OrderManager.World = new World(ModData, map, OrderManager, type);
OrderManager.World.GameOver += FinishBenchmark;
@@ -224,12 +224,6 @@ namespace OpenRA
// Much better to clean up now then to drop frames during gameplay for GC pauses.
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
// PostLoadComplete is designed for anything that should trigger at the very end of loading.
// e.g. audio notifications that the game is starting.
OrderManager.World.PostLoadComplete(worldRenderer);
AfterGameStart();
}
public static void RestartGame()
@@ -273,14 +267,15 @@ namespace OpenRA
{
OrderManager om = null;
void LobbyReady()
Action lobbyReady = null;
lobbyReady = () =>
{
LobbyInfoChanged -= LobbyReady;
LobbyInfoChanged -= lobbyReady;
foreach (var o in setupOrders)
om.IssueOrder(o);
}
};
LobbyInfoChanged += LobbyReady;
LobbyInfoChanged += lobbyReady;
om = JoinServer(CreateLocalServer(mapUID), "");
}
@@ -423,8 +418,8 @@ namespace OpenRA
// Sanitize input from platform-specific launchers
// Process.Start requires paths to not be quoted, even if they contain spaces
if (launchPath != null && launchPath[0] == '"' && launchPath.Last() == '"')
launchPath = launchPath[1..^1];
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath.Substring(1, launchPath.Length - 2);
// Metadata registration requires an explicit launch path
if (launchPath != null)
@@ -531,11 +526,10 @@ namespace OpenRA
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
.Select(m => m.Uid);
var shellmap = shellmaps.RandomOrDefault(CosmeticRandom);
if (shellmap == null)
if (!shellmaps.Any())
throw new InvalidDataException("No valid shellmaps available");
return shellmap;
return shellmaps.Random(CosmeticRandom);
}
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
@@ -572,11 +566,14 @@ 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();
static volatile ActionQueue delayedActions = new ActionQueue();
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
[TranslationReference("filename")]
static readonly string SavedScreenshot = "saved-screenshot";
static void TakeScreenshotInner()
{
using (new PerfTimer("Renderer.SaveScreenshot"))
@@ -590,7 +587,7 @@ namespace OpenRA
Log.Write("debug", "Taking screenshot " + path);
Renderer.SaveScreenshot(path);
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
TextNotificationsManager.Debug(ModData.Translation.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
}
}
@@ -609,10 +606,7 @@ namespace OpenRA
if (orderManager.LastTickTime.ShouldAdvance(tick))
{
if (orderManager.GameStarted && orderManager.LocalFrameNumber == 0)
PerfHistory.Reset(); // Remove history that occurred whilst the new game was loading.
using (var sample = new PerfSample("tick_time"))
using (new PerfSample("tick_time"))
{
orderManager.LastTickTime.AdvanceTickTime(tick);
@@ -621,15 +615,14 @@ namespace OpenRA
Sync.RunUnsynced(world, orderManager.TickImmediate);
if (world == null)
{
if (orderManager.GameStarted)
PerfHistory.Reset(); // Remove old history when a new game starts.
return;
}
if (orderManager.TryTick())
{
Sync.RunUnsynced(world, () => world.OrderGenerator.Tick(world));
Sync.RunUnsynced(world, () =>
{
world.OrderGenerator.Tick(world);
});
world.Tick();
@@ -679,7 +672,7 @@ namespace OpenRA
// Prepare renderables (i.e. render voxels) before calling BeginFrame
using (new PerfSample("render_prepare"))
{
worldRenderer?.BeginFrame();
Renderer.WorldModelRenderer.BeginFrame();
// World rendering is disabled while the loading screen is displayed
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
@@ -689,7 +682,7 @@ namespace OpenRA
}
Ui.PrepareRenderables();
worldRenderer?.EndFrame();
Renderer.WorldModelRenderer.EndFrame();
}
// worldRenderer is null during the initial install/download screen
@@ -793,7 +786,7 @@ namespace OpenRA
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))
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
// Ideal time between screen updates
@@ -928,15 +921,15 @@ namespace OpenRA
{
var endpoints = new List<IPEndPoint>
{
new(IPAddress.IPv6Any, settings.ListenPort),
new(IPAddress.Any, settings.ListenPort)
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
new IPEndPoint(IPAddress.Any, settings.ListenPort)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
return server.GetEndpointForLocalConnection();
}
public static ConnectionTarget CreateLocalServer(string map, bool isSkirmish = false)
public static ConnectionTarget CreateLocalServer(string map)
{
var settings = new ServerSettings()
{
@@ -950,9 +943,9 @@ namespace OpenRA
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
var endpoints = new List<IPEndPoint>
{
new(IPAddress.Loopback, 0)
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, isSkirmish ? ServerType.Skirmish : ServerType.Local);
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
return server.GetEndpointForLocalConnection();
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -36,7 +36,7 @@ namespace OpenRA
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
public IList<Player> Players { get; }
public HashSet<int> DisabledSpawnPoints = new();
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
@@ -49,13 +49,13 @@ namespace OpenRA
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
}
public static GameInformation Deserialize(string data, string path)
public static GameInformation Deserialize(string data)
{
try
{
var info = new GameInformation();
var nodes = MiniYaml.FromString(data, path);
var nodes = MiniYaml.FromString(data);
foreach (var node in nodes)
{
var keyParts = node.Key.Split('@');
@@ -85,7 +85,7 @@ namespace OpenRA
{
var nodes = new List<MiniYamlNode>
{
new("Root", FieldSaver.Save(this))
new MiniYamlNode("Root", FieldSaver.Save(this))
};
for (var i = 0; i < Players.Count; i++)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,7 +22,7 @@ namespace OpenRA
/// </summary>
public class ActorInfo
{
public const char AbstractActorPrefix = '^';
public const string AbstractActorPrefix = "^";
public const char TraitInstanceSeparator = '@';
/// <summary>
@@ -32,7 +32,7 @@ namespace OpenRA
/// You can remove inherited traits by adding a - in front of them as in -TraitName: to inherit everything, but this trait.
/// </summary>
public readonly string Name;
readonly TypeDictionary traits = new();
readonly TypeDictionary traits = new TypeDictionary();
List<TraitInfo> constructOrderCache = null;
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
@@ -115,54 +115,45 @@ namespace OpenRA
}).ToList();
var resolved = source.Where(s => s.Dependencies.Count == 0 && s.OptionalDependencies.Count == 0).ToList();
var unresolved = source.ToHashSet();
unresolved.ExceptWith(resolved);
var unresolved = source.Except(resolved);
static bool AreResolvable(Type a, Type b) => a.IsAssignableFrom(b);
var testResolve = new Func<Type, Type, bool>((a, b) => a == b || a.IsAssignableFrom(b));
// This query detects which unresolved traits can be immediately resolved as all their direct dependencies are met.
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 => AreResolvable(d, r.Type)) && // There must exist a resolved trait that meets the dependency.
!unresolved.Any(u1 => AreResolvable(d, u1.Type))) && // All matching traits that meet this dependency must be resolved first.
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 => AreResolvable(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 optional dependencies 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.
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection
var readyToResolve = more.ToList();
while (readyToResolve.Count != 0)
{
resolved.AddRange(readyToResolve);
unresolved.ExceptWith(readyToResolve);
readyToResolve.Clear();
readyToResolve.AddRange(more);
}
#pragma warning restore CA1851
while (more.Any())
resolved.AddRange(more);
if (unresolved.Count != 0)
if (unresolved.Any())
{
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\n";
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => AreResolvable(d, s.Type)))).Distinct();
var exceptionString = "ActorInfo(\"" + Name + "\") failed to initialize because of the following:\r\n";
var missing = unresolved.SelectMany(u => u.Dependencies.Where(d => !source.Any(s => testResolve(d, s.Type)))).Distinct();
exceptionString += "Missing:\n";
exceptionString += "Missing:\r\n";
foreach (var m in missing)
exceptionString += m + " \n";
exceptionString += m + " \r\n";
exceptionString += "Unresolved:\n";
exceptionString += "Unresolved:\r\n";
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} }}\n";
exceptionString += $"{u.Type}: {{ {allDeps} }}\r\n";
}
throw new YamlException(exceptionString);
}
constructOrderCache = resolved.ConvertAll(r => r.Trait);
constructOrderCache = resolved.Select(r => r.Trait).ToList();
return constructOrderCache;
}
@@ -187,7 +178,7 @@ namespace OpenRA
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>(); }
public IReadOnlyCollection<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
public IEnumerable<T> TraitInfos<T>() where T : ITraitInfoInterface { return traits.WithInterface<T>(); }
public BitSet<TargetableType> GetAllTargetTypes()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -28,14 +28,14 @@ namespace OpenRA.GameRules
Title = value.Value;
var nd = value.ToDictionary();
if (nd.TryGetValue("Hidden", out var yaml))
bool.TryParse(yaml.Value, out Hidden);
if (nd.ContainsKey("Hidden"))
bool.TryParse(nd["Hidden"].Value, out Hidden);
if (nd.TryGetValue("VolumeModifier", out yaml))
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", yaml.Value);
if (nd.ContainsKey("VolumeModifier"))
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
var ext = nd.TryGetValue("Extension", out yaml) ? yaml.Value : "aud";
Filename = (nd.TryGetValue("Filename", out yaml) ? yaml.Value : key) + "." + ext;
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
}
public void Load(IReadOnlyFileSystem fileSystem)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -15,6 +15,7 @@ using System.Linq;
using System.Threading.Tasks;
using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA
@@ -27,6 +28,7 @@ namespace OpenRA
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
public readonly ITerrainInfo TerrainInfo;
public readonly SequenceProvider Sequences;
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
public Ruleset(
@@ -36,6 +38,7 @@ namespace OpenRA
IReadOnlyDictionary<string, SoundInfo> notifications,
IReadOnlyDictionary<string, MusicInfo> music,
ITerrainInfo terrainInfo,
SequenceProvider sequences,
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
{
Actors = new ActorInfoDictionary(actors);
@@ -44,6 +47,7 @@ namespace OpenRA
Notifications = notifications;
Music = music;
TerrainInfo = terrainInfo;
Sequences = sequences;
ModelSequences = modelSequences;
foreach (var a in Actors.Values)
@@ -120,11 +124,11 @@ namespace OpenRA
var fs = modData.DefaultFileSystem;
Ruleset ruleset = null;
void LoadRuleset()
Action f = () =>
{
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
k => new WeaponInfo(k.Value));
@@ -141,15 +145,15 @@ namespace OpenRA
var modelSequences = MergeOrDefault("Manifest,ModelSequences", fs, m.ModelSequences, null, null,
k => k);
// The default ruleset does not include a preferred tileset
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, modelSequences);
}
// The default ruleset does not include a preferred tileset or sequence set
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null, modelSequences);
};
if (modData.IsOnMainThread)
{
modData.HandleLoadingProgress();
var loader = new Task(LoadRuleset);
var loader = new Task(f);
loader.Start();
// Animate the loadscreen while we wait
@@ -157,7 +161,7 @@ namespace OpenRA
modData.HandleLoadingProgress();
}
else
LoadRuleset();
f();
return ruleset;
}
@@ -166,23 +170,24 @@ namespace OpenRA
{
var dr = modData.DefaultRules;
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
var sequences = modData.DefaultSequences[tileSet];
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, dr.ModelSequences);
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, sequences, dr.ModelSequences);
}
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications,
MiniYaml mapMusic, MiniYaml mapModelSequences)
MiniYaml mapMusic, MiniYaml mapSequences, MiniYaml mapModelSequences)
{
var m = modData.Manifest;
var dr = modData.DefaultRules;
Ruleset ruleset = null;
void LoadRuleset()
Action f = () =>
{
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix));
filterNode: n => n.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal));
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
k => new WeaponInfo(k.Value));
@@ -199,19 +204,23 @@ namespace OpenRA
// TODO: Add support for merging custom terrain modifications
var terrainInfo = modData.DefaultTerrainInfo[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);
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, modelSequences);
}
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, sequences, modelSequences);
};
if (modData.IsOnMainThread)
{
modData.HandleLoadingProgress();
var loader = new Task(LoadRuleset);
var loader = new Task(f);
loader.Start();
// Animate the loadscreen while we wait
@@ -219,17 +228,17 @@ namespace OpenRA
modData.HandleLoadingProgress();
}
else
LoadRuleset();
f();
return ruleset;
}
static bool AnyCustomYaml(MiniYaml yaml)
{
return yaml != null && (yaml.Value != null || yaml.Nodes.Length > 0);
return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0);
}
static bool AnyFlaggedTraits(ModData modData, IEnumerable<MiniYamlNode> actors)
static bool AnyFlaggedTraits(ModData modData, List<MiniYamlNode> actors)
{
foreach (var actorNode in actors)
{
@@ -244,7 +253,7 @@ namespace OpenRA
}
catch (Exception ex)
{
Log.Write("debug", "Error in AnyFlaggedTraits\n" + ex.ToString());
Log.Write("debug", "Error in AnyFlaggedTraits\r\n" + ex.ToString());
}
}
}
@@ -260,18 +269,18 @@ namespace OpenRA
return true;
// Any trait overrides that aren't explicitly whitelisted are flagged
if (mapRules == null)
return false;
if (AnyFlaggedTraits(modData, mapRules.Nodes))
return true;
if (mapRules.Value != null)
if (mapRules != null)
{
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
foreach (var f in mapFiles)
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
return true;
if (AnyFlaggedTraits(modData, mapRules.Nodes))
return true;
if (mapRules.Value != null)
{
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
foreach (var f in mapFiles)
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
return true;
}
}
return false;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -17,14 +17,14 @@ namespace OpenRA.GameRules
{
public class SoundInfo
{
public readonly Dictionary<string, string[]> Variants = new();
public readonly Dictionary<string, string[]> Prefixes = new();
public readonly Dictionary<string, string[]> Voices = new();
public readonly Dictionary<string, string[]> Notifications = new();
public readonly Dictionary<string, string[]> Variants = new Dictionary<string, string[]>();
public readonly Dictionary<string, string[]> Prefixes = new Dictionary<string, string[]>();
public readonly Dictionary<string, string[]> Voices = new Dictionary<string, string[]>();
public readonly Dictionary<string, string[]> Notifications = new Dictionary<string, string[]>();
public readonly string DefaultVariant = ".aud";
public readonly string DefaultPrefix = "";
public readonly HashSet<string> DisableVariants = new();
public readonly HashSet<string> DisablePrefixes = new();
public readonly HashSet<string> DisableVariants = new HashSet<string>();
public readonly HashSet<string> DisablePrefixes = new HashSet<string>();
public readonly Lazy<Dictionary<string, SoundPool>> VoicePools;
public readonly Lazy<Dictionary<string, SoundPool>> NotificationsPools;
@@ -40,16 +40,16 @@ namespace OpenRA.GameRules
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
{
var ret = new Dictionary<string, SoundPool>();
var classifiction = y.NodeWithKey(key);
var classifiction = y.Nodes.First(x => x.Key == key);
foreach (var t in classifiction.Value.Nodes)
{
var volumeModifier = 1f;
var volumeModifierNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.VolumeModifier));
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
if (volumeModifierNode != null)
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
var interruptType = SoundPool.DefaultInterruptType;
var interruptTypeNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.InterruptType));
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);
@@ -69,7 +69,7 @@ namespace OpenRA.GameRules
public readonly float VolumeModifier;
public readonly InterruptType Type;
readonly string[] clips;
readonly List<string> liveclips = new();
readonly List<string> liveclips = new List<string>();
public SoundPool(float volumeModifier, InterruptType interruptType, params string[] clips)
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -99,20 +99,17 @@ 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("Ground", "Water");
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("Air");
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(128);
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.")]
@@ -128,10 +125,10 @@ namespace OpenRA.GameRules
public readonly IProjectileInfo Projectile;
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
public readonly List<IWarhead> Warheads = new();
public readonly List<IWarhead> Warheads = new List<IWarhead>();
/// <summary>
/// This constructor is used solely for documentation generation.
/// This constructor is used solely for documentation generation!
/// </summary>
public WeaponInfo() { }
@@ -139,14 +136,13 @@ namespace OpenRA.GameRules
{
// Resolve any weapon-level yaml inheritance or removals
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection<MiniYamlNode>[] { content.Nodes }));
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
FieldLoader.Load(this, content);
}
static object LoadProjectile(MiniYaml yaml)
{
var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value;
if (proj == null)
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
@@ -160,7 +156,7 @@ namespace OpenRA.GameRules
static object LoadWarheads(MiniYaml yaml)
{
var retList = new List<IWarhead>();
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead", StringComparison.Ordinal)))
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
{
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
if (ret == null)
@@ -210,24 +206,29 @@ 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>

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -10,12 +10,12 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
public class GameSpeed
{
[TranslationReference]
[FieldLoader.Require]
public readonly string Name;
@@ -37,7 +37,7 @@ namespace OpenRA
static object LoadSpeeds(MiniYaml y)
{
var ret = new Dictionary<string, GameSpeed>();
var speedsNode = y.NodeWithKeyOrDefault("Speeds");
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
if (speedsNode == null)
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
public string Name { get; private set; }
public bool IsDecoration { get; set; }
readonly SequenceSet sequences;
readonly SequenceProvider sequenceProvider;
readonly Func<WAngle> facingFunc;
readonly Func<bool> paused;
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
bool backwards;
bool tickAlways;
int timeUntilNextFrame;
Action tickFunc;
Action tickFunc = () => { };
public Animation(World world, string name)
: this(world, name, () => WAngle.Zero) { }
@@ -43,7 +43,7 @@ namespace OpenRA.Graphics
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
{
sequences = world.Map.Sequences;
sequenceProvider = world.Map.Rules.Sequences;
Name = name.ToLowerInvariant();
this.facingFunc = facingFunc;
this.paused = paused;
@@ -61,9 +61,9 @@ namespace OpenRA.Graphics
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
rotation);
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
if (shadow != null)
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);
return new IRenderable[] { shadowRenderable, imageRenderable };
@@ -80,9 +80,9 @@ namespace OpenRA.Graphics
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha, rotation);
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
if (shadow != null)
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 };
@@ -164,7 +164,7 @@ namespace OpenRA.Graphics
if (frame >= CurrentSequence.Length)
{
frame = CurrentSequence.Length - 1;
tickFunc = null;
tickFunc = () => { };
after?.Invoke();
}
};
@@ -212,13 +212,13 @@ namespace OpenRA.Graphics
public void Tick(int t)
{
if (tickAlways)
tickFunc?.Invoke();
tickFunc();
else
{
timeUntilNextFrame -= t;
while (timeUntilNextFrame <= 0)
{
tickFunc?.Invoke();
tickFunc();
timeUntilNextFrame += CurrentSequenceTickOrDefault();
}
}
@@ -236,11 +236,11 @@ namespace OpenRA.Graphics
}
}
public bool HasSequence(string seq) { return sequences.HasSequence(Name, seq); }
public bool HasSequence(string seq) { return sequenceProvider.HasSequence(Name, seq); }
public ISpriteSequence GetSequence(string sequenceName)
{
return sequences.GetSequence(Name, sequenceName);
return sequenceProvider.GetSequence(Name, sequenceName);
}
public string GetRandomExistingSequence(string[] sequences, MersenneTwister random)

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -49,7 +49,7 @@ namespace OpenRA.Graphics
public readonly int[] PanelRegion = null;
public readonly PanelSides PanelSides = PanelSides.All;
public readonly Dictionary<string, Rectangle> Regions = new();
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
}
public static IReadOnlyDictionary<string, Collection> Collections => collections;
@@ -77,12 +77,11 @@ namespace OpenRA.Graphics
cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
foreach (var c in chrome)
if (!c.Key.StartsWith('^'))
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
LoadCollection(c.Key, c.Value);
}
@@ -101,7 +100,9 @@ namespace OpenRA.Graphics
static void LoadCollection(string name, MiniYaml yaml)
{
Game.ModData.LoadScreen?.Display();
if (Game.ModData.LoadScreen != null)
Game.ModData.LoadScreen.Display();
collections.Add(name, FieldLoader.Load<Collection>(yaml));
}
@@ -235,7 +236,7 @@ namespace OpenRA.Graphics
{
// 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.Count == 0)
if (!collection.Regions.Any())
return Array.Empty<Sprite>();
// Support manual definitions for unusual dialog layouts
@@ -264,13 +265,13 @@ namespace OpenRA.Graphics
if (!collections.TryGetValue(collectionName, out var collection))
{
Log.Write("debug", $"Could not find collection '{collectionName}'");
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 '{collectionName}' does not define a valid PanelRegion");
Log.Write("debug", "Collection '{0}' does not define a valid PanelRegion", collectionName);
return new Size(0, 0);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -17,7 +17,7 @@ namespace OpenRA.Graphics
{
public sealed class CursorManager
{
sealed class Cursor
class Cursor
{
public string Name;
public int2 PaddedSize;
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
public IHardwareCursor[] Cursors;
}
readonly Dictionary<string, Cursor> cursors = new();
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
readonly SheetBuilder sheetBuilder;
readonly GraphicSettings graphicSettings;
@@ -111,7 +111,8 @@ namespace OpenRA.Graphics
var template = kv.Value;
for (var i = 0; i < template.Sprites.Length; i++)
{
template.Cursors[i]?.Dispose();
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());

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -24,11 +24,10 @@ namespace OpenRA.Graphics
public CursorProvider(ModData modData)
{
var fileSystem = modData.DefaultFileSystem;
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool)));
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
// Overwrite previous definitions if there are duplicates
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
@@ -36,14 +35,14 @@ namespace OpenRA.Graphics
if (p.Palette != null)
pals[p.Palette] = p;
Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value)
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
.Where(p => p != null)
.Distinct()
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
var cursors = new Dictionary<string, CursorSequence>();
foreach (var s in cursorsYaml.Nodes)
foreach (var s in nodesDict["Cursors"].Nodes)
foreach (var sequence in s.Value.Nodes)
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -27,40 +27,32 @@ namespace OpenRA.Graphics
{
var d = info.ToDictionary();
Start = Exts.ParseInt32Invariant(d["Start"].Value);
Start = Exts.ParseIntegerInvariant(d["Start"].Value);
Palette = palette;
Name = name;
var cursorSprites = cache[cursorSrc];
Frames = cursorSprites.Skip(Start).ToArray();
Frames = cache[cursorSrc].Skip(Start).ToArray();
if ((d.TryGetValue("Length", out var yaml) && yaml.Value == "*") ||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
Length = Frames.Length;
else if (d.TryGetValue("Length", out yaml))
Length = Exts.ParseInt32Invariant(yaml.Value);
else if (d.TryGetValue("End", out yaml))
Length = Exts.ParseInt32Invariant(yaml.Value) - Start;
else if (d.ContainsKey("Length"))
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
else if (d.ContainsKey("End"))
Length = Exts.ParseIntegerInvariant(d["End"].Value) - Start;
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.");
if (d.TryGetValue("X", out yaml))
if (d.ContainsKey("X"))
{
Exts.TryParseInt32Invariant(yaml.Value, out var x);
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
Hotspot = Hotspot.WithX(x);
}
if (d.TryGetValue("Y", out yaml))
if (d.ContainsKey("Y"))
{
Exts.TryParseInt32Invariant(yaml.Value, out var y);
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
Hotspot = Hotspot.WithY(y);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -21,9 +21,9 @@ namespace OpenRA.Graphics
public ITexture ColorShifts { get; }
public int Height { get; private set; }
readonly Dictionary<string, ImmutablePalette> palettes = new();
readonly Dictionary<string, MutablePalette> mutablePalettes = new();
readonly Dictionary<string, int> indices = new();
readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>();
readonly Dictionary<string, MutablePalette> mutablePalettes = new Dictionary<string, MutablePalette>();
readonly Dictionary<string, int> indices = new Dictionary<string, int>();
byte[] buffer = Array.Empty<byte>();
float[] colorShiftBuffer = Array.Empty<float>();
@@ -70,7 +70,7 @@ namespace OpenRA.Graphics
{
Height = Exts.NextPowerOf2(index + 1);
Array.Resize(ref buffer, Height * Palette.Size * 4);
Array.Resize(ref colorShiftBuffer, Height * 8);
Array.Resize(ref colorShiftBuffer, Height * 4);
}
if (allowModifiers)
@@ -79,9 +79,6 @@ namespace OpenRA.Graphics
CopyPaletteToBuffer(index, p);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
Justification = "False positive - indexer is a set not a get.")]
public void ReplacePalette(string name, IPalette p)
{
if (mutablePalettes.ContainsKey(name))
@@ -93,20 +90,19 @@ namespace OpenRA.Graphics
CopyBufferToTexture();
}
public void SetColorShift(string name, float hueOffset, float satOffset, float valueMultiplier, float minHue, float maxHue)
public void SetColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
var index = GetPaletteIndex(name);
colorShiftBuffer[8 * index + 0] = minHue;
colorShiftBuffer[8 * index + 1] = maxHue;
colorShiftBuffer[8 * index + 4] = hueOffset;
colorShiftBuffer[8 * index + 5] = satOffset;
colorShiftBuffer[8 * index + 6] = valueMultiplier;
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[8 * index] != 0 || colorShiftBuffer[8 * index + 1] != 0;
return colorShiftBuffer[4 * index + 2] != 0 || colorShiftBuffer[4 * index + 3] != 0;
}
public void Initialize()
@@ -129,7 +125,7 @@ namespace OpenRA.Graphics
void CopyBufferToTexture()
{
Texture.SetData(buffer, Palette.Size, Height);
ColorShifts.SetFloatData(colorShiftBuffer, 2, Height);
ColorShifts.SetFloatData(colorShiftBuffer, 1, Height);
}
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)

View File

@@ -1,56 +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.Linq;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class MarkerTileRenderable : IRenderable, IFinalizedRenderable
{
readonly CPos pos;
readonly Color color;
public MarkerTileRenderable(CPos pos, Color color)
{
this.pos = pos;
this.color = color;
}
public WPos Pos => WPos.Zero;
public int ZOffset => 0;
public bool IsDecoration => true;
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec)
{
return new MarkerTileRenderable(pos, color);
}
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var map = wr.World.Map;
var r = map.Grid.Ramps[map.Ramp[pos]];
var wpos = map.CenterOfCell(pos) - new WVec(0, 0, r.CenterHeightOffset);
var corners = r.Corners.Select(corner => wr.Viewport.WorldToViewPx(wr.Screen3DPosition(wpos + corner))).ToList();
Game.Renderer.RgbaColorRenderer.FillRect(corners[0], corners[1], corners[2], corners[3], color);
}
public void RenderDebugGeometry(WorldRenderer wr) { }
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -10,8 +10,9 @@
#endregion
using System;
using System.Collections.Generic;
using OpenRA.FileSystem;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Graphics
{
@@ -25,18 +26,10 @@ namespace OpenRA.Graphics
float[] Bounds(uint frame);
ModelRenderData RenderData(uint section);
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model.</summary>
/// <summary>Returns the smallest rectangle that covers all rotations of all frames in a model</summary>
Rectangle AggregateBounds { get; }
}
public interface IModelWidget
{
public string Palette { get; }
public float Scale { get; }
public void Setup(Func<bool> isVisible, Func<string> getPalette, Func<string> getPlayerPalette,
Func<float> getScale, Func<IModel> getVoxel, Func<WRot> getRotation);
}
public readonly struct ModelRenderData
{
public readonly int Start;
@@ -51,13 +44,52 @@ namespace OpenRA.Graphics
}
}
public interface IModelCacheInfo : ITraitInfoInterface { }
public interface IModelCache
public interface IModelCache : IDisposable
{
IModel GetModel(string model);
IModel GetModelSequence(string model, string sequence);
bool HasModelSequence(string model, string sequence);
IVertexBuffer<ModelVertex> VertexBuffer { get; }
IVertexBuffer<Vertex> VertexBuffer { get; }
}
public interface IModelSequenceLoader
{
Action<string> OnMissingModelError { get; set; }
IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions);
}
public class PlaceholderModelSequenceLoader : IModelSequenceLoader
{
public Action<string> OnMissingModelError { get; set; }
class PlaceholderModelCache : IModelCache
{
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
public void Dispose() { }
public IModel GetModel(string model)
{
throw new NotImplementedException();
}
public IModel GetModelSequence(string model, string sequence)
{
throw new NotImplementedException();
}
public bool HasModelSequence(string model, string sequence)
{
throw new NotImplementedException();
}
}
[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)
{
return new PlaceholderModelCache();
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -12,11 +12,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Traits
namespace OpenRA.Graphics
{
public class ModelRenderProxy
{
@@ -34,19 +32,12 @@ namespace OpenRA.Mods.Cnc.Traits
}
}
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
[Desc("Render voxels")]
public class ModelRendererInfo : TraitInfo, Requires<IModelCacheInfo>
{
public override object Create(ActorInitializer init) { return new ModelRenderer(this, init.Self); }
}
public sealed class ModelRenderer : IDisposable, IRenderer, INotifyActorDisposing
public sealed class ModelRenderer : IDisposable
{
// Static constants
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
static readonly float[] ShadowAmbient = new float[] { 1, 1, 1 };
static readonly float2 SpritePadding = new(2, 2);
static readonly float2 SpritePadding = new float2(2, 2);
static readonly float[] ZeroVector = new float[] { 0, 0, 0, 1 };
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
@@ -55,32 +46,28 @@ namespace OpenRA.Mods.Cnc.Traits
readonly Renderer renderer;
readonly IShader shader;
public readonly IModelCache ModelCache;
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
readonly List<(Sheet Sheet, Action Func)> doRender = new();
readonly int sheetSize;
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)>();
SheetBuilder sheetBuilderForFrame;
bool isInFrame;
public void SetPalette(HardwarePalette palette)
public ModelRenderer(Renderer renderer, IShader shader)
{
shader.SetTexture("Palette", palette.Texture);
shader.SetVec("PaletteRows", palette.Height);
this.renderer = renderer;
this.shader = shader;
}
public ModelRenderer(ModelRendererInfo info, Actor self)
public void SetPalette(ITexture palette)
{
renderer = Game.Renderer;
shader = renderer.CreateShader(new ModelShaderBindings());
renderer.WorldRenderers = renderer.WorldRenderers.Append(this).ToArray();
shader.SetTexture("Palette", palette);
}
ModelCache = self.Trait<IModelCache>();
sheetSize = Game.Settings.Graphics.SheetSize;
var a = 2f / sheetSize;
public void SetViewportParams()
{
var a = 2f / renderer.SheetSize;
var view = new[]
{
a, 0, 0, 0,
@@ -180,7 +167,8 @@ namespace OpenRA.Mods.Cnc.Traits
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
sheetBuilderForFrame ??= new SheetBuilder(SheetType.BGRA, AllocateSheet);
if (sheetBuilderForFrame == null)
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
var sprite = sheetBuilderForFrame.Allocate(spriteSize, 0, spriteOffset);
var shadowSprite = sheetBuilderForFrame.Allocate(shadowSpriteSize, 0, shadowSpriteOffset);
@@ -189,12 +177,12 @@ namespace OpenRA.Mods.Cnc.Traits
var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2);
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, sheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, sheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
void RenderFunc()
doRender.Add((sprite.Sheet, () =>
{
foreach (var m in models)
{
@@ -226,18 +214,16 @@ namespace OpenRA.Mods.Cnc.Traits
// Transform light vector from shadow -> world -> limb coords
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
Render(rd, ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
lightAmbientColor, lightDiffuseColor, color.TextureIndex, normals.TextureIndex);
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);
// Disable shadow normals by forcing zero diffuse and identity ambient light
if (m.ShowShadow)
Render(rd, ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureIndex, normals.TextureIndex);
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
}
}
}
doRender.Add((sprite.Sheet, RenderFunc));
}));
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
@@ -252,9 +238,9 @@ namespace OpenRA.Mods.Cnc.Traits
// Width and height must be even to avoid rendering glitches
if ((width & 1) == 1)
width++;
width += 1;
if ((height & 1) == 1)
height++;
height += 1;
size = new Size(width, height);
}
@@ -282,17 +268,17 @@ namespace OpenRA.Mods.Cnc.Traits
IModelCache cache,
float[] t, float[] lightDirection,
float[] ambientLight, float[] diffuseLight,
float colorPaletteTextureIndex, float normalsPaletteTextureIndex)
float colorPaletteTextureMidIndex, float normalsPaletteTextureMidIndex)
{
shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture());
shader.SetVec("Palettes", colorPaletteTextureIndex, normalsPaletteTextureIndex);
shader.SetVec("PaletteRows", colorPaletteTextureMidIndex, normalsPaletteTextureMidIndex);
shader.SetMatrix("TransformMatrix", t);
shader.SetVec("LightDirection", lightDirection, 4);
shader.SetVec("AmbientLight", ambientLight, 3);
shader.SetVec("DiffuseLight", diffuseLight, 3);
shader.PrepareRender();
renderer.DrawBatch(cache.VertexBuffer, shader, renderData.Start, renderData.Count, PrimitiveType.TriangleList);
renderer.DrawBatch(cache.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.TriangleList);
}
public void BeginFrame()
@@ -313,14 +299,14 @@ namespace OpenRA.Mods.Cnc.Traits
Game.Renderer.Flush();
fbo.Bind();
Game.Renderer.EnableDepthBuffer();
Game.Renderer.Context.EnableDepthBuffer();
return fbo;
}
static void DisableFrameBuffer(IFrameBuffer fbo)
void DisableFrameBuffer(IFrameBuffer fbo)
{
Game.Renderer.Flush();
Game.Renderer.DisableDepthBuffer();
Game.Renderer.Context.DisableDepthBuffer();
fbo.Unbind();
}
@@ -368,7 +354,8 @@ namespace OpenRA.Mods.Cnc.Traits
return kv.Key;
}
var framebuffer = renderer.CreateFrameBuffer(new Size(sheetSize, sheetSize));
var size = new Size(renderer.SheetSize, renderer.SheetSize);
var framebuffer = renderer.Context.CreateFrameBuffer(size);
var sheet = new Sheet(SheetType.BGRA, framebuffer.Texture);
mappedBuffers.Add(sheet, framebuffer);
@@ -385,12 +372,6 @@ namespace OpenRA.Mods.Cnc.Traits
mappedBuffers.Clear();
unmappedBuffers.Clear();
renderer.WorldRenderers = renderer.WorldRenderers.Where(r => r != this).ToArray();
}
void INotifyActorDisposing.Disposing(Actor a)
{
Dispose();
}
}
}

View File

@@ -1,53 +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.Runtime.InteropServices;
namespace OpenRA.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public readonly struct ModelVertex
{
// 3d position
public readonly float X, Y, Z;
// Primary and secondary texture coordinates or RGBA color
public readonly float S, T, U, V;
// Palette and channel flags
public readonly float P, C;
public ModelVertex(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) { }
public ModelVertex(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;
}
}
public sealed class ModelShaderBindings : ShaderBindings
{
public ModelShaderBindings()
: base("model")
{ }
public override ShaderVertexAttribute[] Attributes { get; } = new[]
{
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
new ShaderVertexAttribute("aVertexTexMetadata", ShaderVertexAttributeType.Float, 2, 28),
};
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
public static Color GetColor(this IPalette palette, int index)
{
return Color.FromArgb(palette[index]);
return Color.FromArgb((int)palette[index]);
}
public static IPalette AsReadOnly(this IPalette palette)
@@ -40,7 +40,7 @@ namespace OpenRA.Graphics
return new ReadOnlyPalette(palette);
}
sealed class ReadOnlyPalette : IPalette
class ReadOnlyPalette : IPalette
{
readonly IPalette palette;
public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
@@ -103,7 +103,7 @@ namespace OpenRA.Graphics
: this(p)
{
for (var i = 0; i < Palette.Size; i++)
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
}
public ImmutablePalette(IPalette p)
@@ -142,7 +142,7 @@ namespace OpenRA.Graphics
public void SetColor(int index, Color color)
{
colors[index] = color.ToArgb();
colors[index] = (uint)color.ToArgb();
}
public void SetFromPalette(IPalette p)
@@ -153,7 +153,7 @@ namespace OpenRA.Graphics
public void ApplyRemap(IPaletteRemap r)
{
for (var i = 0; i < Palette.Size; i++)
colors[i] = r.GetRemappedColor(this.GetColor(i), i).ToArgb();
colors[i] = (uint)r.GetRemappedColor(this.GetColor(i), i).ToArgb();
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -20,12 +20,13 @@ namespace OpenRA
Automatic,
ANGLE,
Modern,
Embedded
Embedded,
Legacy
}
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int vertexBatchSize, int indexBatchSize, int videoDisplay, GLProfile profile);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -82,18 +83,16 @@ namespace OpenRA
public interface IGraphicsContext : IDisposable
{
IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct;
T[] CreateVertices<T>(int size) where T : struct;
IIndexBuffer CreateIndexBuffer(uint[] indices);
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
Vertex[] CreateVertices(int size);
ITexture CreateTexture();
IFrameBuffer CreateFrameBuffer(Size s);
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
IShader CreateShader(IShaderBindings shaderBindings);
IShader CreateShader(string name);
void EnableScissor(int x, int y, int width, int height);
void DisableScissor();
void Present();
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
void DrawElements(int numIndices, int offset);
void Clear();
void EnableDepthBuffer();
void DisableDepthBuffer();
@@ -103,14 +102,7 @@ namespace OpenRA
string GLVersion { get; }
}
public interface IRenderer
{
void BeginFrame();
void EndFrame();
void SetPalette(HardwarePalette palette);
}
public interface IVertexBuffer<T> : IDisposable where T : struct
public interface IVertexBuffer<T> : IDisposable
{
void Bind();
void SetData(T[] vertices, int length);
@@ -122,11 +114,6 @@ namespace OpenRA
void SetData(T[] vertices, int offset, int start, int length);
}
public interface IIndexBuffer : IDisposable
{
void Bind();
}
public interface IShader
{
void SetBool(string name, bool value);
@@ -137,17 +124,6 @@ namespace OpenRA
void SetTexture(string param, ITexture texture);
void SetMatrix(string param, float[] mtx);
void PrepareRender();
void Bind();
}
public interface IShaderBindings
{
string VertexShaderName { get; }
string VertexShaderCode { get; }
string FragmentShaderName { get; }
string FragmentShaderCode { get; }
int Stride { get; }
ShaderVertexAttribute[] Attributes { get; }
}
public enum TextureScaleFilter { Nearest, Linear }
@@ -156,7 +132,6 @@ namespace OpenRA
{
void SetData(byte[] colors, int width, int height);
void SetFloatData(float[] data, int width, int height);
void SetDataFromReadBuffer(Rectangle rect);
byte[] GetData();
Size Size { get; }
TextureScaleFilter ScaleFilter { get; set; }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -20,14 +20,12 @@ namespace OpenRA.Graphics
readonly int[] remapIndices;
readonly float hue;
readonly float saturation;
readonly float value;
public PlayerColorRemap(int[] remapIndices, Color color)
public PlayerColorRemap(int[] remapIndices, float hue, float saturation)
{
this.remapIndices = remapIndices;
var (r, g, b) = color.ToLinear();
(hue, saturation, value) = Color.RgbToHsv(r, g, b);
this.hue = hue;
this.saturation = saturation;
}
public Color GetRemappedColor(Color original, int index)
@@ -44,7 +42,7 @@ namespace OpenRA.Graphics
var value = Math.Max(Math.Max(r, g), b);
// Construct the new RGB color
(r, g, b) = Color.HsvToRgb(hue, saturation, value * this.value);
(r, g, b) = Color.HsvToRgb(hue, saturation, value);
// Convert linear back to SRGB and pre-multiply by the alpha
return Color.FromLinear(original.A, r, g, b);

View File

@@ -1,64 +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.Runtime.InteropServices;
namespace OpenRA.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public readonly struct RenderPostProcessPassVertex
{
public readonly float X, Y;
public RenderPostProcessPassVertex(float x, float y)
{
X = x; Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public readonly struct RenderPostProcessPassTexturedVertex
{
// 3d position
public readonly float X, Y;
public readonly float S, T;
public RenderPostProcessPassTexturedVertex(float x, float y, float s, float t)
{
X = x; Y = y;
S = s; T = t;
}
}
public sealed class RenderPostProcessPassShaderBindings : ShaderBindings
{
public RenderPostProcessPassShaderBindings(string name)
: base("postprocess", "postprocess_" + name) { }
public override ShaderVertexAttribute[] Attributes { get; } = new[]
{
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0)
};
}
public sealed class RenderPostProcessPassTexturedShaderBindings : ShaderBindings
{
public RenderPostProcessPassTexturedShaderBindings(string name)
: base("postprocess_textured", "postprocess_textured_" + name)
{ }
public override ShaderVertexAttribute[] Attributes { get; } = new[]
{
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0),
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8),
};
}
}

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -18,10 +18,10 @@ namespace OpenRA.Graphics
{
public class RgbaColorRenderer
{
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
static readonly float3 Offset = new float3(0.5f, 0.5f, 0f);
readonly SpriteRenderer parent;
readonly Vertex[] vertices = new Vertex[4];
readonly Vertex[] vertices = new Vertex[6];
public RgbaColorRenderer(SpriteRenderer parent)
{
@@ -45,12 +45,14 @@ namespace OpenRA.Graphics
var eb = endColor.B / 255.0f;
var ea = endColor.A / 255.0f;
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0);
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0);
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0);
vertices[3] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0);
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
vertices[1] = new Vertex(start + corner + Offset, sr, sg, sb, sa, 0, 0);
vertices[2] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
vertices[3] = new Vertex(end + corner + Offset, er, eg, eb, ea, 0, 0);
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
parent.DrawRGBAQuad(vertices, blendMode);
parent.DrawRGBAVertices(vertices, blendMode);
}
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
@@ -64,19 +66,21 @@ namespace OpenRA.Graphics
var b = color.B / 255.0f;
var a = color.A / 255.0f;
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0);
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0);
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0);
vertices[3] = new Vertex(end - corner + Offset, r, g, b, a, 0);
parent.DrawRGBAQuad(vertices, blendMode);
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
vertices[1] = new Vertex(start + corner + Offset, r, g, b, a, 0, 0);
vertices[2] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode);
}
/// <summary>
/// Calculate the 2D intersection of two lines.
/// Will behave badly if the lines are parallel.
/// Z position is the average of a and b (ignores actual intersection point if it exists).
/// Z position is the average of a and b (ignores actual intersection point if it exists)
/// </summary>
static float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in 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);
@@ -134,7 +138,7 @@ namespace OpenRA.Graphics
// Segment is part of closed loop
if (closed)
{
var prev = points[^1];
var prev = points[points.Length - 1];
var prevDir = (start - prev) / (start - prev).XY.Length;
var prevCorner = width / 2 * new float3(-prevDir.Y, prevDir.X, prevDir.Z);
ca = IntersectionOf(start - prevCorner, prevDir, start - corner, dir);
@@ -153,11 +157,13 @@ namespace OpenRA.Graphics
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
// Fill segment
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0);
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0);
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0);
vertices[3] = new Vertex(cd + Offset, r, g, b, a, 0);
parent.DrawRGBAQuad(vertices, blendMode);
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices, blendMode);
// Advance line segment
end = next;
@@ -194,6 +200,20 @@ namespace OpenRA.Graphics
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
}
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
var cg = color.G / 255.0f;
var cb = color.B / 255.0f;
var ca = color.A / 255.0f;
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);
}
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
{
var tr = new float3(br.X, tl.Y, tl.Z);
@@ -209,11 +229,13 @@ namespace OpenRA.Graphics
var cb = color.B / 255.0f;
var ca = color.A / 255.0f;
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0);
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0);
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0);
vertices[3] = new Vertex(d + Offset, cr, cg, cb, ca, 0);
parent.DrawRGBAQuad(vertices, blendMode);
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);
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);
}
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)
@@ -221,9 +243,11 @@ namespace OpenRA.Graphics
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor);
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
vertices[3] = VertexWithColor(d + Offset, bottomLeftColor);
vertices[3] = VertexWithColor(c + Offset, bottomRightColor);
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
parent.DrawRGBAQuad(vertices, blendMode);
parent.DrawRGBAVertices(vertices, blendMode);
}
static Vertex VertexWithColor(in float3 xyz, Color color)
@@ -234,7 +258,7 @@ namespace OpenRA.Graphics
var cb = color.B / 255.0f;
var ca = color.A / 255.0f;
return new Vertex(xyz, cr, cg, cb, ca, 0);
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
}
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)

View File

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

View File

@@ -0,0 +1,152 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
using Sequences = IReadOnlyDictionary<string, Lazy<IReadOnlyDictionary<string, ISpriteSequence>>>;
using UnitSequences = Lazy<IReadOnlyDictionary<string, ISpriteSequence>>;
public interface ISpriteSequence
{
string Name { get; }
int Start { get; }
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);
}
public interface ISpriteSequenceLoader
{
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
}
public class SequenceProvider : IDisposable
{
readonly ModData modData;
readonly string tileSet;
readonly Lazy<Sequences> sequences;
readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache => spriteCache.Value;
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
{
this.modData = modData;
this.tileSet = tileSet;
sequences = Exts.Lazy(() =>
{
using (new Support.PerfTimer("LoadSequences"))
return Load(fileSystem, additionalSequences);
});
spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders));
}
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.");
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
return seq;
}
public IEnumerable<string> Images => sequences.Value.Keys;
public bool HasSequence(string unitName)
{
return sequences.Value.ContainsKey(unitName);
}
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.");
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.");
return unitSeq.Value.Keys;
}
Sequences Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
{
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
var items = new Dictionary<string, UnitSequences>();
foreach (var node in nodes)
{
// Nodes starting with ^ are inheritable but never loaded directly
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
continue;
var key = node.Value.ToLines(node.Key).JoinWith("|");
if (sequenceCache.TryGetValue(key, out var t))
items.Add(node.Key, t);
else
{
t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node));
sequenceCache.Add(key, t);
items.Add(node.Key, t);
}
}
return items;
}
public void Preload()
{
foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.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();
}
public void Dispose()
{
if (spriteCache.IsValueCreated)
foreach (var sb in SpriteCache.SheetBuilders.Values)
sb.Dispose();
}
}
}

View File

@@ -1,119 +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.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public interface ISpriteSequence
{
string Name { get; }
int Length { get; }
int Facings { get; }
int Tick { get; }
int ZOffset { get; }
int ShadowZOffset { get; }
Rectangle Bounds { get; }
bool IgnoreWorldTint { get; }
float Scale { get; }
void ResolveSprites(SpriteCache cache);
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing);
(Sprite Sprite, WAngle Rotation) GetSpriteWithRotation(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing);
float GetAlpha(int frame);
}
public interface ISpriteSequenceLoader
{
int BgraSheetSize { get; }
int IndexedSheetSize { get; }
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
}
public sealed class SequenceSet : IDisposable
{
readonly ModData modData;
readonly string tileSet;
readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> images;
public SpriteCache SpriteCache { get; }
public SequenceSet(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
{
this.modData = modData;
this.tileSet = tileSet;
SpriteCache = new SpriteCache(fileSystem, modData.SpriteLoaders, modData.SpriteSequenceLoader.BgraSheetSize, modData.SpriteSequenceLoader.IndexedSheetSize);
using (new Support.PerfTimer("LoadSequences"))
images = Load(fileSystem, additionalSequences);
}
public ISpriteSequence GetSequence(string image, string sequence)
{
if (!images.TryGetValue(image, out var sequences))
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
if (!sequences.TryGetValue(sequence, out var seq))
throw new InvalidOperationException($"Image `{image}` does not have a sequence named `{sequence}`.");
return seq;
}
public IEnumerable<string> Images => images.Keys;
public bool HasSequence(string image, string sequence)
{
if (!images.TryGetValue(image, out var sequences))
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
return sequences.ContainsKey(sequence);
}
public IEnumerable<string> Sequences(string image)
{
if (!images.TryGetValue(image, out var sequences))
throw new InvalidOperationException($"Image `{image}` does not have any sequences defined.");
return sequences.Keys;
}
IReadOnlyDictionary<string, IReadOnlyDictionary<string, ISpriteSequence>> Load(IReadOnlyFileSystem fileSystem, MiniYaml additionalSequences)
{
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
var images = new Dictionary<string, IReadOnlyDictionary<string, ISpriteSequence>>();
foreach (var node in nodes)
{
// Nodes starting with ^ are inheritable but never loaded directly
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
continue;
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
}
return images;
}
public void LoadSprites()
{
SpriteCache.LoadReservations(modData);
foreach (var sequences in images.Values)
foreach (var sequence in sequences)
sequence.Value.ResolveSprites(SpriteCache);
}
public void Dispose()
{
SpriteCache.Dispose();
}
}
}

View File

@@ -1,70 +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;
using System.Linq;
namespace OpenRA.Graphics
{
public enum ShaderVertexAttributeType
{
// Assign the underlying OpenGL type values
// to simplify enum use in the shader
Float = 0x1406, // GL_FLOAT
Int = 0x1404, // GL_INT
UInt = 0x1405 // GL_UNSIGNED_INT
}
public readonly struct ShaderVertexAttribute
{
public readonly string Name;
public readonly ShaderVertexAttributeType Type;
public readonly int Components;
public readonly int Offset;
public ShaderVertexAttribute(string name, ShaderVertexAttributeType type, int components, int offset)
{
Name = name;
Type = type;
Components = components;
Offset = offset;
}
}
public abstract class ShaderBindings : IShaderBindings
{
public string VertexShaderName { get; }
public string VertexShaderCode { get; }
public string FragmentShaderName { get; }
public string FragmentShaderCode { get; }
public int Stride { get; }
public abstract ShaderVertexAttribute[] Attributes { get; }
protected ShaderBindings(string name)
: this(name, name) { }
protected ShaderBindings(string vertexName, string fragmentName)
{
Stride = Attributes.Sum(a => a.Components * 4);
VertexShaderName = vertexName;
VertexShaderCode = GetShaderCode(VertexShaderName + ".vert");
FragmentShaderName = fragmentName;
FragmentShaderCode = GetShaderCode(FragmentShaderName + ".frag");
}
public static string GetShaderCode(string filename)
{
var filepath = Path.Combine(Platform.EngineDir, "glsl", filename);
return File.ReadAllText(filepath);
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -34,16 +34,15 @@ namespace OpenRA.Graphics
public sealed class SheetBuilder : IDisposable
{
public readonly SheetType Type;
readonly List<Sheet> sheets = new();
readonly List<Sheet> sheets = new List<Sheet>();
readonly Func<Sheet> allocateSheet;
readonly int margin;
Sheet current;
TextureChannel channel;
int rowHeight = 0;
int2 p;
public Sheet Current { get; private set; }
public TextureChannel CurrentChannel { get; private set; }
public IEnumerable<Sheet> AllSheets => sheets;
public static Sheet AllocateSheet(SheetType type, int sheetSize)
{
return new Sheet(type, new Size(sheetSize, sheetSize));
@@ -74,33 +73,33 @@ namespace OpenRA.Graphics
public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
{
CurrentChannel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
channel = t == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
Type = t;
Current = allocateSheet();
sheets.Add(Current);
current = allocateSheet();
sheets.Add(current);
this.allocateSheet = allocateSheet;
this.margin = margin;
}
public Sprite Add(ISpriteFrame frame, bool premultiplied = false) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset, premultiplied); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size, bool premultiplied = false) { return Add(src, type, size, 0, float3.Zero, premultiplied); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false)
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)
{
// Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0)
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
var rect = Allocate(size, zRamp, spriteOffset);
Util.FastCopyIntoChannel(rect, src, type, premultiplied);
Current.CommitBufferedData();
Util.FastCopyIntoChannel(rect, src, type);
current.CommitBufferedData();
return rect;
}
public Sprite Add(Png src, float scale = 1f)
{
var rect = Allocate(new Size(src.Width, src.Height), scale);
var rect = Allocate(new Size(src.Width, src.Height), scale);
Util.FastCopyIntoSprite(rect, src);
Current.CommitBufferedData();
current.CommitBufferedData();
return rect;
}
@@ -116,7 +115,7 @@ namespace OpenRA.Graphics
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)
{
if (imageSize.Width + p.X + margin > Current.Size.Width)
if (imageSize.Width + p.X + margin > current.Size.Width)
{
p = new int2(0, p.Y + rowHeight + margin);
rowHeight = imageSize.Height;
@@ -125,29 +124,33 @@ namespace OpenRA.Graphics
if (imageSize.Height > rowHeight)
rowHeight = imageSize.Height;
if (p.Y + imageSize.Height + margin > Current.Size.Height)
if (p.Y + imageSize.Height + margin > current.Size.Height)
{
var next = NextChannel(CurrentChannel);
var next = NextChannel(channel);
if (next == null)
{
Current.ReleaseBuffer();
Current = allocateSheet();
sheets.Add(Current);
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
current.ReleaseBuffer();
current = allocateSheet();
sheets.Add(current);
channel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
}
else
CurrentChannel = next.Value;
channel = next.Value;
rowHeight = imageSize.Height;
p = int2.Zero;
}
var rect = new Sprite(Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
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);
return rect;
}
public Sheet Current => current;
public TextureChannel CurrentChannel => channel;
public IEnumerable<Sheet> AllSheets => sheets;
public void Dispose()
{
foreach (var sheet in sheets)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -42,11 +42,11 @@ namespace OpenRA.Graphics
// 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.
const float 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;
var inset = 1 / 128f;
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
}
}

View File

@@ -1,170 +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 System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public sealed class SpriteCache : IDisposable
{
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location, Func<ISpriteFrame, ISpriteFrame> AdjustFrame, bool Premultiplied)> spriteReservations = new();
readonly Dictionary<string, List<int>> reservationsByFilename = new();
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
int nextReservationToken = 1;
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
{
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
{
{ SheetType.Indexed, new SheetBuilder(SheetType.Indexed, indexedSheetSize, indexedSheetMargin) },
{ SheetType.BGRA, new SheetBuilder(SheetType.BGRA, bgraSheetSize, bgraSheetMargin) }
};
this.fileSystem = fileSystem;
this.loaders = loaders;
}
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location, Func<ISpriteFrame, ISpriteFrame> adjustFrame = null, bool premultiplied = false)
{
var token = nextReservationToken++;
spriteReservations[token] = (frames?.ToArray(), location, adjustFrame, premultiplied);
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
return token;
}
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders, out TypeDictionary metadata)
{
metadata = null;
if (!fileSystem.TryOpen(filename, out var stream))
return null;
using (stream)
{
foreach (var loader in loaders)
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
return frames;
return null;
}
}
public void LoadReservations(ModData modData)
{
foreach (var sb in SheetBuilders.Values)
sb.Current.CreateBuffer();
var pendingResolve = new List<(
string Filename,
int FrameIndex,
bool Premultiplied,
Func<ISpriteFrame, ISpriteFrame> AdjustFrame,
ISpriteFrame Frame,
Sprite[] SpritesForToken)>();
foreach (var (filename, tokens) in reservationsByFilename)
{
modData.LoadScreen?.Display();
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
foreach (var token in tokens)
{
if (spriteReservations.TryGetValue(token, out var rs))
{
if (loadedFrames != null)
{
var resolved = new Sprite[loadedFrames.Length];
resolvedSprites[token] = resolved;
var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length);
foreach (var i in frames)
{
var frame = loadedFrames[i];
if (rs.AdjustFrame != null)
frame = rs.AdjustFrame(frame);
pendingResolve.Add((filename, i, rs.Premultiplied, rs.AdjustFrame, frame, resolved));
}
}
else
{
resolvedSprites[token] = null;
missingFiles[token] = (filename, rs.Location);
}
}
}
}
spriteReservations.Clear();
spriteReservations.TrimExcess();
reservationsByFilename.Clear();
reservationsByFilename.TrimExcess();
// When the sheet builder is adding sprites, it reserves height for the tallest sprite seen along the row.
// We can achieve better sheet packing by keeping sprites with similar heights together.
var orderedPendingResolve = pendingResolve.OrderBy(x => x.Frame.Size.Height);
var spriteCache = new Dictionary<(
string Filename,
int FrameIndex,
bool Premultiplied,
Func<ISpriteFrame, ISpriteFrame> AdjustFrame),
Sprite>(pendingResolve.Count);
foreach (var (filename, frameIndex, premultiplied, adjustFrame, frame, spritesForToken) in orderedPendingResolve)
{
// Premultiplied and non-premultiplied sprites must be cached separately
// to cover the case where the same image is requested in both versions.
spritesForToken[frameIndex] = spriteCache.GetOrAdd(
(filename, frameIndex, premultiplied, adjustFrame),
_ =>
{
var sheetBuilder = SheetBuilders[SheetBuilder.FrameTypeToSheetType(frame.Type)];
return sheetBuilder.Add(frame, premultiplied);
});
modData.LoadScreen?.Display();
}
foreach (var sb in SheetBuilders.Values)
sb.Current.ReleaseBuffer();
}
public Sprite[] ResolveSprites(int token)
{
if (!resolvedSprites.Remove(token, out var resolved))
throw new InvalidOperationException($"{nameof(token)} {token} has either already been resolved, or was never reserved via {nameof(ReserveSprites)}");
resolvedSprites.TrimExcess();
if (missingFiles.TryGetValue(token, out var r))
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
return resolved;
}
public IEnumerable<(string Filename, MiniYamlNode.SourceLocation Location)> MissingFiles => missingFiles.Values.ToHashSet();
public void Dispose()
{
foreach (var sb in SheetBuilders.Values)
sb.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -123,7 +123,7 @@ namespace OpenRA.Graphics
}
}
static float2 Rotate(float2 v, float sina, float cosa, float2 offset)
float2 Rotate(float2 v, float sina, float cosa, float2 offset)
{
return new float2(
v.X * cosa - v.Y * sina + offset.X,
@@ -427,7 +427,7 @@ namespace OpenRA.Graphics
}
}
sealed class GlyphInfo
class GlyphInfo
{
public float Advance;
public int2 Offset;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -9,7 +9,10 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Primitives;
@@ -18,34 +21,24 @@ 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().
/// to a 32bit ARGB value, such as that returned by Color.ToArgb()!
/// </summary>
public enum SpriteFrameType
{
/// <summary>
/// 8 bit index into an external palette.
/// </summary>
// 8 bit index into an external palette
Indexed8,
/// <summary>
/// 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).
/// </summary>
// 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,
/// <summary>
/// Like BGRA, but without an alpha channel.
/// </summary>
// Like BGRA, but without an alpha channel
Bgr24,
/// <summary>
/// 32 bit color in big-endian format, like png.
/// </summary>
// 32 bit color in big-endian format, like png
Rgba32,
/// <summary>
/// Like RGBA, but without an alpha channel.
/// </summary>
// Like RGBA, but without an alpha channel
Rgb24
}
@@ -74,6 +67,94 @@ namespace OpenRA.Graphics
bool DisableExportPadding { get; }
}
public class SpriteCache
{
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;
readonly Dictionary<string, List<Sprite[]>> sprites = new Dictionary<string, List<Sprite[]>>();
readonly Dictionary<string, ISpriteFrame[]> unloadedFrames = new Dictionary<string, ISpriteFrame[]>();
readonly Dictionary<string, TypeDictionary> metadata = new Dictionary<string, TypeDictionary>();
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
this.fileSystem = fileSystem;
this.loaders = loaders;
}
/// <summary>
/// Returns the first set of sprites with the given filename.
/// If getUsedFrames is defined then the indices returned by the function call
/// are guaranteed to be loaded. The value of other indices in the returned
/// array are undefined and should never be accessed.
/// </summary>
public Sprite[] this[string filename, Func<int, IEnumerable<int>> getUsedFrames = null]
{
get
{
var allSprites = sprites.GetOrAdd(filename);
var sprite = allSprites.FirstOrDefault();
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
unloaded = null;
// This is the first time that the file has been requested
// Load all of the frames into the unused buffer and initialize
// the loaded cache (initially empty)
if (sprite == null)
{
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
unloadedFrames[filename] = unloaded;
metadata[filename] = fileMetadata;
sprite = new Sprite[unloaded.Length];
allSprites.Add(sprite);
}
// HACK: The sequence code relies on side-effects from getUsedFrames
var indices = getUsedFrames != null ? getUsedFrames(sprite.Length) :
Enumerable.Range(0, sprite.Length);
// Load any unused frames into the SheetBuilder
if (unloaded != null)
{
foreach (var i in indices)
{
if (unloaded[i] != null)
{
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
unloaded[i] = null;
}
}
// All frames have been loaded
if (unloaded.All(f => f == null))
unloadedFrames.Remove(filename);
}
return sprite;
}
}
/// <summary>
/// Returns a TypeDictionary containing any metadata defined by the frame
/// or null if the frame does not define metadata.
/// </summary>
public TypeDictionary FrameMetadata(string filename)
{
if (!metadata.TryGetValue(filename, out var fileMetadata))
{
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
metadata[filename] = fileMetadata;
}
return fileMetadata;
}
}
public class FrameCache
{
readonly Cache<string, ISpriteFrame[]> frames;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -21,95 +21,102 @@ namespace OpenRA.Graphics
readonly Sprite sprite;
readonly WPos pos;
readonly WVec offset;
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)
{
this.sprite = sprite;
this.pos = pos;
Offset = offset;
ZOffset = zOffset;
Palette = palette;
this.offset = offset;
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.rotation = rotation;
Tint = tint;
IsDecoration = isDecoration;
TintModifiers = tintModifiers;
Alpha = alpha;
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))
Palette = null;
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 => pos + Offset;
public WVec Offset { get; }
public PaletteReference Palette { get; }
public int ZOffset { get; }
public bool IsDecoration { get; }
public WPos Pos => pos + offset;
public WVec Offset => offset;
public PaletteReference Palette => palette;
public int ZOffset => zOffset;
public bool IsDecoration => isDecoration;
public float Alpha { get; }
public float3 Tint { get; }
public TintModifiers TintModifiers { get; }
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);
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);
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);
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);
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);
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);
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration, rotation);
}
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);
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(offset) - new float3((int)s.X, (int)s.Y, s.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)
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)
var a = alpha;
if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;
wsr.DrawSprite(sprite, Palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a, rotation.RendererRadians());
}
public void RenderDebugGeometry(WorldRenderer wr)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using OpenRA.Primitives;
namespace OpenRA.Graphics
@@ -20,7 +19,6 @@ namespace OpenRA.Graphics
{
public const int SheetCount = 8;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
static readonly int UintSize = Marshal.SizeOf(typeof(uint));
readonly Renderer renderer;
readonly IShader shader;
@@ -29,21 +27,21 @@ namespace OpenRA.Graphics
readonly Sheet[] sheets = new Sheet[SheetCount];
BlendMode currentBlend = BlendMode.Alpha;
int vertexCount = 0;
int sheetCount = 0;
int nv = 0;
int ns = 0;
public SpriteRenderer(Renderer renderer, IShader shader)
{
this.renderer = renderer;
this.shader = shader;
vertices = renderer.Context.CreateVertices<Vertex>(renderer.TempVertexBufferSize);
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
}
public void Flush()
{
if (vertexCount > 0)
if (nv > 0)
{
for (var i = 0; i < sheetCount; i++)
for (var i = 0; i < ns; i++)
{
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
sheets[i] = null;
@@ -52,11 +50,12 @@ namespace OpenRA.Graphics
renderer.Context.SetBlendMode(currentBlend);
shader.PrepareRender();
renderer.DrawQuadBatch(ref vertices, shader, vertexCount);
// PERF: The renderer may choose to replace vertices with a different temporary buffer.
renderer.DrawBatch(ref vertices, nv, PrimitiveType.TriangleList);
renderer.Context.SetBlendMode(BlendMode.None);
vertexCount = 0;
sheetCount = 0;
nv = 0;
ns = 0;
}
}
@@ -64,7 +63,7 @@ namespace OpenRA.Graphics
{
renderer.CurrentBatchRenderer = this;
if (s.BlendMode != currentBlend || vertexCount + 4 > renderer.TempVertexBufferSize)
if (s.BlendMode != currentBlend || nv + 6 > renderer.TempBufferSize)
Flush();
currentBlend = s.BlendMode;
@@ -72,7 +71,7 @@ namespace OpenRA.Graphics
// Check if the sheet (or secondary data sheet) have already been mapped
var sheet = s.Sheet;
var sheetIndex = 0;
for (; sheetIndex < sheetCount; sheetIndex++)
for (; sheetIndex < ns; sheetIndex++)
if (sheets[sheetIndex] == sheet)
break;
@@ -81,7 +80,7 @@ namespace OpenRA.Graphics
if (ss != null)
{
var secondarySheet = ss.SecondarySheet;
for (; secondarySheetIndex < sheetCount; secondarySheetIndex++)
for (; secondarySheetIndex < ns; secondarySheetIndex++)
if (sheets[secondarySheetIndex] == secondarySheet)
break;
@@ -100,22 +99,22 @@ namespace OpenRA.Graphics
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
}
if (sheetIndex >= sheetCount)
if (sheetIndex >= ns)
{
sheets[sheetIndex] = sheet;
sheetCount++;
ns++;
}
if (secondarySheetIndex >= sheetCount && ss != null)
if (secondarySheetIndex >= ns && ss != null)
{
sheets[secondarySheetIndex] = ss.SecondarySheet;
sheetCount++;
ns++;
}
return new int2(sheetIndex, secondarySheetIndex);
}
static int ResolveTextureIndex(Sprite s, PaletteReference pal)
float ResolveTextureIndex(Sprite s, PaletteReference pal)
{
if (pal == null)
return 0;
@@ -129,20 +128,20 @@ namespace OpenRA.Graphics
return pal.TextureIndex;
}
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale, float rotation = 0f)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
vertexCount += 4;
nv += 6;
}
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, float rotation = 0f)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, vertexCount, scale * s.Size, float3.Ones,
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones,
1f, rotation);
vertexCount += 4;
nv += 6;
}
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
@@ -150,13 +149,13 @@ namespace OpenRA.Graphics
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, rotation);
}
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha,
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, vertexCount, scale * s.Size, tint, alpha,
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha,
rotation);
vertexCount += 4;
nv += 6;
}
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
@@ -165,14 +164,14 @@ namespace OpenRA.Graphics
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha, rotation);
}
internal void DrawSprite(Sprite s, int paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
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, vertexCount);
vertexCount += 4;
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
nv += 6;
}
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, IIndexBuffer indices, int start, int length, IEnumerable<Sheet> sheets, BlendMode blendMode)
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)
@@ -186,7 +185,7 @@ namespace OpenRA.Graphics
renderer.Context.SetBlendMode(blendMode);
shader.PrepareRender();
renderer.DrawQuadBatch(buffer, indices, shader, length, UintSize * start);
renderer.DrawBatch(buffer, start, length, type);
renderer.Context.SetBlendMode(BlendMode.None);
}
@@ -197,32 +196,29 @@ namespace OpenRA.Graphics
}
// For RGBAColorRenderer
internal void DrawRGBAQuad(Vertex[] v, BlendMode blendMode)
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
{
renderer.CurrentBatchRenderer = this;
if (currentBlend != blendMode || vertexCount + 4 > renderer.TempVertexBufferSize)
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
Flush();
currentBlend = blendMode;
Array.Copy(v, 0, vertices, vertexCount, v.Length);
vertexCount += 4;
Array.Copy(v, 0, vertices, nv, v.Length);
nv += v.Length;
}
public void SetPalette(HardwarePalette palette)
public void SetPalette(ITexture palette, ITexture colorShifts)
{
shader.SetTexture("Palette", palette.Texture);
shader.SetTexture("ColorShifts", palette.ColorShifts);
shader.SetVec("PaletteRows", palette.Height);
shader.SetTexture("Palette", palette);
shader.SetTexture("ColorShifts", colorShifts);
}
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
{
// OpenGL only renders x and y coordinates inside [-1, 1] range. We project world coordinates
// using p1 to values [0, 2] and then subtract by 1 using p2, where p stands for projection. It's
// standard practice for shaders to use a projection matrix, but as we project orthographically
// we are able to send less data to the GPU.
// 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);
@@ -243,8 +239,8 @@ namespace OpenRA.Graphics
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("p1", width, height, -depth);
shader.SetVec("p2", -1, -1, depthMargin != 0f ? 1 : 0);
shader.SetVec("r1", width, height, -depth);
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
}
public void SetDepthPreview(bool enabled, float contrast, float offset)

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -12,15 +12,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
namespace OpenRA.Graphics
{
public sealed class TerrainSpriteLayer : IDisposable
{
// PERF: we can reuse the IndexBuffer as all layers have the same size.
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = new();
readonly IndexBufferRc indexBufferWrapper;
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
public readonly BlendMode BlendMode;
@@ -30,9 +27,8 @@ namespace OpenRA.Graphics
readonly IVertexBuffer<Vertex> vertexBuffer;
readonly Vertex[] vertices;
readonly bool[] ignoreTint;
readonly HashSet<int> dirtyRows = new();
readonly int indexRowStride;
readonly int vertexRowStride;
readonly HashSet<int> dirtyRows = new HashSet<int>();
readonly int rowStride;
readonly bool restrictToBounds;
readonly WorldRenderer worldRenderer;
@@ -47,25 +43,19 @@ namespace OpenRA.Graphics
this.emptySprite = emptySprite;
sheets = new Sheet[SpriteRenderer.SheetCount];
BlendMode = blendMode;
map = world.Map;
rowStride = 6 * map.MapSize.X;
vertexRowStride = 4 * map.MapSize.X;
vertices = new Vertex[vertexRowStride * map.MapSize.Y];
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer<Vertex>(vertices.Length);
indexRowStride = 6 * map.MapSize.X;
lock (IndexBuffers)
{
indexBufferWrapper = IndexBuffers.GetValue(world, world => new IndexBufferRc(world));
indexBufferWrapper.AddRef();
}
vertices = new Vertex[rowStride * map.MapSize.Y];
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
wr.PaletteInvalidated += UpdatePaletteIndices;
if (wr.TerrainLighting != null)
{
ignoreTint = new bool[vertexRowStride * map.MapSize.Y];
ignoreTint = new bool[rowStride * map.MapSize.Y];
wr.TerrainLighting.CellChanged += UpdateTint;
}
}
@@ -75,9 +65,8 @@ namespace OpenRA.Graphics
for (var i = 0; i < vertices.Length; i++)
{
var v = vertices[i];
var p = palettes[i / 4]?.TextureIndex ?? 0;
var c = (uint)((p & 0xFFFF) << 16) | (v.C & 0xFFFF);
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, c, v.R, v.G, v.B, v.A);
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);
}
for (var row = 0; row < map.MapSize.Y; row++)
@@ -108,13 +97,13 @@ namespace OpenRA.Graphics
void UpdateTint(MPos uv)
{
var offset = vertexRowStride * uv.V + 4 * uv.U;
var offset = rowStride * uv.V + 6 * uv.U;
if (ignoreTint[offset])
{
for (var i = 0; i < 4; i++)
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.C, v.A * float3.Ones, v.A);
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * float3.Ones, v.A);
}
return;
@@ -125,7 +114,7 @@ namespace OpenRA.Graphics
// transparent for isometric tiles
var tl = worldRenderer.TerrainLighting;
var pos = map.CenterOfCell(uv.ToCPos(map));
var step = map.Grid.TileScale / 2;
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
var weights = new[]
{
tl.TintAt(pos + new WVec(-step, -step, 0)),
@@ -136,10 +125,10 @@ namespace OpenRA.Graphics
// 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 < 4; i++)
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.C, v.A * weights[i], v.A);
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);
@@ -191,7 +180,7 @@ namespace OpenRA.Graphics
if (!map.Tiles.Contains(uv))
return;
var offset = vertexRowStride * uv.V + 4 * uv.U;
var offset = rowStride * uv.V + 6 * uv.U;
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
palettes[uv.V * map.MapSize.X + uv.U] = palette;
@@ -220,13 +209,13 @@ namespace OpenRA.Graphics
if (!dirtyRows.Remove(row))
continue;
var rowOffset = vertexRowStride * row;
vertexBuffer.SetData(vertices, rowOffset, rowOffset, vertexRowStride);
var rowOffset = rowStride * row;
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
}
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, indexBufferWrapper.Buffer, indexRowStride * firstRow,
indexRowStride * (lastRow - firstRow), sheets, BlendMode);
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
PrimitiveType.TriangleList, sheets, BlendMode);
Game.Renderer.Flush();
}
@@ -238,29 +227,6 @@ namespace OpenRA.Graphics
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
vertexBuffer.Dispose();
lock (IndexBuffers)
indexBufferWrapper.Dispose();
}
sealed class IndexBufferRc : IDisposable
{
public IIndexBuffer Buffer;
int count;
public IndexBufferRc(World world)
{
Buffer = Game.Renderer.Context.CreateIndexBuffer(Util.CreateQuadIndices(world.Map.MapSize.X * world.Map.MapSize.Y));
}
public void AddRef() { count++; }
public void Dispose()
{
count--;
if (count == 0)
Buffer.Dispose();
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -16,7 +16,10 @@ namespace OpenRA.Graphics
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
{
readonly Sprite sprite;
readonly WPos effectiveWorldPos;
readonly int2 screenPos;
readonly int zOffset;
readonly PaletteReference palette;
readonly float scale;
readonly float alpha;
readonly float rotation = 0f;
@@ -24,10 +27,10 @@ namespace OpenRA.Graphics
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f)
{
this.sprite = sprite;
Pos = effectiveWorldPos;
this.effectiveWorldPos = effectiveWorldPos;
this.screenPos = screenPos;
ZOffset = zOffset;
Palette = palette;
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.alpha = alpha;
this.rotation = rotation;
@@ -36,18 +39,18 @@ namespace OpenRA.Graphics
// 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))
Palette = null;
this.palette = null;
}
// Does not exist in the world, so a world positions don't make sense
public WPos Pos { get; }
public WPos Pos => effectiveWorldPos;
public WVec Offset => WVec.Zero;
public bool IsDecoration => true;
public PaletteReference Palette { get; }
public int ZOffset { get; }
public PaletteReference Palette => palette;
public int ZOffset => zOffset;
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation); }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale, alpha, rotation); }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(in WVec vec) { return this; }
public IRenderable AsDecoration() { return this; }
@@ -55,7 +58,7 @@ namespace OpenRA.Graphics
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, palette, screenPos, scale, float3.Ones, alpha, rotation);
}
public void RenderDebugGeometry(WorldRenderer wr)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -10,7 +10,6 @@
#endregion
using System;
using System.Runtime.InteropServices;
using OpenRA.FileFormats;
using OpenRA.Primitives;
@@ -21,17 +20,7 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static uint[] CreateQuadIndices(int quads)
{
var indices = new uint[quads * 6];
ReadOnlySpan<uint> cornerVertexMap = stackalloc uint[] { 0, 1, 2, 2, 3, 0 };
for (var i = 0; i < indices.Length; i++)
indices[i] = cornerVertexMap[i % 6] + (uint)(4 * (i / 6));
return indices;
}
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, int paletteTextureIndex, int nv,
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
in float3 size, in float3 tint, float alpha, float rotation = 0f)
{
float3 a, b, c, d;
@@ -73,7 +62,7 @@ namespace OpenRA.Graphics
public static void FastCreateQuad(Vertex[] vertices,
in float3 a, in float3 b, in float3 c, in float3 d,
Sprite r, int2 samplers, int paletteTextureIndex,
Sprite r, int2 samplers, float paletteTextureIndex,
in float3 tint, float alpha, int nv)
{
float sl = 0;
@@ -95,33 +84,76 @@ namespace OpenRA.Graphics
attribC |= samplers.Y << 9;
}
attribC |= (paletteTextureIndex & 0xFFFF) << 16;
var uAttribC = (uint)attribC;
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, uAttribC, tint, alpha);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, uAttribC, tint, alpha);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, uAttribC, tint, alpha);
vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, tint, alpha);
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);
}
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType, bool premultiplied = false)
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
{
var destData = dest.Sheet.GetData();
var stride = dest.Sheet.Size.Width;
var x = dest.Bounds.Left;
var y = dest.Bounds.Top;
var width = dest.Bounds.Width;
var height = dest.Bounds.Height;
if (dest.Channel == TextureChannel.RGBA)
{
CopyIntoRgba(src, srcType, premultiplied, destData, x, y, width, height, stride);
var destStride = dest.Sheet.Size.Width;
unsafe
{
// 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();
}
}
}
}
}
else
{
// Copy into single channel of destination.
var destStride = stride * 4;
var destOffset = destStride * y + x * 4 + ChannelMasks[(int)dest.Channel];
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;
@@ -138,127 +170,64 @@ namespace OpenRA.Graphics
}
}
static void CopyIntoRgba(
byte[] src, SpriteFrameType srcType, bool premultiplied, byte[] dest, int x, int y, int width, int height, int stride)
{
var si = 0;
var di = y * stride + x;
var d = MemoryMarshal.Cast<byte, uint>(dest);
// SpriteFrameType.Brga32 is a common source format, and it matches the destination format.
// Provide a fast past that just performs memory copies.
if (srcType == SpriteFrameType.Bgra32)
{
var s = MemoryMarshal.Cast<byte, uint>(src);
for (var h = 0; h < height; h++)
{
s[si..(si + width)].CopyTo(d[di..(di + width)]);
if (!premultiplied)
{
for (var w = 0; w < width; w++)
{
d[di] = PremultiplyAlpha(Color.FromArgb(d[di])).ToArgb();
di++;
}
di -= width;
}
si += width;
di += stride;
}
return;
}
for (var h = 0; h < height; h++)
{
for (var w = 0; w < width; w++)
{
byte r, g, b, a;
switch (srcType)
{
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
b = src[si++];
g = src[si++];
r = src[si++];
a = srcType == SpriteFrameType.Bgra32 ? src[si++] : byte.MaxValue;
break;
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
r = src[si++];
g = src[si++];
b = src[si++];
a = srcType == SpriteFrameType.Rgba32 ? src[si++] : byte.MaxValue;
break;
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
}
var c = Color.FromArgb(a, r, g, b);
if (!premultiplied)
c = PremultiplyAlpha(c);
d[di++] = c.ToArgb();
}
di += stride - width;
}
}
public static void FastCopyIntoSprite(Sprite dest, Png src)
{
var destData = dest.Sheet.GetData();
var stride = dest.Sheet.Size.Width;
var x = dest.Bounds.Left;
var y = dest.Bounds.Top;
var destStride = dest.Sheet.Size.Width;
var width = dest.Bounds.Width;
var height = dest.Bounds.Height;
var si = 0;
var di = y * stride + x;
var d = MemoryMarshal.Cast<byte, uint>(destData);
for (var h = 0; h < height; h++)
unsafe
{
for (var w = 0; w < width; w++)
// Cast the data to an int array so we can copy the src data directly
fixed (byte* bd = &destData[0])
{
Color c;
switch (src.Type)
var data = (int*)bd;
var x = dest.Bounds.Left;
var y = dest.Bounds.Top;
var k = 0;
for (var j = 0; j < height; j++)
{
case SpriteFrameType.Indexed8:
c = src.Palette[src.Data[si++]];
break;
for (var i = 0; i < width; i++)
{
Color cc;
switch (src.Type)
{
case SpriteFrameType.Indexed8:
{
cc = src.Palette[src.Data[k++]];
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
var r = src.Data[si++];
var g = src.Data[si++];
var b = src.Data[si++];
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[si++] : byte.MaxValue;
c = Color.FromArgb(a, r, g, b);
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}");
// Pngs don't support BGR[A], so no need to include them here
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
}
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
}
}
d[di++] = PremultiplyAlpha(c).ToArgb();
}
di += stride - width;
}
}
/// <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>
/// <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;
@@ -289,15 +258,15 @@ namespace OpenRA.Graphics
/// <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>
/// <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 = RotateQuad(offset, size, rotation);
var rotatedQuad = Util.RotateQuad(offset, size, rotation);
var minX = rotatedQuad[0].X;
var maxX = rotatedQuad[0].X;
var minY = rotatedQuad[0].Y;
@@ -336,5 +305,239 @@ namespace OpenRA.Graphics
(int)((byte)(t * a2 * c2.G + 0.5f) + (1 - t) * (byte)(a1 * c1.G + 0.5f)),
(int)((byte)(t * a2 * c2.B + 0.5f) + (1 - t) * (byte)(a1 * c1.B + 0.5f))));
}
public static float[] IdentityMatrix()
{
return Exts.MakeArray(16, j => (j % 5 == 0) ? 1.0f : 0);
}
public static float[] ScaleMatrix(float sx, float sy, float sz)
{
var mtx = IdentityMatrix();
mtx[0] = sx;
mtx[5] = sy;
mtx[10] = sz;
return mtx;
}
public static float[] TranslationMatrix(float x, float y, float z)
{
var mtx = IdentityMatrix();
mtx[12] = x;
mtx[13] = y;
mtx[14] = z;
return mtx;
}
public static float[] MatrixMultiply(float[] lhs, float[] rhs)
{
var mtx = new float[16];
for (var i = 0; i < 4; i++)
for (var j = 0; j < 4; j++)
{
mtx[4 * i + j] = 0;
for (var k = 0; k < 4; k++)
mtx[4 * i + j] += lhs[4 * k + j] * rhs[4 * i + k];
}
return mtx;
}
public static float[] MatrixVectorMultiply(float[] mtx, float[] vec)
{
var ret = new float[4];
for (var j = 0; j < 4; j++)
{
ret[j] = 0;
for (var k = 0; k < 4; k++)
ret[j] += mtx[4 * k + j] * vec[k];
}
return ret;
}
public static float[] MatrixInverse(float[] m)
{
var mtx = new float[16];
mtx[0] = m[5] * m[10] * m[15] -
m[5] * m[11] * m[14] -
m[9] * m[6] * m[15] +
m[9] * m[7] * m[14] +
m[13] * m[6] * m[11] -
m[13] * m[7] * m[10];
mtx[4] = -m[4] * m[10] * m[15] +
m[4] * m[11] * m[14] +
m[8] * m[6] * m[15] -
m[8] * m[7] * m[14] -
m[12] * m[6] * m[11] +
m[12] * m[7] * m[10];
mtx[8] = m[4] * m[9] * m[15] -
m[4] * m[11] * m[13] -
m[8] * m[5] * m[15] +
m[8] * m[7] * m[13] +
m[12] * m[5] * m[11] -
m[12] * m[7] * m[9];
mtx[12] = -m[4] * m[9] * m[14] +
m[4] * m[10] * m[13] +
m[8] * m[5] * m[14] -
m[8] * m[6] * m[13] -
m[12] * m[5] * m[10] +
m[12] * m[6] * m[9];
mtx[1] = -m[1] * m[10] * m[15] +
m[1] * m[11] * m[14] +
m[9] * m[2] * m[15] -
m[9] * m[3] * m[14] -
m[13] * m[2] * m[11] +
m[13] * m[3] * m[10];
mtx[5] = m[0] * m[10] * m[15] -
m[0] * m[11] * m[14] -
m[8] * m[2] * m[15] +
m[8] * m[3] * m[14] +
m[12] * m[2] * m[11] -
m[12] * m[3] * m[10];
mtx[9] = -m[0] * m[9] * m[15] +
m[0] * m[11] * m[13] +
m[8] * m[1] * m[15] -
m[8] * m[3] * m[13] -
m[12] * m[1] * m[11] +
m[12] * m[3] * m[9];
mtx[13] = m[0] * m[9] * m[14] -
m[0] * m[10] * m[13] -
m[8] * m[1] * m[14] +
m[8] * m[2] * m[13] +
m[12] * m[1] * m[10] -
m[12] * m[2] * m[9];
mtx[2] = m[1] * m[6] * m[15] -
m[1] * m[7] * m[14] -
m[5] * m[2] * m[15] +
m[5] * m[3] * m[14] +
m[13] * m[2] * m[7] -
m[13] * m[3] * m[6];
mtx[6] = -m[0] * m[6] * m[15] +
m[0] * m[7] * m[14] +
m[4] * m[2] * m[15] -
m[4] * m[3] * m[14] -
m[12] * m[2] * m[7] +
m[12] * m[3] * m[6];
mtx[10] = m[0] * m[5] * m[15] -
m[0] * m[7] * m[13] -
m[4] * m[1] * m[15] +
m[4] * m[3] * m[13] +
m[12] * m[1] * m[7] -
m[12] * m[3] * m[5];
mtx[14] = -m[0] * m[5] * m[14] +
m[0] * m[6] * m[13] +
m[4] * m[1] * m[14] -
m[4] * m[2] * m[13] -
m[12] * m[1] * m[6] +
m[12] * m[2] * m[5];
mtx[3] = -m[1] * m[6] * m[11] +
m[1] * m[7] * m[10] +
m[5] * m[2] * m[11] -
m[5] * m[3] * m[10] -
m[9] * m[2] * m[7] +
m[9] * m[3] * m[6];
mtx[7] = m[0] * m[6] * m[11] -
m[0] * m[7] * m[10] -
m[4] * m[2] * m[11] +
m[4] * m[3] * m[10] +
m[8] * m[2] * m[7] -
m[8] * m[3] * m[6];
mtx[11] = -m[0] * m[5] * m[11] +
m[0] * m[7] * m[9] +
m[4] * m[1] * m[11] -
m[4] * m[3] * m[9] -
m[8] * m[1] * m[7] +
m[8] * m[3] * m[5];
mtx[15] = m[0] * m[5] * m[10] -
m[0] * m[6] * m[9] -
m[4] * m[1] * m[10] +
m[4] * m[2] * m[9] +
m[8] * m[1] * m[6] -
m[8] * m[2] * m[5];
var det = m[0] * mtx[0] + m[1] * mtx[4] + m[2] * mtx[8] + m[3] * mtx[12];
if (det == 0)
return null;
for (var i = 0; i < 16; i++)
mtx[i] *= 1 / det;
return mtx;
}
public static float[] MakeFloatMatrix(Int32Matrix4x4 imtx)
{
var multipler = 1f / imtx.M44;
return new[]
{
imtx.M11 * multipler,
imtx.M12 * multipler,
imtx.M13 * multipler,
imtx.M14 * multipler,
imtx.M21 * multipler,
imtx.M22 * multipler,
imtx.M23 * multipler,
imtx.M24 * multipler,
imtx.M31 * multipler,
imtx.M32 * multipler,
imtx.M33 * multipler,
imtx.M34 * multipler,
imtx.M41 * multipler,
imtx.M42 * multipler,
imtx.M43 * multipler,
imtx.M44 * multipler,
};
}
public static float[] MatrixAABBMultiply(float[] mtx, float[] bounds)
{
// Corner offsets
var ix = new uint[] { 0, 0, 0, 0, 3, 3, 3, 3 };
var iy = new uint[] { 1, 1, 4, 4, 1, 1, 4, 4 };
var iz = new uint[] { 2, 5, 2, 5, 2, 5, 2, 5 };
// Vectors to opposing corner
var ret = new[]
{
float.MaxValue, float.MaxValue, float.MaxValue,
float.MinValue, float.MinValue, float.MinValue
};
// Transform vectors and find new bounding box
for (var i = 0; i < 8; i++)
{
var vec = new[] { bounds[ix[i]], bounds[iy[i]], bounds[iz[i]], 1 };
var tvec = MatrixVectorMultiply(mtx, vec);
ret[0] = Math.Min(ret[0], tvec[0] / tvec[3]);
ret[1] = Math.Min(ret[1], tvec[1] / tvec[3]);
ret[2] = Math.Min(ret[2], tvec[2] / tvec[3]);
ret[3] = Math.Max(ret[3], tvec[0] / tvec[3]);
ret[4] = Math.Max(ret[4], tvec[1] / tvec[3]);
ret[5] = Math.Max(ret[5], tvec[2] / tvec[3]);
}
return ret;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -23,42 +23,27 @@ namespace OpenRA.Graphics
public readonly float S, T, U, V;
// Palette and channel flags
public readonly uint C;
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, uint c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, float3.Ones, 1f) { }
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, uint c, in float3 tint, float a)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, tint.X, tint.Y, tint.Z, a) { }
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, uint c, in float3 tint, float a)
: this(x, y, z, s, t, u, v, 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, uint c, float r, float g, float b, float a)
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b, float a)
{
X = x; Y = y; Z = z;
S = s; T = t;
U = u; V = v;
C = c;
P = p; C = c;
R = r; G = g; B = b; A = a;
}
}
public sealed class CombinedShaderBindings : ShaderBindings
{
public CombinedShaderBindings()
: base("combined")
{ }
public override ShaderVertexAttribute[] Attributes { get; } = new[]
{
new ShaderVertexAttribute("aVertexPosition", ShaderVertexAttributeType.Float, 3, 0),
new ShaderVertexAttribute("aVertexTexCoord", ShaderVertexAttributeType.Float, 4, 12),
new ShaderVertexAttribute("aVertexAttributes", ShaderVertexAttributeType.UInt, 1, 28),
new ShaderVertexAttribute("aVertexTint", ShaderVertexAttributeType.Float, 4, 32)
};
}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -53,7 +53,7 @@ namespace OpenRA.Graphics
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation);
public Rectangle Rectangle => new(TopLeft, new Size(viewportSize.X, viewportSize.Y));
public Rectangle Rectangle => new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y));
public int2 TopLeft => CenterLocation - viewportSize / 2;
public int2 BottomRight => CenterLocation + viewportSize / 2;
int2 viewportSize;
@@ -66,6 +66,9 @@ namespace OpenRA.Graphics
WorldViewport lastViewportDistance;
float zoom = 1f;
float minZoom = 1f;
float maxZoom = 2f;
bool unlockMinZoom;
float unlockedMinZoomScale;
float unlockedMinZoom = 1f;
@@ -83,13 +86,12 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get; private set; } = 1f;
public float MaxZoom { get; private set; } = 2f;
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);
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
}
public void AdjustZoom(float dz, int2 center)
@@ -103,10 +105,10 @@ namespace OpenRA.Graphics
public void ToggleZoom()
{
// Unlocked zooms always reset to the default zoom
if (zoom < MinZoom)
Zoom = MinZoom;
if (zoom < minZoom)
Zoom = minZoom;
else
Zoom = zoom > MinZoom ? MinZoom : MaxZoom;
Zoom = zoom > minZoom ? minZoom : maxZoom;
}
public void UnlockMinimumZoom(float scale)
@@ -119,6 +121,23 @@ namespace OpenRA.Graphics
public static long LastMoveRunTime = 0;
public static int2 LastMousePos;
float ClosestTo(float[] collection, float target)
{
var closestValue = collection.First();
var subtractResult = Math.Abs(closestValue - target);
foreach (var element in collection)
{
if (Math.Abs(element - target) < subtractResult)
{
subtractResult = Math.Abs(element - target);
closestValue = element;
}
}
return closestValue;
}
public ScrollDirection GetBlockedDirections()
{
var ret = ScrollDirection.None;
@@ -172,7 +191,7 @@ namespace OpenRA.Graphics
UpdateViewportZooms();
}
static float CalculateMinimumZoom(float minHeight, float maxHeight)
float CalculateMinimumZoom(float minHeight, float maxHeight)
{
var h = Game.Renderer.NativeResolution.Height;
@@ -208,14 +227,14 @@ namespace OpenRA.Graphics
var vd = graphicSettings.ViewportDistance;
if (viewportSizes.AllowNativeZoom && vd == WorldViewport.Native)
MinZoom = viewportSizes.DefaultScale;
minZoom = 1;
else
{
var range = viewportSizes.GetSizeRange(vd);
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
minZoom = CalculateMinimumZoom(range.X, range.Y);
}
MaxZoom = Math.Min(MinZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
maxZoom = Math.Min(minZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * 1f / viewportSizes.MaxZoomWindowHeight);
if (unlockMinZoom)
{
@@ -223,19 +242,19 @@ namespace OpenRA.Graphics
// 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;
unlockedMinZoom = minZoom * unlockedMinZoomScale;
}
if (resetCurrentZoom)
Zoom = MinZoom;
Zoom = minZoom;
else
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
Zoom = Zoom.Clamp(minZoom, maxZoom);
var maxSize = 1f / (unlockMinZoom ? unlockedMinZoom : MinZoom) * new float2(Game.Renderer.NativeResolution);
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);
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}
public CPos ViewToWorld(int2 view)
@@ -278,27 +297,15 @@ namespace OpenRA.Graphics
return worldRenderer.World.Map.CellContaining(worldRenderer.ProjectedPosition(ViewToWorldPx(view)));
}
/// <summary>Returns an unfiltered list of all cells that could potentially contain the mouse cursor.</summary>
/// <summary> Returns an unfiltered list of all cells that could potentially contain the mouse cursor</summary>
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
{
var map = worldRenderer.World.Map;
var tileScale = map.Grid.TileScale / 2;
var minPos = worldRenderer.ProjectedPosition(world);
// Find all the cells that could potentially have been clicked.
MPos a;
MPos b;
if (map.Grid.Type == MapGridType.RectangularIsometric)
{
// TODO: this generates too many cells.
a = map.CellContaining(minPos - new WVec(tileScale, 0, 0)).ToMPos(map.Grid.Type);
b = map.CellContaining(minPos + new WVec(tileScale, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
}
else
{
a = map.CellContaining(minPos).ToMPos(map.Grid.Type);
b = map.CellContaining(minPos + new WVec(0, tileScale * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
}
// Find all the cells that could potentially have been clicked
var a = map.CellContaining(minPos - new WVec(1024, 0, 0)).ToMPos(map.Grid.Type);
var b = map.CellContaining(minPos + new WVec(512, 512 * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
for (var v = b.V; v >= a.V; v--)
for (var u = b.U; u >= a.U; u--)
@@ -306,18 +313,15 @@ namespace OpenRA.Graphics
}
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 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 void Center(IEnumerable<Actor> actors)
{
var actorsCollection = actors as IReadOnlyCollection<Actor>;
actorsCollection ??= actors.ToList();
if (actorsCollection.Count == 0)
if (!actors.Any())
return;
Center(actorsCollection.Select(a => a.CenterPosition).Average());
Center(actors.Select(a => a.CenterPosition).Average());
}
public void Center(WPos pos)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -31,27 +31,25 @@ namespace OpenRA.Graphics
public event Action PaletteInvalidated = null;
readonly HashSet<Actor> onScreenActors = new();
readonly HardwarePalette palette = new();
readonly Dictionary<string, PaletteReference> palettes = new();
readonly HashSet<Actor> onScreenActors = new HashSet<Actor>();
readonly HardwarePalette palette = new HardwarePalette();
readonly Dictionary<string, PaletteReference> palettes = new Dictionary<string, PaletteReference>();
readonly IRenderTerrain terrainRenderer;
readonly Lazy<DebugVisualizations> debugVis;
readonly Func<string, PaletteReference> createPaletteReference;
readonly bool enableDepthBuffer;
readonly List<IFinalizedRenderable> preparedRenderables = new();
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new();
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
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();
readonly IRenderer[] renderers;
readonly IRenderPostProcessPass[] postProcessPasses;
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
internal WorldRenderer(ModData modData, World world)
{
World = world;
TileSize = World.Map.Grid.TileSize;
TileScale = World.Map.Grid.TileScale;
TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024;
Viewport = new Viewport(this, world.Map);
createPaletteReference = CreatePaletteReference;
@@ -68,24 +66,9 @@ namespace OpenRA.Graphics
palette.Initialize();
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
postProcessPasses = world.WorldActor.TraitsImplementing<IRenderPostProcessPass>().ToArray();
}
public void BeginFrame()
{
foreach (var r in renderers)
r.BeginFrame();
}
public void EndFrame()
{
foreach (var r in renderers)
r.EndFrame();
}
public void UpdatePalettesForPlayer(string internalName, Color color, bool replaceExisting)
@@ -104,7 +87,7 @@ namespace OpenRA.Graphics
{
// 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 string.IsNullOrEmpty(name) ? null : palettes.GetOrAdd(name, createPaletteReference);
return name == null ? null : palettes.GetOrAdd(name, createPaletteReference);
}
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
@@ -126,13 +109,13 @@ namespace OpenRA.Graphics
palette.ReplacePalette(name, pal);
// Update cached PlayerReference if one exists
if (palettes.TryGetValue(name, out var paletteReference))
paletteReference.Palette = pal;
if (palettes.ContainsKey(name))
palettes[name].Palette = pal;
}
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float valueModifier, float minHue, float maxHue)
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
palette.SetColorShift(name, hueOffset, satOffset, valueModifier, minHue, maxHue);
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
}
// PERF: Avoid LINQ.
@@ -194,7 +177,7 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects)
{
if (e is not IEffectAboveShroud ea)
if (!(e is IEffectAboveShroud ea))
continue;
foreach (var renderable in ea.RenderAboveShroud(this))
@@ -235,7 +218,7 @@ namespace OpenRA.Graphics
foreach (var e in World.Effects)
{
if (e is not IEffectAnnotation ea)
if (!(e is IEffectAnnotation ea))
continue;
foreach (var renderAnnotation in ea.RenderAnnotation(this))
@@ -287,8 +270,6 @@ namespace OpenRA.Graphics
if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer();
ApplyPostProcessing(PostProcessPassType.AfterActors);
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
{
if (actor.IsInWorld && !actor.Disposed)
@@ -298,8 +279,6 @@ namespace OpenRA.Graphics
if (enableDepthBuffer)
Game.Renderer.ClearDepthBuffer();
ApplyPostProcessing(PostProcessPassType.AfterWorld);
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
if (enableDepthBuffer)
@@ -313,23 +292,9 @@ namespace OpenRA.Graphics
foreach (var r in g)
r.Render(this);
ApplyPostProcessing(PostProcessPassType.AfterShroud);
Game.Renderer.Flush();
}
void ApplyPostProcessing(PostProcessPassType type)
{
foreach (var pass in postProcessPasses)
{
if (pass.Type != type || !pass.Enabled)
continue;
Game.Renderer.Flush();
pass.Draw(this);
}
}
public void DrawAnnotations()
{
Game.Renderer.EnableAntialiasingFilter();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -10,6 +10,7 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -18,8 +19,8 @@ namespace OpenRA
public readonly string Name;
public readonly Hotkey Default = Hotkey.Invalid;
public readonly string Description = "";
public readonly HashSet<string> Types = new();
public readonly HashSet<string> Contexts = new();
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; }
@@ -30,26 +31,29 @@ namespace OpenRA
if (!string.IsNullOrEmpty(node.Value))
Default = FieldLoader.GetValue<Hotkey>("value", node.Value);
var nodeDict = node.ToDictionary();
var descriptionNode = node.Nodes.FirstOrDefault(n => n.Key == "Description");
if (descriptionNode != null)
Description = descriptionNode.Value.Value;
if (nodeDict.TryGetValue("Description", out var descriptionYaml))
Description = descriptionYaml.Value;
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
if (typesNode != null)
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
if (nodeDict.TryGetValue("Types", out var typesYaml))
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
if (contextsNode != null)
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value);
if (nodeDict.TryGetValue("Platform", out var platformYaml))
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
if (platformNode != null)
{
var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString());
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
if (platformOverride != null)
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
}
if (nodeDict.TryGetValue("Readonly", out var readonlyYaml))
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyYaml.Value);
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
if (readonlyNode != null)
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -18,8 +18,8 @@ namespace OpenRA
public sealed class HotkeyManager
{
readonly Dictionary<string, Hotkey> settings;
readonly Dictionary<string, HotkeyDefinition> definitions = new();
readonly Dictionary<string, Hotkey> keys = new();
readonly Dictionary<string, HotkeyDefinition> definitions = new Dictionary<string, HotkeyDefinition>();
readonly Dictionary<string, Hotkey> keys = new Dictionary<string, Hotkey>();
public HotkeyManager(IReadOnlyFileSystem fileSystem, Dictionary<string, Hotkey> settings, Manifest manifest)
{
@@ -35,7 +35,7 @@ namespace OpenRA
foreach (var kv in settings)
{
if (definitions.TryGetValue(kv.Key, out var definition) && !definition.Readonly)
if (definitions.ContainsKey(kv.Key) && !definitions[kv.Key].Readonly)
keys[kv.Key] = kv.Value;
}
@@ -43,9 +43,6 @@ namespace OpenRA
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value, this[hd.Value.Name].GetValue()) != null;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance", "CA1854:Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method",
Justification = "Func must perform a live lookup in the collection, as the lookup value can change.")]
internal Func<Hotkey> GetHotkeyReference(string name)
{
// Is this a mod-defined hotkey?
@@ -105,7 +102,7 @@ namespace OpenRA
return null;
}
public HotkeyReference this[string name] => new(GetHotkeyReference(name));
public HotkeyReference this[string name] => new HotkeyReference(GetHotkeyReference(name));
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
}

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -9,41 +9,12 @@
*/
#endregion
using System;
using System.Reflection;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
public class Utility
{
static readonly ConcurrentCache<Type, FieldInfo[]> TypeFields =
new(type => type.GetFields());
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType), bool> MemberHasAttribute =
new(x => Attribute.IsDefined(x.Member, x.AttributeType));
static readonly ConcurrentCache<(MemberInfo Member, Type AttributeType, bool Inherit), object[]> MemberCustomAttributes =
new(x => x.Member.GetCustomAttributes(x.AttributeType, x.Inherit));
public static FieldInfo[] GetFields(Type type)
{
return TypeFields[type];
}
public static bool HasAttribute<TAttribute>(MemberInfo member)
where TAttribute : Attribute
{
return MemberHasAttribute[(member, typeof(TAttribute))];
}
public static TAttribute[] GetCustomAttributes<TAttribute>(MemberInfo member, bool inherit)
where TAttribute : Attribute
{
return (TAttribute[])MemberCustomAttributes[(member, typeof(TAttribute), inherit)];
}
public readonly ModData ModData;
public readonly InstalledMods Mods;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -15,7 +15,7 @@ namespace OpenRA
{
public readonly struct Hotkey : IEquatable<Hotkey>
{
public static Hotkey Invalid = new(Keycode.UNKNOWN, Modifiers.None);
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
public bool IsValid()
{
return Key != Keycode.UNKNOWN;
@@ -42,7 +42,7 @@ namespace OpenRA
var mods = Modifiers.None;
if (parts.Length >= 2)
{
var modString = s[s.IndexOf(' ')..];
var modString = s.Substring(s.IndexOf(' '));
if (!Enum<Modifiers>.TryParse(modString, true, out mods))
return false;
}
@@ -84,7 +84,7 @@ namespace OpenRA
return obj is Hotkey o && (Hotkey?)o == this;
}
public override string ToString() { return $"{Key} {Modifiers:F}"; }
public override string ToString() { return $"{Key} {Modifiers.ToString("F")}"; }
public string DisplayString()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -14,7 +14,7 @@ using System;
namespace OpenRA
{
/// <summary>
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey.
/// A reference to either a named hotkey (defined in the game settings) or a statically assigned hotkey
/// </summary>
public class HotkeyReference
{

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -13,8 +13,7 @@ using System.Collections.Generic;
namespace OpenRA
{
// List of keycodes. Duplicated from SDL 2.0.1, with the addition
// of MOUSE4 and MOUSE5.
// List of keycodes, duplicated from SDL 2.0.1
public enum Keycode
{
UNKNOWN = 0,
@@ -253,13 +252,11 @@ namespace OpenRA
KBDILLUMUP = 280 | (1 << 30),
EJECT = 281 | (1 << 30),
SLEEP = 282 | (1 << 30),
MOUSE4 = 283 | (1 << 30),
MOUSE5 = 284 | (1 << 30)
}
public static class KeycodeExts
{
static readonly Dictionary<Keycode, string> KeyNames = new()
static readonly Dictionary<Keycode, string> KeyNames = new Dictionary<Keycode, string>
{
{ Keycode.UNKNOWN, "Undefined" },
{ Keycode.RETURN, "Return" },
@@ -497,8 +494,6 @@ namespace OpenRA
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
{ Keycode.EJECT, "Eject" },
{ Keycode.SLEEP, "Sleep" },
{ Keycode.MOUSE4, "Mouse 4" },
{ Keycode.MOUSE5, "Mouse 5" },
};
public static string DisplayString(Keycode k)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -54,7 +54,7 @@ namespace OpenRA
return mods;
}
static Manifest LoadMod(string id, string path)
Manifest LoadMod(string id, string path)
{
IReadOnlyPackage package = null;
try
@@ -79,7 +79,7 @@ namespace OpenRA
return null;
}
static Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
Dictionary<string, Manifest> GetInstalledMods(IEnumerable<string> searchPaths, IEnumerable<string> explicitPaths)
{
var ret = new Dictionary<string, Manifest>();
var candidates = GetCandidateMods(searchPaths)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -66,10 +66,8 @@ namespace OpenRA
}
catch (Exception e)
{
Console.WriteLine("Failed to load keys:");
Console.WriteLine(e);
Log.Write("debug", $"Failed to load player keypair from `{filePath}` with exception:");
Log.Write("debug", e);
Console.WriteLine("Failed to load keys: {0}", e);
Log.Write("debug", "Failed to load player keypair from `{0}` with exception: {1}", filePath, e);
}
}
@@ -84,17 +82,16 @@ namespace OpenRA
{
var client = HttpClientFactory.Create();
var url = playerDatabase.Profile + Fingerprint;
var httpResponseMessage = await client.GetAsync(url);
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
var yaml = MiniYaml.FromStream(result, url).First();
var yaml = MiniYaml.FromStream(result).First();
if (yaml.Key == "Player")
{
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
if (innerData.KeyRevoked)
{
Log.Write("debug", $"Revoking key with fingerprint {Fingerprint}");
Log.Write("debug", "Revoking key with fingerprint {0}", Fingerprint);
DeleteKeypair();
}
else
@@ -105,8 +102,7 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to parse player data result with exception:");
Log.Write("debug", e);
Log.Write("debug", "Failed to parse player data result with exception: {0}", e);
innerState = LinkState.ConnectionFailed;
}
finally
@@ -140,10 +136,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to generate keypair with exception:");
Log.Write("debug", e);
Console.WriteLine("Key generation failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to generate keypair with exception: {1}", e);
Console.WriteLine("Key generation failed: {0}", e);
innerState = LinkState.Uninitialized;
}
@@ -158,10 +152,8 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Failed to delete keypair with exception:");
Log.Write("debug", e);
Console.WriteLine("Key deletion failed:");
Console.WriteLine(e);
Log.Write("debug", "Failed to delete keypair with exception: {1}", e);
Console.WriteLine("Key deletion failed: {0}", e);
}
innerState = LinkState.Uninitialized;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -9,13 +9,18 @@
*/
#endregion
using System;
using System.Collections.Generic;
namespace OpenRA.Mods.Common.Installer
namespace OpenRA
{
public interface ISourceAction
public interface ILog
{
void RunActionOnSource(MiniYaml actionYaml, string path, ModData modData, List<string> extracted, Action<string> updateMessage);
void Write(string channel, string format, params object[] args);
}
public class LogProxy : ILog
{
public void Write(string channel, string format, params object[] args)
{
Log.Write(channel, format, args);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -19,7 +19,7 @@ namespace OpenRA
public readonly int U, V;
public MPos(int u, int v) { U = u; V = v; }
public static readonly MPos Zero = new(0, 0);
public static readonly MPos Zero = new MPos(0, 0);
public static bool operator ==(MPos me, MPos other) { return me.U == other.U && me.V == other.V; }
public static bool operator !=(MPos me, MPos other) { return !(me == other); }
@@ -27,7 +27,7 @@ namespace OpenRA
public override int GetHashCode() { return U.GetHashCode() ^ V.GetHashCode(); }
public bool Equals(MPos other) { return other == this; }
public override bool Equals(object obj) { return obj is MPos uv && Equals(uv); }
public override bool Equals(object obj) { return obj is MPos && Equals((MPos)obj); }
public MPos Clamp(Rectangle r)
{
@@ -64,14 +64,14 @@ namespace OpenRA
}
/// <summary>
/// Projected map position.
/// Projected map position
/// </summary>
public readonly struct PPos : IEquatable<PPos>
{
public readonly int U, V;
public PPos(int u, int v) { U = u; V = v; }
public static readonly PPos Zero = new(0, 0);
public static readonly PPos Zero = new PPos(0, 0);
public static bool operator ==(PPos me, PPos other) { return me.U == other.U && me.V == other.V; }
public static bool operator !=(PPos me, PPos other) { return !(me == other); }
@@ -88,7 +88,7 @@ namespace OpenRA
public override int GetHashCode() { return U.GetHashCode() ^ V.GetHashCode(); }
public bool Equals(PPos other) { return other == this; }
public override bool Equals(object obj) { return obj is PPos puv && Equals(puv); }
public override bool Equals(object obj) { return obj is PPos && Equals((PPos)obj); }
public override string ToString() { return U + "," + V; }
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -43,6 +43,17 @@ namespace OpenRA
}
}
public sealed class ModelSequenceFormat : IGlobalModData
{
public readonly string Type;
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
public ModelSequenceFormat(MiniYaml yaml)
{
Type = yaml.Value;
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
}
}
public class ModMetadata
{
public string Title;
@@ -53,8 +64,8 @@ namespace OpenRA
public bool Hidden;
}
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
public sealed class Manifest : IDisposable
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
public class Manifest : IDisposable
{
public readonly string Id;
public readonly IReadOnlyPackage Package;
@@ -84,7 +95,7 @@ namespace OpenRA
"RequiresMods", "PackageFormats"
};
readonly TypeDictionary modules = new();
readonly TypeDictionary modules = new TypeDictionary();
readonly Dictionary<string, MiniYaml> yaml;
bool customDataLoaded;
@@ -94,8 +105,7 @@ namespace OpenRA
Id = modId;
Package = package;
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool);
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
for (var i = nodes.Count - 1; i >= 0; i--)
{
if (nodes[i].Key != "Include")
@@ -108,7 +118,7 @@ namespace OpenRA
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
nodes.RemoveAt(i);
nodes.InsertRange(i, MiniYaml.FromStream(contents, $"{package.Name}:{filename}", stringPool: stringPool));
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
}
// Merge inherited overrides
@@ -147,25 +157,25 @@ namespace OpenRA
// Allow inherited mods to import parent maps.
var compat = new List<string> { Id };
if (yaml.TryGetValue("SupportsMapsFrom", out var entry))
compat.AddRange(entry.Value.Split(',').Select(c => c.Trim()));
if (yaml.ContainsKey("SupportsMapsFrom"))
compat.AddRange(yaml["SupportsMapsFrom"].Value.Split(',').Select(c => c.Trim()));
MapCompatibility = compat.ToArray();
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
DefaultOrderGenerator = entry.Value;
if (yaml.ContainsKey("DefaultOrderGenerator"))
DefaultOrderGenerator = yaml["DefaultOrderGenerator"].Value;
if (yaml.TryGetValue("PackageFormats", out entry))
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
if (yaml.ContainsKey("PackageFormats"))
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", yaml["PackageFormats"].Value);
if (yaml.TryGetValue("SoundFormats", out entry))
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", entry.Value);
if (yaml.ContainsKey("SoundFormats"))
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);
if (yaml.TryGetValue("SpriteFormats", out entry))
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", entry.Value);
if (yaml.ContainsKey("SpriteFormats"))
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
if (yaml.TryGetValue("VideoFormats", out entry))
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
if (yaml.ContainsKey("VideoFormats"))
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", yaml["VideoFormats"].Value);
}
public void LoadCustomData(ObjectCreator oc)
@@ -201,18 +211,18 @@ namespace OpenRA
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
{
if (!yaml.TryGetValue(key, out var value))
if (!yaml.ContainsKey(key))
return Array.Empty<string>();
return value.Nodes.Select(n => n.Key).ToArray();
return yaml[key].ToDictionary().Keys.ToArray();
}
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
{
if (!yaml.TryGetValue(key, out var value))
if (!yaml.ContainsKey(key))
return new Dictionary<string, string>();
return value.ToDictionary(my => my.Value);
return yaml[key].ToDictionary(my => my.Value);
}
public bool Contains<T>() where T : IGlobalModData

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -54,7 +54,7 @@ namespace OpenRA
// Traits tagged with an instance name prefer inits with the same name.
// If a more specific init is not available, fall back to an unnamed init.
// If duplicate inits are defined, take the last to match standard yaml override expectations
if (!string.IsNullOrEmpty(info?.InstanceName))
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
@@ -159,9 +159,9 @@ namespace OpenRA
public virtual void Initialize(T value)
{
typeof(ValueActorInit<T>)
.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(this, value);
var field = typeof(ValueActorInit<T>).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, value);
}
public override MiniYaml Save()
@@ -175,7 +175,8 @@ namespace OpenRA
protected CompositeActorInit(TraitInfo info)
: base(info.InstanceName) { }
protected CompositeActorInit() { }
protected CompositeActorInit()
: base() { }
public virtual void Initialize(MiniYaml yaml)
{
@@ -245,16 +246,16 @@ namespace OpenRA
public void Initialize(MiniYaml yaml)
{
typeof(OwnerInit)
.GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance)
?.SetValue(this, yaml.Value);
var field = typeof(OwnerInit).GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance);
if (field != null)
field.SetValue(this, yaml.Value);
}
public void Initialize(Player player)
{
typeof(OwnerInit)
.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)
?.SetValue(this, player);
var field = typeof(OwnerInit).GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, player);
}
public override MiniYaml Save()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -22,7 +22,7 @@ namespace OpenRA
{
public interface ISuppressInitExport { }
public class ActorReference : IEnumerable<object>
public class ActorReference : IEnumerable
{
public string Type;
readonly Lazy<TypeDictionary> initDict;
@@ -84,29 +84,27 @@ namespace OpenRA
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
{
var nodes = new List<MiniYamlNode>();
var ret = new MiniYaml(Type);
foreach (var o in initDict.Value)
{
if (o is not ActorInit init || o is ISuppressInitExport)
if (!(o is ActorInit init) || o is ISuppressInitExport)
continue;
if (initFilter != null && !initFilter(init))
continue;
var initTypeName = init.GetType().Name;
var initName = initTypeName[..^4];
var initName = initTypeName.Substring(0, initTypeName.Length - 4);
if (!string.IsNullOrEmpty(init.InstanceName))
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
nodes.Add(new MiniYamlNode(initName, init.Save()));
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
}
return new MiniYaml(Type, nodes);
return ret;
}
public IEnumerator<object> GetEnumerator() { return initDict.Value.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); }
public ActorReference Clone()
{
@@ -139,7 +137,7 @@ namespace OpenRA
return removed;
}
public IReadOnlyCollection<T> GetAll<T>() where T : ActorInit
public IEnumerable<T> GetAll<T>() where T : ActorInit
{
return initDict.Value.WithInterface<T>();
}
@@ -152,9 +150,8 @@ namespace OpenRA
// If a more specific init is not available, fall back to an unnamed init.
// If duplicate inits are defined, take the last to match standard yaml override expectations
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
return
inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
// Untagged traits will only use untagged inits
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));

View File

@@ -1,90 +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.Collections;
using System.Collections.Generic;
namespace OpenRA
{
public readonly struct CellCoordsRegion : IEnumerable<CPos>
{
public struct CellCoordsEnumerator : IEnumerator<CPos>
{
readonly CellCoordsRegion r;
public CellCoordsEnumerator(CellCoordsRegion region)
: this()
{
r = region;
Reset();
}
public bool MoveNext()
{
var x = Current.X + 1;
var y = Current.Y;
// Check for column overflow.
if (x > r.BottomRight.X)
{
y++;
x = r.TopLeft.X;
// Check for row overflow.
if (y > r.BottomRight.Y)
return false;
}
Current = new CPos(x, y);
return true;
}
public void Reset()
{
Current = new CPos(r.TopLeft.X - 1, r.TopLeft.Y);
}
public CPos Current { get; private set; }
readonly object IEnumerator.Current => Current;
public readonly void Dispose() { }
}
public CellCoordsRegion(CPos topLeft, CPos bottomRight)
{
TopLeft = topLeft;
BottomRight = bottomRight;
}
public override string ToString()
{
return $"{TopLeft}->{BottomRight}";
}
public CellCoordsEnumerator GetEnumerator()
{
return new CellCoordsEnumerator(this);
}
IEnumerator<CPos> IEnumerable<CPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public CPos TopLeft { get; }
public CPos BottomRight { get; }
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -72,7 +72,7 @@ namespace OpenRA
return uv.V * Size.Width + uv.U;
}
/// <summary>Gets or sets the <see cref="CellLayer"/> using cell coordinates.</summary>
/// <summary>Gets or sets the <see cref="CellLayer"/> using cell coordinates</summary>
public T this[CPos cell]
{
get => Entries[Index(cell)];
@@ -85,7 +85,7 @@ namespace OpenRA
}
}
/// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!).</summary>
/// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!)</summary>
public T this[MPos uv]
{
get => Entries[Index(uv)];

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -24,10 +24,10 @@ namespace OpenRA
protected readonly T[] Entries;
protected readonly Rectangle Bounds;
protected CellLayerBase(Map map)
public CellLayerBase(Map map)
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
protected CellLayerBase(MapGridType gridType, Size size)
public CellLayerBase(MapGridType gridType, Size size)
{
Size = size;
Bounds = new Rectangle(0, 0, Size.Width, Size.Height);
@@ -43,13 +43,13 @@ namespace OpenRA
Array.Copy(anotherLayer.Entries, Entries, Entries.Length);
}
/// <summary>Clears the layer contents with their default value.</summary>
/// <summary>Clears the layer contents with their default value</summary>
public virtual void Clear()
{
Array.Clear(Entries, 0, Entries.Length);
}
/// <summary>Clears the layer contents with a known value.</summary>
/// <summary>Clears the layer contents with a known value</summary>
public virtual void Clear(T clearValue)
{
Array.Fill(Entries, clearValue);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -12,6 +12,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -63,9 +64,9 @@ namespace OpenRA
}
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
public static CellRegion BoundingRegion(MapGridType shape, IReadOnlyCollection<CPos> cells)
public static CellRegion BoundingRegion(MapGridType shape, IEnumerable<CPos> cells)
{
if (cells == null || cells.Count == 0)
if (cells == null || !cells.Any())
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
var minU = int.MaxValue;
@@ -101,8 +102,7 @@ namespace OpenRA
return uv.U >= mapTopLeft.U && uv.U <= mapBottomRight.U && uv.V >= mapTopLeft.V && uv.V <= mapBottomRight.V;
}
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
public CellCoordsRegion CellCoords => new(TopLeft, BottomRight);
public MapCoordsRegion MapCoords => new MapCoordsRegion(mapTopLeft, mapBottomRight);
public CellRegionEnumerator GetEnumerator()
{
@@ -126,22 +126,25 @@ namespace OpenRA
// Current position, in map coordinates
int u, v;
// Current position, in cell coordinates
CPos current;
public CellRegionEnumerator(CellRegion region)
: this()
{
r = region;
Reset();
Current = new MPos(u, v).ToCPos(r.gridType);
current = new MPos(u, v).ToCPos(r.gridType);
}
public bool MoveNext()
{
u++;
u += 1;
// Check for column overflow
if (u > r.mapBottomRight.U)
{
v++;
v += 1;
u = r.mapTopLeft.U;
// Check for row overflow
@@ -149,8 +152,7 @@ namespace OpenRA
return false;
}
// Current position, in cell coordinates
Current = new MPos(u, v).ToCPos(r.gridType);
current = new MPos(u, v).ToCPos(r.gridType);
return true;
}
@@ -161,9 +163,9 @@ namespace OpenRA
v = r.mapTopLeft.V;
}
public CPos Current { get; private set; }
readonly object IEnumerator.Current => Current;
public readonly void Dispose() { }
public CPos Current => current;
object IEnumerator.Current => Current;
public void Dispose() { }
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -66,7 +65,7 @@ namespace OpenRA
MissionSelector = 4
}
sealed class MapField
class MapField
{
enum Type { Normal, NodeList, MiniYaml }
readonly FieldInfo field;
@@ -91,13 +90,13 @@ namespace OpenRA
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
var t = field != null ? field.FieldType : property.PropertyType;
type = t == typeof(IReadOnlyCollection<MiniYamlNode>) ? Type.NodeList :
type = t == typeof(List<MiniYamlNode>) ? Type.NodeList :
t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal;
}
public void Deserialize(Map map, MiniYaml yaml)
public void Deserialize(Map map, List<MiniYamlNode> nodes)
{
var node = yaml.NodeWithKeyOrDefault(key);
var node = nodes.FirstOrDefault(n => n.Key == key);
if (node == null)
{
if (required)
@@ -131,14 +130,14 @@ namespace OpenRA
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
if (type == Type.NodeList)
{
var listValue = (IReadOnlyCollection<MiniYamlNode>)value;
var listValue = (List<MiniYamlNode>)value;
if (required || listValue.Count > 0)
nodes.Add(new MiniYamlNode(key, null, listValue));
}
else if (type == Type.MiniYaml)
{
var yamlValue = (MiniYaml)value;
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Length > 0)))
if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Count > 0)))
nodes.Add(new MiniYamlNode(key, yamlValue));
}
else
@@ -150,35 +149,35 @@ namespace OpenRA
}
}
public sealed class Map : IReadOnlyFileSystem, IDisposable
public class Map : IReadOnlyFileSystem
{
public const int SupportedMapFormat = 11;
public const int CurrentMapFormat = 12;
const short InvalidCachedTerrainIndex = -1;
/// <summary>Defines the order of the fields in map.yaml.</summary>
/// <summary>Defines the order of the fields in map.yaml</summary>
static readonly MapField[] YamlFields =
{
new("MapFormat"),
new("RequiresMod"),
new("Title"),
new("Author"),
new("Tileset"),
new("MapSize"),
new("Bounds"),
new("Visibility"),
new("Categories"),
new("LockPreview", required: false, ignoreIfValue: "False"),
new("Players", "PlayerDefinitions"),
new("Actors", "ActorDefinitions"),
new("Rules", "RuleDefinitions", required: false),
new("Translations", "TranslationDefinitions", required: false),
new("Sequences", "SequenceDefinitions", required: false),
new("ModelSequences", "ModelSequenceDefinitions", required: false),
new("Weapons", "WeaponDefinitions", required: false),
new("Voices", "VoiceDefinitions", required: false),
new("Music", "MusicDefinitions", required: false),
new("Notifications", "NotificationDefinitions", required: false),
new MapField("MapFormat"),
new MapField("RequiresMod"),
new MapField("Title"),
new MapField("Author"),
new MapField("Tileset"),
new MapField("MapSize"),
new MapField("Bounds"),
new MapField("Visibility"),
new MapField("Categories"),
new MapField("Translations", required: false, ignoreIfValue: ""),
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
new MapField("Players", "PlayerDefinitions"),
new MapField("Actors", "ActorDefinitions"),
new MapField("Rules", "RuleDefinitions", required: false),
new MapField("Sequences", "SequenceDefinitions", required: false),
new MapField("ModelSequences", "ModelSequenceDefinitions", required: false),
new MapField("Weapons", "WeaponDefinitions", required: false),
new MapField("Voices", "VoiceDefinitions", required: false),
new MapField("Music", "MusicDefinitions", required: false),
new MapField("Notifications", "NotificationDefinitions", required: false),
};
// Format versions
@@ -194,24 +193,24 @@ namespace OpenRA
public Rectangle Bounds;
public MapVisibility Visibility = MapVisibility.Lobby;
public string[] Categories = { "Conquest" };
public string[] Translations;
public int2 MapSize { get; private set; }
// Player and actor yaml. Public for access by the map importers and lint checks.
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = ImmutableArray<MiniYamlNode>.Empty;
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = ImmutableArray<MiniYamlNode>.Empty;
public List<MiniYamlNode> PlayerDefinitions = new List<MiniYamlNode>();
public List<MiniYamlNode> ActorDefinitions = new List<MiniYamlNode>();
// Custom map yaml. Public for access by the map importers and lint checks
public MiniYaml RuleDefinitions;
public MiniYaml TranslationDefinitions;
public MiniYaml SequenceDefinitions;
public MiniYaml ModelSequenceDefinitions;
public MiniYaml WeaponDefinitions;
public MiniYaml VoiceDefinitions;
public MiniYaml MusicDefinitions;
public MiniYaml NotificationDefinitions;
public readonly MiniYaml RuleDefinitions;
public readonly MiniYaml SequenceDefinitions;
public readonly MiniYaml ModelSequenceDefinitions;
public readonly MiniYaml WeaponDefinitions;
public readonly MiniYaml VoiceDefinitions;
public readonly MiniYaml MusicDefinitions;
public readonly MiniYaml NotificationDefinitions;
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new Dictionary<CPos, TerrainTile>();
// Generated data
public readonly MapGrid Grid;
@@ -219,8 +218,6 @@ namespace OpenRA
public string Uid { get; private set; }
public Ruleset Rules { get; private set; }
public SequenceSet Sequences { get; private set; }
public bool InvalidCustomRules { get; private set; }
public Exception InvalidCustomRulesException { get; private set; }
@@ -257,6 +254,8 @@ namespace OpenRA
CellLayer<byte> projectedHeight;
Rectangle projectionSafeBounds;
internal Translation Translation;
public static string ComputeUID(IReadOnlyPackage package)
{
return ComputeUID(package, GetMapFormat(package));
@@ -275,10 +274,7 @@ namespace OpenRA
try
{
foreach (var filename in contents)
if (filename.EndsWith(".yaml", StringComparison.Ordinal) ||
filename.EndsWith(".bin", StringComparison.Ordinal) ||
filename.EndsWith(".lua", StringComparison.Ordinal) ||
(format >= 12 && filename == "map.png"))
if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua") || (format >= 12 && filename == "map.png"))
streams.Add(package.GetStream(filename));
// Take the SHA1
@@ -361,15 +357,15 @@ namespace OpenRA
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), $"{package.Name}:map.yaml"));
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name));
foreach (var field in YamlFields)
field.Deserialize(this, yaml);
field.Deserialize(this, yaml.Nodes);
if (MapFormat < SupportedMapFormat)
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
Grid = modData.Manifest.Get<MapGrid>();
@@ -394,7 +390,7 @@ namespace OpenRA
// TODO: Remember to remove this when rewriting tile variants / PickAny
if (index == byte.MaxValue)
index = (byte)(i % 4 + j % 4 * 4);
index = (byte)(i % 4 + (j % 4) * 4);
Tiles[new MPos(i, j)] = new TerrainTile(tile, index);
}
@@ -441,18 +437,19 @@ namespace OpenRA
try
{
Rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions,
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, ModelSequenceDefinitions);
VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions, ModelSequenceDefinitions);
}
catch (Exception e)
{
Log.Write("debug", $"Failed to load rules for {Title} with error");
Log.Write("debug", e);
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e);
InvalidCustomRules = true;
InvalidCustomRulesException = e;
Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset);
}
Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions);
Rules.Sequences.Preload();
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
var tl = new MPos(0, 0).ToCPos(this);
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
@@ -483,17 +480,17 @@ namespace OpenRA
AllEdgeCells = UpdateEdgeCells();
// Invalidate the entry for a cell if anything could cause the terrain index to change.
void InvalidateTerrainIndex(CPos c)
Action<CPos> invalidateTerrainIndex = c =>
{
if (cachedTerrainIndexes != null)
cachedTerrainIndexes[c] = InvalidCachedTerrainIndex;
}
};
// Even though the cache is lazily initialized, we must attach these event handlers on init.
// This ensures our handler to invalidate the cache runs first,
// so other listeners to these same events will get correct data when calling GetTerrainIndex.
CustomTerrain.CellEntryChanged += InvalidateTerrainIndex;
Tiles.CellEntryChanged += InvalidateTerrainIndex;
CustomTerrain.CellEntryChanged += invalidateTerrainIndex;
Tiles.CellEntryChanged += invalidateTerrainIndex;
}
void UpdateRamp(CPos cell)
@@ -611,7 +608,7 @@ namespace OpenRA
// Odd-height ramps get bumped up a level to the next even height layer
if ((height & 1) == 1 && Ramp[uv] != 0)
height++;
height += 1;
var candidates = new List<PPos>();
@@ -650,25 +647,13 @@ namespace OpenRA
foreach (var file in Package.Contents)
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
void UpdatePackage(string filename, byte[] data)
{
if (Package != toPackage)
toPackage.Update(filename, data);
else
{
var stream = Package.GetStream(filename);
if (stream == null || !Enumerable.SequenceEqual(data, stream.ReadAllBytes()))
toPackage.Update(filename, data);
}
}
if (!LockPreview)
UpdatePackage("map.png", SavePreview());
toPackage.Update("map.png", SavePreview());
// Update the package with the new map data
UpdatePackage("map.yaml", Encoding.UTF8.GetBytes(root.WriteToString()));
UpdatePackage("map.bin", SaveBinaryData());
var s = root.WriteToString();
toPackage.Update("map.yaml", Encoding.UTF8.GetBytes(s));
toPackage.Update("map.bin", SaveBinaryData());
Package = toPackage;
// Update UID to match the newly saved data
@@ -688,16 +673,16 @@ namespace OpenRA
writer.Write((ushort)MapSize.Y);
// Data offsets
const int TilesOffset = 17;
var tilesOffset = 17;
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.X * MapSize.Y + 17;
writer.Write((uint)TilesOffset);
writer.Write((uint)tilesOffset);
writer.Write((uint)heightsOffset);
writer.Write((uint)resourcesOffset);
// Tile data
if (TilesOffset != 0)
if (tilesOffset != 0)
{
for (var i = 0; i < MapSize.X; i++)
{
@@ -754,8 +739,8 @@ namespace OpenRA
public byte[] SavePreview()
{
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
var actors = ActorDefinitions.Where(a => actorTypes.Any(ai => ai.Name == a.Value.Value));
var positions = new List<(MPos Uv, Color Color)>();
var actors = ActorDefinitions.Where(a => actorTypes.Where(ai => ai.Name == a.Value.Value).Any());
var positions = new List<(MPos Position, Color Color)>();
foreach (var actor in actors)
{
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
@@ -779,10 +764,19 @@ namespace OpenRA
if (Grid.MaximumTerrainHeight > 0)
{
(top, bottom) = GetCellSpaceBounds();
// The minimap is drawn in cell space, so we need to
// unproject the PPos bounds to find the MPos boundaries.
// This matches the calculation in RadarWidget that is used ingame
for (var x = Bounds.Left; x < Bounds.Right; x++)
{
var allTop = Unproject(new PPos(x, Bounds.Top));
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
if (allTop.Count > 0)
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
if (top == int.MaxValue || bottom == int.MinValue)
throw new InvalidDataException("The map has invalid boundaries");
if (allBottom.Count > 0)
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
}
}
else
{
@@ -799,21 +793,18 @@ namespace OpenRA
bitmapWidth = 2 * bitmapWidth - 1;
var stride = bitmapWidth * 4;
const int PxStride = 4;
var pxStride = 4;
var minimapData = new byte[stride * height];
(Color Left, Color Right) terrainColor = default;
var colorsByPosition = positions
.GroupBy(p => p.Uv)
.ToDictionary(g => g.Key, g => g.First().Color);
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var uv = new MPos(x + Bounds.Left, y + top);
// TryGetValue will return Color.Transparent if not found
colorsByPosition.TryGetValue(uv, out var actorColor);
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
if (actorColor.A == 0)
terrainColor = GetTerrainColorPair(uv);
@@ -821,10 +812,10 @@ namespace OpenRA
{
// Odd rows are shifted right by 1px
var dx = uv.V & 1;
var xOffset = PxStride * (2 * x + dx);
var xOffset = pxStride * (2 * x + dx);
if (x + dx > 0)
{
var z = y * stride + xOffset - PxStride;
var z = y * stride + xOffset - pxStride;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
@@ -844,7 +835,7 @@ namespace OpenRA
}
else
{
var z = y * stride + PxStride * x;
var z = y * stride + pxStride * x;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
@@ -858,28 +849,6 @@ namespace OpenRA
return png.Save();
}
public (int Top, int Bottom) GetCellSpaceBounds()
{
var top = int.MaxValue;
var bottom = int.MinValue;
// The minimap is drawn in cell space, so we need to
// unproject the PPos bounds to find the MPos boundaries.
// This matches the calculation in RadarWidget that is used ingame
for (var x = Bounds.Left; x < Bounds.Right; x++)
{
var allTop = Unproject(new PPos(x, Bounds.Top));
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
if (allTop.Count > 0)
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
if (allBottom.Count > 0)
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
}
return (top, bottom);
}
public bool Contains(CPos cell)
{
if (Grid.Type == MapGridType.RectangularIsometric)
@@ -1014,11 +983,11 @@ namespace OpenRA
}
/// <summary>
/// The size of the map Height step in world units.
/// The size of the map Height step in world units
/// </summary>
/// RectangularIsometric defines 1024 units along the diagonal axis,
/// giving a half-tile height step of sqrt(2) * 512
public WDist CellHeightStep => new(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512);
public WDist CellHeightStep => new WDist(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512);
public CPos CellContaining(WPos pos)
{
@@ -1200,7 +1169,7 @@ namespace OpenRA
// Project this guessed cell and take the first available cell
// If it is projected outside the layer, then make another guess.
var allProjected = ProjectedCellsCovering(uv);
var projected = allProjected.Length > 0 ? allProjected[0]
var projected = allProjected.Length > 0 ? allProjected.First()
: new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom));
// Clamp the projected cell to the map area
@@ -1228,7 +1197,7 @@ namespace OpenRA
// This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode.
if (unProjected.Count == 0)
{
Log.Write("debug", $"Failed to clamp map cell {uv} to map bounds");
Log.Write("debug", "Failed to clamp map cell {0} to map bounds", uv);
return uv;
}
}
@@ -1269,9 +1238,9 @@ namespace OpenRA
PPos edge;
if (allProjected.Length > 0)
{
var puv = allProjected[0];
var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
var puv = allProjected.First();
var horizontalBound = ((puv.U - Bounds.Left) < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
var verticalBound = ((puv.V - Bounds.Top) < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
var du = Math.Abs(horizontalBound - puv.U);
var dv = Math.Abs(verticalBound - puv.V);
@@ -1300,7 +1269,7 @@ namespace OpenRA
// This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode.
if (unProjected.Count == 0)
{
Log.Write("debug", $"Failed to find closest edge for map cell {uv}");
Log.Write("debug", "Failed to find closest edge for map cell {0}", uv);
return uv;
}
}
@@ -1369,18 +1338,13 @@ namespace OpenRA
throw new ArgumentOutOfRangeException(nameof(maxRange),
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
return FindTilesInAnnulus();
IEnumerable<CPos> FindTilesInAnnulus()
for (var i = minRange; i <= maxRange; i++)
{
for (var i = minRange; i <= maxRange; i++)
foreach (var offset in Grid.TilesByDistance[i])
{
foreach (var offset in Grid.TilesByDistance[i])
{
var t = offset + center;
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
yield return t;
}
var t = offset + center;
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
yield return t;
}
}
}
@@ -1436,9 +1400,12 @@ namespace OpenRA
return false;
}
public void Dispose()
public string Translate(string key, IDictionary<string, object> args = null)
{
Sequences.Dispose();
if (Translation.TryGetString(key, out var message, args))
return message;
return modData.Translation.GetString(key, args);
}
}
}

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