Compare commits
28 Commits
log_chat_s
...
d40f5292b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40f5292b6 | ||
|
|
3a75add1ba | ||
|
|
b3bcdeb4ec | ||
|
|
dde39344a0 | ||
|
|
386f691c2e | ||
|
|
24623eaa65 | ||
|
|
dc89341634 | ||
|
|
8961b4986f | ||
|
|
cbf4207d22 | ||
|
|
c27bf85631 | ||
|
|
809cb16075 | ||
|
|
f4c186b7a6 | ||
|
|
c3cf94b67a | ||
|
|
8b3e7bec2a | ||
|
|
64ec6eef0a | ||
|
|
4dec1fe430 | ||
|
|
db3145ed5e | ||
|
|
e49135bb09 | ||
|
|
9d79e52989 | ||
|
|
0ac9d96ab8 | ||
|
|
5ce559c853 | ||
|
|
a4821b51a2 | ||
|
|
cfc026a1ac | ||
|
|
58ab3eb153 | ||
|
|
47b6542b1d | ||
|
|
3c7addcb80 | ||
|
|
37f1b9efbf | ||
|
|
82acdbc32a |
940
.editorconfig
940
.editorconfig
File diff suppressed because it is too large
Load Diff
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
|||||||
patreon: orahosting
|
|
||||||
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -14,14 +14,11 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Remove System .NET
|
|
||||||
run: sudo apt-get remove -y dotnet*
|
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -42,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Check Code
|
- name: Check Code
|
||||||
run: |
|
run: |
|
||||||
@@ -60,10 +57,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
|
|||||||
147
.github/workflows/documentation.yml
vendored
147
.github/workflows/documentation.yml
vendored
@@ -1,9 +1,6 @@
|
|||||||
name: Deploy Documentation
|
name: Deploy Documentation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ bleed ]
|
|
||||||
tags: [ 'release-*', 'playtest-*' ]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -15,49 +12,18 @@ permissions:
|
|||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
wiki:
|
||||||
name: Prepare version strings
|
name: Update Wiki
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
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
|
|
||||||
steps:
|
|
||||||
- name: Debug output
|
|
||||||
run: |
|
|
||||||
echo ${{ needs.prepare.outputs.git_tag }}
|
|
||||||
echo ${{ needs.prepare.outputs.version_type }}
|
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
ref: ${{ github.event.inputs.tag }}
|
||||||
|
|
||||||
- name: Install .NET 6
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -66,53 +32,49 @@ jobs:
|
|||||||
make all
|
make all
|
||||||
|
|
||||||
- name: Clone Wiki
|
- name: Clone Wiki
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: openra/openra.wiki
|
repository: openra/openra.wiki
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
path: wiki
|
path: wiki
|
||||||
|
|
||||||
- name: Update Wiki (Playtest)
|
- 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: |
|
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)
|
- 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: |
|
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
|
- name: Push Wiki
|
||||||
|
env:
|
||||||
|
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
cd wiki
|
cd wiki
|
||||||
git config --local user.email "actions@github.com"
|
git config --local user.email "actions@github.com"
|
||||||
git config --local user.name "GitHub Actions"
|
git config --local user.name "GitHub Actions"
|
||||||
git status
|
git add --all
|
||||||
git diff-index --quiet HEAD || \
|
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||||
(
|
git push origin master
|
||||||
git add --all && \
|
|
||||||
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
|
||||||
git push origin master
|
|
||||||
)
|
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Update docs.openra.net
|
name: Update docs.openra.net
|
||||||
needs: prepare
|
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Debug output
|
|
||||||
run: |
|
|
||||||
echo ${{ needs.prepare.outputs.git_tag }}
|
|
||||||
echo ${{ needs.prepare.outputs.version_type }}
|
|
||||||
|
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.git_tag }}
|
ref: ${{ github.event.inputs.tag }}
|
||||||
|
|
||||||
- name: Install .NET 6
|
- name: Install .NET 6
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -120,31 +82,62 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
make all
|
make all
|
||||||
|
|
||||||
# version_type is release/playtest/bleed - the name of the target branch.
|
- name: Clone docs.openra.net (Playtest)
|
||||||
- name: Clone docs.openra.net
|
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: openra/docs
|
repository: openra/docs
|
||||||
token: ${{ secrets.DOCS_TOKEN }}
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
path: docs
|
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@v3
|
||||||
|
with:
|
||||||
|
repository: openra/docs
|
||||||
|
token: ${{ secrets.DOCS_TOKEN }}
|
||||||
|
path: docs
|
||||||
|
ref: release
|
||||||
|
|
||||||
|
- name: Update docs.openra.net (Playtest)
|
||||||
|
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||||
|
env:
|
||||||
|
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
./utility.sh all --docs "${{ needs.prepare.outputs.git_tag }}" | python3 ./packaging/format-docs.py > "docs/api/traits.md"
|
./utility.sh all --docs "${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 --weapon-docs "${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 --sprite-sequence-docs "${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 --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: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
git config --local user.email "actions@github.com"
|
git config --local user.email "actions@github.com"
|
||||||
git config --local user.name "GitHub Actions"
|
git config --local user.name "GitHub Actions"
|
||||||
git status
|
git add api/*.md
|
||||||
git diff-index --quiet HEAD || \
|
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||||
(
|
|
||||||
git add api/*.md && \
|
- name: Push docs.openra.net (Release)
|
||||||
git commit -m "Update auto-generated documentation for ${{ needs.prepare.outputs.git_tag }}" && \
|
if: startsWith(github.event.inputs.tag, 'release-')
|
||||||
git push origin ${{ needs.prepare.outputs.version_type }}
|
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
|
||||||
|
|||||||
91
.github/workflows/itch.yml
vendored
91
.github/workflows/itch.yml
vendored
@@ -15,56 +15,73 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: github.repository == 'openra/openra'
|
if: github.repository == 'openra/openra'
|
||||||
steps:
|
steps:
|
||||||
- name: Download Butler
|
- name: Download Packages
|
||||||
env:
|
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
|
||||||
run: |
|
|
||||||
wget -cq -O butler-linux-amd64.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
|
|
||||||
unzip butler-linux-amd64.zip
|
|
||||||
rm butler-linux-amd64.zip
|
|
||||||
chmod +x butler
|
|
||||||
./butler -V
|
|
||||||
./butler login
|
|
||||||
|
|
||||||
- name: Publish Windows Installer
|
|
||||||
env:
|
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
|
||||||
run: |
|
run: |
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
|
||||||
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64.exe" "openra/openra:win" --userversion ${{ github.event.inputs.tag }}
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
|
||||||
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
||||||
- name: Publish Windows Itch Bundle
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||||
env:
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||||
run: |
|
|
||||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
|
||||||
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
|
||||||
./butler push "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" "openra/openra:itch" --userversion ${{ github.event.inputs.tag }}
|
|
||||||
|
- name: Publish Windows Installer
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
|
env:
|
||||||
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
|
CHANNEL: win
|
||||||
|
ITCH_GAME: openra
|
||||||
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
|
||||||
|
|
||||||
|
- name: Publish Windows Itch Bundle
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
|
env:
|
||||||
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
|
CHANNEL: itch
|
||||||
|
ITCH_GAME: openra
|
||||||
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||||
|
|
||||||
- name: Publish macOS Package
|
- name: Publish macOS Package
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
env:
|
env:
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
run: |
|
CHANNEL: macos
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
|
ITCH_GAME: openra
|
||||||
./butler push "OpenRA-${{ github.event.inputs.tag }}.dmg" "openra/openra:macos" --userversion ${{ github.event.inputs.tag }}
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
|
||||||
|
|
||||||
- name: Publish RA AppImage
|
- name: Publish RA AppImage
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
env:
|
env:
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
run: |
|
CHANNEL: linux-ra
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
|
ITCH_GAME: openra
|
||||||
./butler push "OpenRA-Red-Alert-x86_64.AppImage" "openra/openra:linux-ra" --userversion ${{ github.event.inputs.tag }}
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||||
|
|
||||||
- name: Publish TD AppImage
|
- name: Publish TD AppImage
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
env:
|
env:
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
run: |
|
CHANNEL: linux-cnc
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
ITCH_GAME: openra
|
||||||
./butler push "OpenRA-Tiberian-Dawn-x86_64.AppImage" "openra/openra:linux-cnc" --userversion ${{ github.event.inputs.tag }}
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||||
|
|
||||||
- name: Publish D2k AppImage
|
- name: Publish D2k AppImage
|
||||||
|
uses: josephbmanley/butler-publish-itchio-action@master
|
||||||
env:
|
env:
|
||||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||||
run: |
|
CHANNEL: linux-d2k
|
||||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
|
ITCH_GAME: openra
|
||||||
./butler push "OpenRA-Dune-2000-x86_64.AppImage" "openra/openra:linux-d2k" --userversion ${{ github.event.inputs.tag }}
|
ITCH_USER: openra
|
||||||
|
VERSION: ${{ github.event.inputs.tag }}
|
||||||
|
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||||
|
|||||||
54
.github/workflows/packaging.yml
vendored
54
.github/workflows/packaging.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Prepare Environment
|
- name: Prepare Environment
|
||||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||||
@@ -40,10 +40,10 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -53,25 +53,27 @@ jobs:
|
|||||||
- name: Package AppImages
|
- name: Package AppImages
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build/linux
|
mkdir -p build/linux
|
||||||
sudo apt-get install -y desktop-file-utils
|
sudo apt install libfuse2
|
||||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||||
|
|
||||||
- name: Upload Packages
|
- name: Upload Packages
|
||||||
env:
|
uses: svenstaro/upload-release-action@v2
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
shell: bash
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
tag: ${{ github.ref }}
|
||||||
gh release upload ${{ github.ref_name }} build/linux/*
|
overwrite: true
|
||||||
|
file_glob: true
|
||||||
|
file: build/linux/*
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
name: macOS Disk Image
|
name: macOS Disk Image
|
||||||
runs-on: macos-13
|
runs-on: macos-11
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -90,21 +92,23 @@ jobs:
|
|||||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||||
|
|
||||||
- name: Upload Package
|
- name: Upload Package
|
||||||
env:
|
uses: svenstaro/upload-release-action@v2
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
shell: bash
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
tag: ${{ github.ref }}
|
||||||
gh release upload ${{ github.ref_name }} build/macos/*
|
overwrite: true
|
||||||
|
file_glob: true
|
||||||
|
file: build/macos/*
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Windows Installers
|
name: Windows Installers
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Clone Repository
|
- name: Clone Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0
|
- name: Install .NET 6.0
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
@@ -120,8 +124,10 @@ jobs:
|
|||||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||||
|
|
||||||
- name: Upload Packages
|
- name: Upload Packages
|
||||||
env:
|
uses: svenstaro/upload-release-action@v2
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
shell: bash
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
tag: ${{ github.ref }}
|
||||||
gh release upload ${{ github.ref_name }} build/windows/*
|
overwrite: true
|
||||||
|
file_glob: true
|
||||||
|
file: build/windows/*
|
||||||
|
|||||||
60
AUTHORS
60
AUTHORS
@@ -1,4 +1,5 @@
|
|||||||
OpenRA wouldn't be where it is today without the hard work of many contributors.
|
OpenRA wouldn't be where it is today without the
|
||||||
|
hard work of many contributors.
|
||||||
|
|
||||||
The OpenRA developers are:
|
The OpenRA developers are:
|
||||||
* Gustas Kažukauskas (PunkPun)
|
* Gustas Kažukauskas (PunkPun)
|
||||||
@@ -97,7 +98,6 @@ Also thanks to:
|
|||||||
* Kanar
|
* Kanar
|
||||||
* Kenny Hoxworth (hoxworth)
|
* Kenny Hoxworth (hoxworth)
|
||||||
* Kevin Azzam (ChaoticMind)
|
* Kevin Azzam (ChaoticMind)
|
||||||
* Kevin Streser
|
|
||||||
* Krishnakanth Mallik
|
* Krishnakanth Mallik
|
||||||
* Kyle Smith (Smitty)
|
* Kyle Smith (Smitty)
|
||||||
* Kyrre Soerensen (zypres)
|
* Kyrre Soerensen (zypres)
|
||||||
@@ -150,7 +150,6 @@ Also thanks to:
|
|||||||
* Teemu Nieminen (Temeez)
|
* Teemu Nieminen (Temeez)
|
||||||
* Thomas Christlieb (ThomasChr)
|
* Thomas Christlieb (ThomasChr)
|
||||||
* Tim Mylemans (gecko)
|
* Tim Mylemans (gecko)
|
||||||
* Tinix
|
|
||||||
* Tirili
|
* Tirili
|
||||||
* Tomas Einarsson (Mesacer)
|
* Tomas Einarsson (Mesacer)
|
||||||
* Tom van Leth (tovl)
|
* Tom van Leth (tovl)
|
||||||
@@ -162,42 +161,61 @@ Also thanks to:
|
|||||||
* Wojciech Walaszek (Voidwalker)
|
* Wojciech Walaszek (Voidwalker)
|
||||||
* Wuschel
|
* Wuschel
|
||||||
|
|
||||||
Using GNU FreeFont distributed under the GNU GPL terms.
|
Using GNU FreeFont distributed under the GNU GPL
|
||||||
|
terms.
|
||||||
|
|
||||||
Using Simple DirectMedia Layer distributed under the terms of the zlib license.
|
Using Simple DirectMedia Layer distributed under
|
||||||
|
the terms of the zlib license.
|
||||||
|
|
||||||
Using FreeType distributed under the terms of the FreeType License.
|
Using FreeType distributed under the terms of the
|
||||||
|
FreeType License.
|
||||||
|
|
||||||
Using OpenAL Soft distributed under the GNU LGPL.
|
Using OpenAL Soft distributed under the GNU LGPL.
|
||||||
|
|
||||||
Using SDL2-CS and OpenAL-CS created by Ethan Lee and released under the zlib license.
|
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||||
|
Lee and released under the zlib license.
|
||||||
|
|
||||||
Using Eluant created by Chris Howie and released under the MIT license.
|
Using Eluant created by Chris Howie and released
|
||||||
|
under the MIT license.
|
||||||
|
|
||||||
Using FuzzyLogicLibrary (fuzzynet) by Dmitry Kaluzhny and released under the GNU GPL terms.
|
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
|
||||||
|
Kaluzhny and released under the GNU GPL terms.
|
||||||
|
|
||||||
Using Mono.Nat by Alan McGovern, Ben Motmans, Nicholas Terry distributed under the MIT license.
|
Using Mono.Nat by Alan McGovern, Ben Motmans,
|
||||||
|
Nicholas Terry distributed under the MIT license.
|
||||||
|
|
||||||
Using MP3Sharp by Robert Bruke and Zane Wagner licensed under the GNU LGPL Version 3.
|
Using MP3Sharp by Robert Bruke and Zane Wagner
|
||||||
|
licensed under the GNU LGPL Version 3.
|
||||||
|
|
||||||
Using TagLib# by Stephen Shaw licensed under the GNU LGPL Version 2.1.
|
Using TagLib# by Stephen Shaw licensed under the
|
||||||
|
GNU LGPL Version 2.1.
|
||||||
|
|
||||||
Using NVorbis by Andrew Ward distributed under the MIT license.
|
Using NVorbis by Andrew Ward distributed under
|
||||||
|
the MIT license.
|
||||||
|
|
||||||
Using ICSharpCode.SharpZipLib initially by Mike Krueger and distributed under the GNU GPL terms.
|
Using ICSharpCode.SharpZipLib initially by Mike
|
||||||
|
Krueger and distributed under the GNU GPL terms.
|
||||||
|
|
||||||
Using rix0rrr.BeaconLib developed by Rico Huijbers distributed under MIT License.
|
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||||
|
distributed under MIT License.
|
||||||
|
|
||||||
Using DiscordRichPresence developed by Lachee distributed under MIT License.
|
Using DiscordRichPresence developed by Lachee
|
||||||
|
distributed under MIT License.
|
||||||
|
|
||||||
Using Json.NET developed by James Newton-King distributed under MIT License.
|
Using Json.NET developed by James Newton-King
|
||||||
|
distributed under MIT License.
|
||||||
|
|
||||||
Using ANGLE distributed under the BS3 3-Clause license.
|
Using ANGLE distributed under the BS3 3-Clause license.
|
||||||
|
|
||||||
Using Pfim developed by Nick Babcock distributed under the MIT license.
|
Using Pfim developed by Nick Babcock
|
||||||
|
distributed under the MIT license.
|
||||||
|
|
||||||
Using Linguini by the Space Station 14 team licensed under Apache and MIT terms.
|
Using Linguini by the Space Station 14 team
|
||||||
|
licensed under Apache and MIT terms.
|
||||||
|
|
||||||
This site or product includes IP2Location LITE data available from https://www.ip2location.com.
|
This site or product includes IP2Location LITE data
|
||||||
|
available from http://www.ip2location.com.
|
||||||
|
|
||||||
Finally, special thanks goes to the original teams at Westwood Studios and EA for creating the classic games which OpenRA aims to reimagine.
|
Finally, special thanks goes to the original teams
|
||||||
|
at Westwood Studios and EA for creating the classic
|
||||||
|
games which OpenRA aims to reimagine.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ members of the project's leadership.
|
|||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
available at [https://contributor-covenant.org/version/1/4][version]
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
[homepage]: https://contributor-covenant.org
|
[homepage]: http://contributor-covenant.org
|
||||||
[version]: https://contributor-covenant.org/version/1/4/
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Help us keep OpenRA open and inclusive. Please read and follow our [Code of Cond
|
|||||||
|
|
||||||
* [Coding standard](https://github.com/OpenRA/OpenRA/wiki/Coding-Standard)
|
* [Coding standard](https://github.com/OpenRA/OpenRA/wiki/Coding-Standard)
|
||||||
* [Branches and Releases](https://github.com/OpenRA/OpenRA/wiki/Branches-and-Releases)
|
* [Branches and Releases](https://github.com/OpenRA/OpenRA/wiki/Branches-and-Releases)
|
||||||
* [Licensing](https://www.gnu.org/licenses/quick-guide-gplv3.html)
|
* [Licensing](http://www.gnu.org/licenses/quick-guide-gplv3.html)
|
||||||
|
|
||||||
Please `git rebase` to the latest revision of the bleed branch.
|
Please `git rebase` to the latest revision of the bleed branch.
|
||||||
|
|
||||||
|
|||||||
8
COPYING
8
COPYING
@@ -1,7 +1,7 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
The GNU General Public License does not permit incorporating your program
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
<!-- Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
<!-- Enabling GenerateDocumentationFile is required for IDE0005 (Remove unnecessary import)
|
||||||
rule to run in command line builds. https://github.com/dotnet/roslyn/issues/41640
|
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 -->
|
Enable only for Debug builds to improve compile-time performance for Release builds -->
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -51,11 +51,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- StyleCop/Roslynator -->
|
<!-- StyleCop -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
<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="Roslynator.Formatting.Analyzers" Version="4.2.0" PrivateAssets="All" Condition="'$(MSBuildRuntimeType)'!='Mono'" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Windows
|
|||||||
=======
|
=======
|
||||||
|
|
||||||
Compiling OpenRA requires the following dependencies:
|
Compiling OpenRA requires the following dependencies:
|
||||||
* [Windows PowerShell >= 4.0](https://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||||
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
* [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) (or via Visual Studio)
|
||||||
|
|
||||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
|
||||||
@@ -25,7 +25,7 @@ To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if u
|
|||||||
|
|
||||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||||
|
|
||||||
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](https://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](https://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
|
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](http://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
|
||||||
|
|
||||||
These can be installed using your package manager on various distros:
|
These can be installed using your package manager on various distros:
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -153,12 +153,12 @@ tests:
|
|||||||
|
|
||||||
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
|
||||||
#
|
#
|
||||||
version: VERSION mods/*/mod.yaml
|
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||||
ifeq ($(VERSION),)
|
ifeq ($(VERSION),)
|
||||||
$(error Unable to determine new version (requires git or override of variable VERSION))
|
$(error Unable to determine new version (requires git or override of variable VERSION))
|
||||||
endif
|
endif
|
||||||
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
|
||||||
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/*/mod.yaml'
|
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
|
||||||
|
|||||||
@@ -146,22 +146,18 @@ namespace OpenRA.Activities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>
|
|
||||||
/// Called every tick to run activity logic. Returns false if the activity should
|
/// 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
|
/// remain active, or true if it is complete. Cancelled activities must ensure they
|
||||||
/// return the actor to a consistent state before returning true.
|
/// return the actor to a consistent state before returning true.
|
||||||
/// </para>
|
///
|
||||||
/// <para>
|
|
||||||
/// Child activities can be queued using QueueChild, and these will be ticked
|
/// 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
|
/// 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
|
/// in parallel with child activities should set ChildHasPriority to false and
|
||||||
/// manually call TickChildren.
|
/// manually call TickChildren.
|
||||||
/// </para>
|
///
|
||||||
/// <para>
|
|
||||||
/// Queuing one or more child activities and returning true is valid, and causes
|
/// Queuing one or more child activities and returning true is valid, and causes
|
||||||
/// the activity to be completed immediately (without ticking again) once the
|
/// the activity to be completed immediately (without ticking again) once the
|
||||||
/// children have completed.
|
/// children have completed.
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool Tick(Actor self)
|
public virtual bool Tick(Actor self)
|
||||||
{
|
{
|
||||||
@@ -226,11 +222,10 @@ namespace OpenRA.Activities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>Prints the activity tree, starting from the top or optionally from a given origin.</para>
|
/// Prints the activity tree, starting from the top or optionally from a given origin.
|
||||||
/// <para>
|
///
|
||||||
/// Call this method from any place that's called during a tick, such as the Tick() method itself or
|
/// 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.
|
/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="self">The actor performing this activity.</param>
|
/// <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>
|
/// <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>
|
||||||
|
|||||||
@@ -71,12 +71,7 @@ namespace OpenRA
|
|||||||
public IEffectiveOwner EffectiveOwner { get; }
|
public IEffectiveOwner EffectiveOwner { get; }
|
||||||
public IOccupySpace OccupiesSpace { get; }
|
public IOccupySpace OccupiesSpace { get; }
|
||||||
public ITargetable[] Targetables { get; }
|
public ITargetable[] Targetables { get; }
|
||||||
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; }
|
public IEnumerable<ITargetablePositions> EnabledTargetablePositions { get; private set; }
|
||||||
readonly ICrushable[] crushables;
|
|
||||||
public ICrushable[] Crushables
|
|
||||||
{
|
|
||||||
get => crushables ?? throw new InvalidOperationException($"Crushables for {Info.Name} are not initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsIdle => CurrentActivity == null;
|
public bool IsIdle => CurrentActivity == null;
|
||||||
public bool IsDead => Disposed || (health != null && health.IsDead);
|
public bool IsDead => Disposed || (health != null && health.IsDead);
|
||||||
@@ -160,7 +155,6 @@ namespace OpenRA
|
|||||||
var targetablesList = new List<ITargetable>();
|
var targetablesList = new List<ITargetable>();
|
||||||
var targetablePositionsList = new List<ITargetablePositions>();
|
var targetablePositionsList = new List<ITargetablePositions>();
|
||||||
var syncHashesList = new List<SyncHash>();
|
var syncHashesList = new List<SyncHash>();
|
||||||
var crushablesList = new List<ICrushable>();
|
|
||||||
|
|
||||||
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
foreach (var traitInfo in Info.TraitsInConstructOrder())
|
||||||
{
|
{
|
||||||
@@ -187,7 +181,6 @@ namespace OpenRA
|
|||||||
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
{ if (trait is ITargetable t) targetablesList.Add(t); }
|
||||||
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
|
||||||
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
|
||||||
{ if (trait is ICrushable t) crushablesList.Add(t); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveOrders = resolveOrdersList.ToArray();
|
resolveOrders = resolveOrdersList.ToArray();
|
||||||
@@ -202,7 +195,6 @@ namespace OpenRA
|
|||||||
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
|
||||||
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
|
||||||
SyncHashes = syncHashesList.ToArray();
|
SyncHashes = syncHashesList.ToArray();
|
||||||
crushables = crushablesList.ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ using OpenRA.Scripting;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public readonly struct CPos : IEquatable<CPos>, IScriptBindable,
|
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
|
||||||
ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, ILuaToStringBinding
|
|
||||||
{
|
{
|
||||||
// Coordinates are packed in a 32 bit signed int
|
// Coordinates are packed in a 32 bit signed int
|
||||||
// X and Y are 12 bits (signed): -2048...2047
|
// X and Y are 12 bits (signed): -2048...2047
|
||||||
@@ -97,8 +96,7 @@ namespace OpenRA
|
|||||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
|
||||||
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments " +
|
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
|
||||||
|
|
||||||
return new LuaCustomClrObject(a + b);
|
return new LuaCustomClrObject(a + b);
|
||||||
}
|
}
|
||||||
@@ -107,8 +105,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var rightType = right.WrappedClrType();
|
var rightType = right.WrappedClrType();
|
||||||
if (!left.TryGetClrValue(out CPos a))
|
if (!left.TryGetClrValue(out CPos a))
|
||||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments " +
|
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||||
$"({left.WrappedClrType().Name}, {rightType.Name})");
|
|
||||||
|
|
||||||
if (rightType == typeof(CPos))
|
if (rightType == typeof(CPos))
|
||||||
{
|
{
|
||||||
@@ -121,8 +118,7 @@ namespace OpenRA
|
|||||||
return new LuaCustomClrObject(a - b);
|
return new LuaCustomClrObject(a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments " +
|
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
|
||||||
$"({left.WrappedClrType().Name}, {rightType.Name})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
@@ -149,8 +145,6 @@ namespace OpenRA
|
|||||||
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue ToString(LuaRuntime runtime) => ToString();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ using OpenRA.Scripting;
|
|||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public readonly struct CVec : IEquatable<CVec>, IScriptBindable,
|
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
|
||||||
ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaUnaryMinusBinding,
|
|
||||||
ILuaMultiplicationBinding, ILuaDivisionBinding, ILuaTableBinding, ILuaToStringBinding
|
|
||||||
{
|
{
|
||||||
public readonly int X, Y;
|
public readonly int X, Y;
|
||||||
|
|
||||||
@@ -63,14 +61,14 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static readonly CVec[] Directions =
|
public static readonly CVec[] Directions =
|
||||||
{
|
{
|
||||||
new(-1, -1),
|
new CVec(-1, -1),
|
||||||
new(-1, 0),
|
new CVec(-1, 0),
|
||||||
new(-1, 1),
|
new CVec(-1, 1),
|
||||||
new(0, -1),
|
new CVec(0, -1),
|
||||||
new(0, 1),
|
new CVec(0, 1),
|
||||||
new(1, -1),
|
new CVec(1, -1),
|
||||||
new(1, 0),
|
new CVec(1, 0),
|
||||||
new(1, 1),
|
new CVec(1, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
#region Scripting interface
|
#region Scripting interface
|
||||||
@@ -78,8 +76,7 @@ namespace OpenRA
|
|||||||
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||||
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments " +
|
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
|
||||||
|
|
||||||
return new LuaCustomClrObject(a + b);
|
return new LuaCustomClrObject(a + b);
|
||||||
}
|
}
|
||||||
@@ -87,12 +84,16 @@ namespace OpenRA
|
|||||||
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||||
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments " +
|
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
||||||
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
|
||||||
|
|
||||||
return new LuaCustomClrObject(a - b);
|
return new LuaCustomClrObject(a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LuaValue Minus(LuaRuntime runtime)
|
||||||
|
{
|
||||||
|
return new LuaCustomClrObject(-this);
|
||||||
|
}
|
||||||
|
|
||||||
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
|
||||||
{
|
{
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
|
||||||
@@ -101,29 +102,6 @@ namespace OpenRA
|
|||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue Minus(LuaRuntime runtime)
|
|
||||||
{
|
|
||||||
return new LuaCustomClrObject(-this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LuaValue Multiply(LuaRuntime runtime, LuaValue left, LuaValue right)
|
|
||||||
{
|
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out int b))
|
|
||||||
throw new LuaException("Attempted to call CVec.Multiply(CVec, integer) with invalid arguments " +
|
|
||||||
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
|
||||||
|
|
||||||
return new LuaCustomClrObject(a * b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LuaValue Divide(LuaRuntime runtime, LuaValue left, LuaValue right)
|
|
||||||
{
|
|
||||||
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out int b))
|
|
||||||
throw new LuaException("Attempted to call CVec.Multiply(CVec, integer) with invalid arguments " +
|
|
||||||
$"({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
|
|
||||||
|
|
||||||
return new LuaCustomClrObject(a / b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LuaValue this[LuaRuntime runtime, LuaValue key]
|
public LuaValue this[LuaRuntime runtime, LuaValue key]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -132,7 +110,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
case "X": return X;
|
case "X": return X;
|
||||||
case "Y": return Y;
|
case "Y": return Y;
|
||||||
case "Length": return Length;
|
|
||||||
default: throw new LuaException($"CVec does not define a member '{key}'");
|
default: throw new LuaException($"CVec does not define a member '{key}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,8 +117,6 @@ namespace OpenRA
|
|||||||
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue ToString(LuaRuntime runtime) => ToString();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ namespace OpenRA
|
|||||||
// Fixed byte pattern for the OID header
|
// 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 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 static string PublicKeyFingerprint(RSAParameters parameters)
|
||||||
{
|
{
|
||||||
// Public key fingerprint is defined as the SHA1 of the modulus + exponent bytes
|
// 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))
|
using (var s = new MemoryStream(data))
|
||||||
{
|
{
|
||||||
// SEQUENCE
|
// SEQUENCE
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
|
|
||||||
// SEQUENCE -> fixed header junk
|
// SEQUENCE -> fixed header junk
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
var headerLength = ReadTLVLength(s);
|
var headerLength = ReadTLVLength(s);
|
||||||
s.Position += headerLength;
|
s.Position += headerLength;
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING
|
// SEQUENCE -> BIT_STRING
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
// SEQUENCE -> BIT_STRING -> SEQUENCE
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
ReadTLVLength(s);
|
ReadTLVLength(s);
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (modulus)
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
var modulusLength = ReadTLVLength(s);
|
var modulusLength = ReadTLVLength(s);
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
var modulus = s.ReadBytes(modulusLength - 1);
|
var modulus = s.ReadBytes(modulusLength - 1);
|
||||||
|
|
||||||
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
// SEQUENCE -> BIT_STRING -> SEQUENCE -> INTEGER (exponent)
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
var exponentLength = ReadTLVLength(s);
|
var exponentLength = ReadTLVLength(s);
|
||||||
s.ReadUInt8();
|
s.ReadByte();
|
||||||
var exponent = s.ReadBytes(exponentLength - 1);
|
var exponent = s.ReadBytes(exponentLength - 1);
|
||||||
|
|
||||||
return new RSAParameters
|
return new RSAParameters
|
||||||
@@ -161,13 +158,13 @@ namespace OpenRA
|
|||||||
|
|
||||||
static int ReadTLVLength(Stream s)
|
static int ReadTLVLength(Stream s)
|
||||||
{
|
{
|
||||||
var length = s.ReadUInt8();
|
var length = s.ReadByte();
|
||||||
if (length < 0x80)
|
if (length < 0x80)
|
||||||
return length;
|
return length;
|
||||||
|
|
||||||
Span<byte> data = stackalloc byte[4];
|
var data = new byte[4];
|
||||||
s.ReadBytes(data[..Math.Min(length & 0x7F, 4)]);
|
s.ReadBytes(data, 0, Math.Min(length & 0x7F, 4));
|
||||||
return BitConverter.ToInt32(data);
|
return BitConverter.ToInt32(data.ToArray(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int TripletFullLength(int dataLength)
|
static int TripletFullLength(int dataLength)
|
||||||
@@ -252,44 +249,19 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static string SHA1Hash(Stream data)
|
public static string SHA1Hash(Stream data)
|
||||||
{
|
{
|
||||||
using var csp = SHA1.Create();
|
using (var csp = SHA1.Create())
|
||||||
return ToHex(csp.ComputeHash(data), true);
|
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(byte[] data)
|
public static string SHA1Hash(byte[] data)
|
||||||
{
|
{
|
||||||
using var csp = SHA1.Create();
|
using (var csp = SHA1.Create())
|
||||||
return ToHex(csp.ComputeHash(data), true);
|
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string SHA1Hash(string data)
|
public static string SHA1Hash(string data)
|
||||||
{
|
{
|
||||||
return SHA1Hash(Encoding.UTF8.GetBytes(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly string Id;
|
public readonly string Id;
|
||||||
public readonly string Version;
|
public readonly string Version;
|
||||||
|
public readonly string Title;
|
||||||
public readonly string LaunchPath;
|
public readonly string LaunchPath;
|
||||||
public readonly string[] LaunchArgs;
|
public readonly string[] LaunchArgs;
|
||||||
public Sprite Icon { get; internal set; }
|
public Sprite Icon { get; internal set; }
|
||||||
@@ -65,7 +66,6 @@ namespace OpenRA
|
|||||||
// Several types of support directory types are available, depending on
|
// Several types of support directory types are available, depending on
|
||||||
// how the player has installed and launched the game.
|
// how the player has installed and launched the game.
|
||||||
// Read registration metadata from all of them
|
// Read registration metadata from all of them
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System))
|
||||||
{
|
{
|
||||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||||
@@ -76,7 +76,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var yaml = MiniYaml.FromFile(path, stringPool: stringPool).First().Value;
|
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||||
LoadMod(yaml, path);
|
LoadMod(yaml, path);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -94,17 +94,17 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (sheetBuilder != null)
|
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))
|
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
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))
|
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
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))
|
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||||
@@ -122,29 +122,26 @@ namespace OpenRA
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var key = ExternalMod.MakeKey(mod);
|
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("Id", mod.Id),
|
||||||
new MiniYamlNode("Version", mod.Metadata.Version),
|
new MiniYamlNode("Version", mod.Metadata.Version),
|
||||||
|
new MiniYamlNode("Title", mod.Metadata.Title),
|
||||||
new MiniYamlNode("LaunchPath", launchPath),
|
new MiniYamlNode("LaunchPath", launchPath),
|
||||||
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var iconNodes = new List<MiniYamlNode>();
|
|
||||||
|
|
||||||
using (var stream = mod.Package.GetStream("icon.png"))
|
using (var stream = mod.Package.GetStream("icon.png"))
|
||||||
if (stream != null)
|
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"))
|
using (var stream = mod.Package.GetStream("icon-2x.png"))
|
||||||
if (stream != null)
|
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"))
|
using (var stream = mod.Package.GetStream("icon-3x.png"))
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
iconNodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes())));
|
||||||
|
|
||||||
yaml = yaml.WithValue(yaml.Value.WithNodesAppended(iconNodes));
|
|
||||||
|
|
||||||
var sources = new HashSet<string>();
|
var sources = new HashSet<string>();
|
||||||
if (registration.HasFlag(ModRegistration.System))
|
if (registration.HasFlag(ModRegistration.System))
|
||||||
@@ -204,7 +201,7 @@ namespace OpenRA
|
|||||||
string modKey = null;
|
string modKey = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var yaml = MiniYaml.FromFile(path).First().Value;
|
var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value;
|
||||||
var m = FieldLoader.Load<ExternalMod>(yaml);
|
var m = FieldLoader.Load<ExternalMod>(yaml);
|
||||||
modKey = ExternalMod.MakeKey(m);
|
modKey = ExternalMod.MakeKey(m);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
@@ -23,24 +22,15 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class Exts
|
public static class Exts
|
||||||
{
|
{
|
||||||
/// <summary>Returns <see cref="Color"/> of the <paramref name="actor"/>, taking <see cref="Actor.EffectiveOwner"/> into account.</summary>
|
public static bool IsUppercase(this string str)
|
||||||
public static Color OwnerColor(this Actor actor)
|
|
||||||
{
|
{
|
||||||
var effectiveOwner = actor.EffectiveOwner;
|
return string.Compare(str.ToUpperInvariant(), str, false) == 0;
|
||||||
if (effectiveOwner != null && effectiveOwner.Disguised && actor.World.RenderPlayer != null)
|
|
||||||
return effectiveOwner.Owner.Color;
|
|
||||||
|
|
||||||
return actor.Owner.Color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatInvariant(this string format, params object[] args)
|
public static T WithDefault<T>(T def, Func<T> f)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.InvariantCulture, format, args);
|
try { return f(); }
|
||||||
}
|
catch { return def; }
|
||||||
|
|
||||||
public static string FormatCurrent(this string format, params object[] args)
|
|
||||||
{
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, format, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
public static Lazy<T> Lazy<T>(Func<T> p) { return new Lazy<T>(p); }
|
||||||
@@ -141,35 +131,11 @@ namespace OpenRA
|
|||||||
return ret;
|
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)
|
public static int IndexOf<T>(this T[] array, T value)
|
||||||
{
|
{
|
||||||
return Array.IndexOf(array, 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)
|
public static T Random<T>(this IEnumerable<T> ts, MersenneTwister r)
|
||||||
{
|
{
|
||||||
return Random(ts, r, true);
|
return Random(ts, r, true);
|
||||||
@@ -182,7 +148,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
static T Random<T>(IEnumerable<T> ts, MersenneTwister r, bool throws)
|
||||||
{
|
{
|
||||||
var xs = ts as IReadOnlyCollection<T>;
|
var xs = ts as ICollection<T>;
|
||||||
xs ??= ts.ToList();
|
xs ??= ts.ToList();
|
||||||
if (xs.Count == 0)
|
if (xs.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -337,9 +303,9 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Adjust for other rounding modes
|
// Adjust for other rounding modes
|
||||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||||
root++;
|
root += 1;
|
||||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||||
root++;
|
root += 1;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -378,9 +344,9 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Adjust for other rounding modes
|
// Adjust for other rounding modes
|
||||||
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
if (round == ISqrtRoundMode.Nearest && remainder > root)
|
||||||
root++;
|
root += 1;
|
||||||
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
else if (round == ISqrtRoundMode.Ceiling && root * root < number)
|
||||||
root++;
|
root += 1;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -390,11 +356,6 @@ namespace OpenRA
|
|||||||
return number * 46341 / 32768;
|
return number * 46341 / 32768;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int MultiplyBySqrtTwoOverTwo(int number)
|
|
||||||
{
|
|
||||||
return (int)(number * 23170L / 32768L);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
|
||||||
{
|
{
|
||||||
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
var quotient = Math.DivRem(dividend, divisor, out var remainder);
|
||||||
@@ -433,26 +394,15 @@ namespace OpenRA
|
|||||||
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
|
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
|
||||||
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
||||||
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
||||||
{
|
|
||||||
var output = new Dictionary<TKey, TElement>();
|
|
||||||
IntoDictionaryWithConflictLog(source, keySelector, elementSelector, debugName, output, logKey, logValue);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void IntoDictionaryWithConflictLog<TSource, TKey, TElement>(
|
|
||||||
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
|
|
||||||
string debugName, Dictionary<TKey, TElement> output,
|
|
||||||
Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
|
|
||||||
{
|
{
|
||||||
// Fall back on ToString() if null functions are provided:
|
// Fall back on ToString() if null functions are provided:
|
||||||
logKey ??= s => s.ToString();
|
logKey ??= s => s.ToString();
|
||||||
logValue ??= s => s.ToString();
|
logValue ??= s => s.ToString();
|
||||||
|
|
||||||
// Try to build a dictionary and log all duplicates found (if any):
|
// Try to build a dictionary and log all duplicates found (if any):
|
||||||
Dictionary<TKey, List<string>> dupKeys = null;
|
var dupKeys = new Dictionary<TKey, List<string>>();
|
||||||
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
var capacity = source is ICollection<TSource> collection ? collection.Count : 0;
|
||||||
output.Clear();
|
var d = new Dictionary<TKey, TElement>(capacity);
|
||||||
output.EnsureCapacity(capacity);
|
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
{
|
{
|
||||||
var key = keySelector(item);
|
var key = keySelector(item);
|
||||||
@@ -463,15 +413,14 @@ namespace OpenRA
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check for a key conflict:
|
// Check for a key conflict:
|
||||||
if (!output.TryAdd(key, element))
|
if (!d.TryAdd(key, element))
|
||||||
{
|
{
|
||||||
dupKeys ??= new Dictionary<TKey, List<string>>();
|
|
||||||
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
|
||||||
{
|
{
|
||||||
// Log the initial conflicting value already inserted:
|
// Log the initial conflicting value already inserted:
|
||||||
dupKeyMessages = new List<string>
|
dupKeyMessages = new List<string>
|
||||||
{
|
{
|
||||||
logValue(output[key])
|
logValue(d[key])
|
||||||
};
|
};
|
||||||
dupKeys.Add(key, dupKeyMessages);
|
dupKeys.Add(key, dupKeyMessages);
|
||||||
}
|
}
|
||||||
@@ -482,14 +431,15 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If any duplicates were found, throw a descriptive error
|
// If any duplicates were found, throw a descriptive error
|
||||||
if (dupKeys != null)
|
if (dupKeys.Count > 0)
|
||||||
{
|
{
|
||||||
var badKeysFormatted = new StringBuilder(
|
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
|
||||||
$"{debugName}, duplicate values found for the following keys: ");
|
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
|
||||||
foreach (var p in dupKeys)
|
throw new ArgumentException(msg);
|
||||||
badKeysFormatted.Append($"{logKey(p.Key)}: [{string.Join(",", p.Value)}]");
|
|
||||||
throw new ArgumentException(badKeysFormatted.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the dictionary we built:
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color ColorLerp(float t, Color c1, Color c2)
|
public static Color ColorLerp(float t, Color c1, Color c2)
|
||||||
@@ -543,22 +493,17 @@ namespace OpenRA
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte ParseByteInvariant(string s)
|
public static int ParseIntegerInvariant(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)
|
|
||||||
{
|
{
|
||||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
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);
|
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
}
|
}
|
||||||
@@ -568,26 +513,6 @@ namespace OpenRA
|
|||||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
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)
|
public static bool IsTraitEnabled<T>(this T trait)
|
||||||
{
|
{
|
||||||
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
return trait is not IDisabledTrait disabledTrait || !disabledTrait.IsTraitDisabled;
|
||||||
@@ -637,6 +562,11 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return new LineSplitEnumerator(str.AsSpan(), separator);
|
return new LineSplitEnumerator(str.AsSpan(), separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryParseInt32Invariant(string s, out int i)
|
||||||
|
{
|
||||||
|
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct LineSplitEnumerator
|
public ref struct LineSplitEnumerator
|
||||||
@@ -651,7 +581,7 @@ namespace OpenRA
|
|||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly LineSplitEnumerator GetEnumerator() => this;
|
public LineSplitEnumerator GetEnumerator() => this;
|
||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ namespace OpenRA
|
|||||||
{ typeof(WAngle), ParseWAngle },
|
{ typeof(WAngle), ParseWAngle },
|
||||||
{ typeof(WRot), ParseWRot },
|
{ typeof(WRot), ParseWRot },
|
||||||
{ typeof(CPos), ParseCPos },
|
{ typeof(CPos), ParseCPos },
|
||||||
{ typeof(CPos[]), ParseCPosArray },
|
|
||||||
{ typeof(CVec), ParseCVec },
|
{ typeof(CVec), ParseCVec },
|
||||||
{ typeof(CVec[]), ParseCVecArray },
|
{ typeof(CVec[]), ParseCVecArray },
|
||||||
{ typeof(BooleanExpression), ParseBooleanExpression },
|
{ typeof(BooleanExpression), ParseBooleanExpression },
|
||||||
@@ -119,7 +118,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
|
||||||
{
|
{
|
||||||
if (Exts.TryParseInt32Invariant(value, out var res))
|
if (Exts.TryParseIntegerInvariant(value, out var res))
|
||||||
{
|
{
|
||||||
if (res >= 0 && res < BoxedInts.Length)
|
if (res >= 0 && res < BoxedInts.Length)
|
||||||
return BoxedInts[res];
|
return BoxedInts[res];
|
||||||
@@ -196,11 +195,11 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3)
|
||||||
&& WDist.TryParse(parts[0], out var rx)
|
{
|
||||||
&& WDist.TryParse(parts[1], out var ry)
|
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
|
||||||
&& WDist.TryParse(parts[2], out var rz))
|
return new WVec(rx, ry, rz);
|
||||||
return new WVec(rx, ry, rz);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -220,8 +219,8 @@ namespace OpenRA
|
|||||||
for (var i = 0; i < vecs.Length; ++i)
|
for (var i = 0; i < vecs.Length; ++i)
|
||||||
{
|
{
|
||||||
if (WDist.TryParse(parts[3 * i], out var rx)
|
if (WDist.TryParse(parts[3 * i], out var rx)
|
||||||
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
&& WDist.TryParse(parts[3 * i + 1], out var ry)
|
||||||
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
&& WDist.TryParse(parts[3 * i + 2], out var rz))
|
||||||
vecs[i] = new WVec(rx, ry, rz);
|
vecs[i] = new WVec(rx, ry, rz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +235,13 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3)
|
||||||
&& WDist.TryParse(parts[0], out var rx)
|
{
|
||||||
&& WDist.TryParse(parts[1], out var ry)
|
if (WDist.TryParse(parts[0], out var rx)
|
||||||
&& WDist.TryParse(parts[2], out var rz))
|
&& WDist.TryParse(parts[1], out var ry)
|
||||||
return new WPos(rx, ry, rz);
|
&& WDist.TryParse(parts[2], out var rz))
|
||||||
|
return new WPos(rx, ry, rz);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -248,7 +249,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
|
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 new WAngle(res);
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
}
|
}
|
||||||
@@ -258,11 +259,13 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma);
|
var parts = value.Split(SplitComma);
|
||||||
if (parts.Length == 3
|
if (parts.Length == 3)
|
||||||
&& Exts.TryParseInt32Invariant(parts[0], out var rr)
|
{
|
||||||
&& Exts.TryParseInt32Invariant(parts[1], out var rp)
|
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
|
||||||
&& Exts.TryParseInt32Invariant(parts[2], out var ry))
|
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
|
||||||
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
|
||||||
|
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -275,33 +278,10 @@ namespace OpenRA
|
|||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length == 3)
|
if (parts.Length == 3)
|
||||||
return new CPos(
|
return new CPos(
|
||||||
Exts.ParseInt32Invariant(parts[0]),
|
Exts.ParseIntegerInvariant(parts[0]),
|
||||||
Exts.ParseInt32Invariant(parts[1]),
|
Exts.ParseIntegerInvariant(parts[1]),
|
||||||
Exts.ParseByteInvariant(parts[2]));
|
Exts.ParseByte(parts[2]));
|
||||||
return new CPos(Exts.ParseInt32Invariant(parts[0]), Exts.ParseInt32Invariant(parts[1]));
|
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -312,7 +292,7 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
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);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -405,7 +385,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
var ints = new int2[parts.Length / 2];
|
var ints = new int2[parts.Length / 2];
|
||||||
for (var i = 0; i < ints.Length; i++)
|
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;
|
return ints;
|
||||||
}
|
}
|
||||||
@@ -418,7 +398,7 @@ namespace OpenRA
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
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);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -432,7 +412,7 @@ namespace OpenRA
|
|||||||
if (parts.Length != 2)
|
if (parts.Length != 2)
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
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);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -480,10 +460,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return new Rectangle(
|
return new Rectangle(
|
||||||
Exts.ParseInt32Invariant(parts[0]),
|
Exts.ParseIntegerInvariant(parts[0]),
|
||||||
Exts.ParseInt32Invariant(parts[1]),
|
Exts.ParseIntegerInvariant(parts[1]),
|
||||||
Exts.ParseInt32Invariant(parts[2]),
|
Exts.ParseIntegerInvariant(parts[2]),
|
||||||
Exts.ParseInt32Invariant(parts[3]));
|
Exts.ParseIntegerInvariant(parts[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return InvalidValueAction(value, fieldType, fieldName);
|
return InvalidValueAction(value, fieldType, fieldName);
|
||||||
@@ -520,7 +500,7 @@ namespace OpenRA
|
|||||||
if (yaml == null)
|
if (yaml == null)
|
||||||
return Activator.CreateInstance(fieldType);
|
return Activator.CreateInstance(fieldType);
|
||||||
|
|
||||||
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Length);
|
var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Count);
|
||||||
var arguments = fieldType.GetGenericArguments();
|
var arguments = fieldType.GetGenericArguments();
|
||||||
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
var addMethod = fieldType.GetMethod(nameof(Dictionary<object, object>.Add), arguments);
|
||||||
var addArgs = new object[2];
|
var addArgs = new object[2];
|
||||||
@@ -551,7 +531,7 @@ namespace OpenRA
|
|||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var innerType = fieldType.GetGenericArguments()[0];
|
var innerType = fieldType.GetGenericArguments().First();
|
||||||
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
var innerValue = GetValue("Nullable<T>", innerType, value, field);
|
||||||
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using System.ComponentModel;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -59,7 +58,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
return new MiniYaml(
|
return new MiniYaml(
|
||||||
null,
|
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)
|
public static MiniYamlNode SaveField(object o, string field)
|
||||||
@@ -85,7 +84,7 @@ namespace OpenRA
|
|||||||
// This is only for documentation generation
|
// This is only for documentation generation
|
||||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
{
|
{
|
||||||
var result = new StringBuilder();
|
var result = "";
|
||||||
var dict = (System.Collections.IDictionary)v;
|
var dict = (System.Collections.IDictionary)v;
|
||||||
foreach (var kvp in dict)
|
foreach (var kvp in dict)
|
||||||
{
|
{
|
||||||
@@ -95,10 +94,10 @@ namespace OpenRA
|
|||||||
var formattedKey = FormatValue(key);
|
var formattedKey = FormatValue(key);
|
||||||
var formattedValue = FormatValue(value);
|
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)
|
if (v is DateTime d)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using ICSharpCode.SharpZipLib.Checksum;
|
using ICSharpCode.SharpZipLib.Checksum;
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -46,13 +45,12 @@ namespace OpenRA.FileFormats
|
|||||||
var data = new List<byte>();
|
var data = new List<byte>();
|
||||||
Type = SpriteFrameType.Rgba32;
|
Type = SpriteFrameType.Rgba32;
|
||||||
|
|
||||||
byte bitDepth = 8;
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||||
var type = s.ReadASCII(4);
|
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
||||||
var content = s.ReadBytes(length);
|
var content = s.ReadBytes(length);
|
||||||
s.ReadInt32(); // crc
|
/*var crc = */s.ReadInt32();
|
||||||
|
|
||||||
if (!headerParsed && type != "IHDR")
|
if (!headerParsed && type != "IHDR")
|
||||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||||
@@ -68,7 +66,7 @@ namespace OpenRA.FileFormats
|
|||||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||||
|
|
||||||
bitDepth = ms.ReadUInt8();
|
var bitDepth = ms.ReadUInt8();
|
||||||
var colorType = (PngColorType)ms.ReadUInt8();
|
var colorType = (PngColorType)ms.ReadUInt8();
|
||||||
if (IsPaletted(bitDepth, colorType))
|
if (IsPaletted(bitDepth, colorType))
|
||||||
Type = SpriteFrameType.Indexed8;
|
Type = SpriteFrameType.Indexed8;
|
||||||
@@ -78,7 +76,7 @@ namespace OpenRA.FileFormats
|
|||||||
Data = new byte[Width * Height * PixelStride];
|
Data = new byte[Width * Height * PixelStride];
|
||||||
|
|
||||||
var compression = ms.ReadUInt8();
|
var compression = ms.ReadUInt8();
|
||||||
ms.ReadUInt8(); // filter
|
/*var filter = */ms.ReadUInt8();
|
||||||
var interlace = ms.ReadUInt8();
|
var interlace = ms.ReadUInt8();
|
||||||
|
|
||||||
if (compression != 0)
|
if (compression != 0)
|
||||||
@@ -94,8 +92,8 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
case "PLTE":
|
case "PLTE":
|
||||||
{
|
{
|
||||||
Palette = new Color[length / 3];
|
Palette = new Color[256];
|
||||||
for (var i = 0; i < Palette.Length; i++)
|
for (var i = 0; i < length / 3; i++)
|
||||||
{
|
{
|
||||||
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
var r = ms.ReadUInt8(); var g = ms.ReadUInt8(); var b = ms.ReadUInt8();
|
||||||
Palette[i] = Color.FromArgb(r, g, b);
|
Palette[i] = Color.FromArgb(r, g, b);
|
||||||
@@ -138,34 +136,14 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
var pxStride = PixelStride;
|
var pxStride = PixelStride;
|
||||||
var rowStride = Width * pxStride;
|
var rowStride = Width * pxStride;
|
||||||
var pixelsPerByte = 8 / bitDepth;
|
|
||||||
var sourceRowStride = Exts.IntegerDivisionRoundingAwayFromZero(rowStride, pixelsPerByte);
|
|
||||||
|
|
||||||
Span<byte> prevLine = new byte[rowStride];
|
Span<byte> prevLine = new byte[rowStride];
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
var filter = (PngFilter)ds.ReadUInt8();
|
var filter = (PngFilter)ds.ReadUInt8();
|
||||||
ds.ReadBytes(Data, y * rowStride, sourceRowStride);
|
ds.ReadBytes(Data, y * rowStride, rowStride);
|
||||||
var line = Data.AsSpan(y * rowStride, rowStride);
|
var line = Data.AsSpan(y * rowStride, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (filter)
|
switch (filter)
|
||||||
{
|
{
|
||||||
case PngFilter.None:
|
case PngFilter.None:
|
||||||
@@ -291,7 +269,7 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
static bool IsPaletted(byte bitDepth, PngColorType colorType)
|
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;
|
return true;
|
||||||
|
|
||||||
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
if (bitDepth == 8 && colorType == (PngColorType.Color | PngColorType.Alpha))
|
||||||
@@ -309,10 +287,10 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
var typeBytes = Encoding.ASCII.GetBytes(type);
|
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||||
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
output.Write(IPAddress.HostToNetworkOrder((int)input.Length));
|
||||||
output.Write(typeBytes);
|
output.WriteArray(typeBytes);
|
||||||
|
|
||||||
var data = input.ReadAllBytes();
|
var data = input.ReadAllBytes();
|
||||||
output.Write(data);
|
output.WriteArray(data);
|
||||||
|
|
||||||
var crc32 = new Crc32();
|
var crc32 = new Crc32();
|
||||||
crc32.Update(typeBytes);
|
crc32.Update(typeBytes);
|
||||||
@@ -324,7 +302,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
using (var output = new MemoryStream())
|
using (var output = new MemoryStream())
|
||||||
{
|
{
|
||||||
output.Write(Signature);
|
output.WriteArray(Signature);
|
||||||
using (var header = new MemoryStream())
|
using (var header = new MemoryStream())
|
||||||
{
|
{
|
||||||
header.Write(IPAddress.HostToNetworkOrder(Width));
|
header.Write(IPAddress.HostToNetworkOrder(Width));
|
||||||
@@ -372,14 +350,13 @@ namespace OpenRA.FileFormats
|
|||||||
|
|
||||||
using (var data = new MemoryStream())
|
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;
|
var rowStride = Width * PixelStride;
|
||||||
for (var y = 0; y < Height; y++)
|
for (var y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
// Assuming no filtering for simplicity
|
// Write uncompressed scanlines for simplicity
|
||||||
const byte FilterType = 0;
|
compressed.WriteByte(0);
|
||||||
compressed.WriteByte(FilterType);
|
|
||||||
compressed.Write(Data, y * rowStride, rowStride);
|
compressed.Write(Data, y * rowStride, rowStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +371,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
using (var text = new MemoryStream())
|
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);
|
WritePngChunk(output, "tEXt", text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ namespace OpenRA.FileFormats
|
|||||||
throw new NotSupportedException($"Metadata version {version} is not supported");
|
throw new NotSupportedException($"Metadata version {version} is not supported");
|
||||||
|
|
||||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||||
var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100);
|
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||||
GameInfo = GameInformation.Deserialize(data, path);
|
GameInfo = GameInformation.Deserialize(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(BinaryWriter writer)
|
public void Write(BinaryWriter writer)
|
||||||
@@ -62,7 +62,7 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
// Write lobby info data
|
// Write lobby info data
|
||||||
writer.Flush();
|
writer.Flush();
|
||||||
dataLength += writer.BaseStream.WriteLengthPrefixedString(Encoding.UTF8, GameInfo.Serialize());
|
dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write total length & end marker
|
// Write total length & end marker
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace OpenRA.FileSystem
|
|||||||
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
bool TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename);
|
||||||
bool TryOpen(string filename, out Stream s);
|
bool TryOpen(string filename, out Stream s);
|
||||||
bool Exists(string filename);
|
bool Exists(string filename);
|
||||||
bool IsExternalFile(string filename);
|
bool IsExternalModFile(string filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileSystem : IReadOnlyFileSystem
|
public class FileSystem : IReadOnlyFileSystem
|
||||||
@@ -83,14 +83,14 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
public void Mount(string name, string explicitName = null)
|
public void Mount(string name, string explicitName = null)
|
||||||
{
|
{
|
||||||
var optional = name.StartsWith('~');
|
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IReadOnlyPackage package;
|
IReadOnlyPackage package;
|
||||||
if (name.StartsWith('$'))
|
if (name.StartsWith("$", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
@@ -109,8 +109,10 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
Mount(package, explicitName);
|
Mount(package, explicitName);
|
||||||
}
|
}
|
||||||
catch when (optional)
|
catch
|
||||||
{
|
{
|
||||||
|
if (!optional)
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +161,9 @@ namespace OpenRA.FileSystem
|
|||||||
explicitMounts.Remove(key);
|
explicitMounts.Remove(key);
|
||||||
|
|
||||||
// Mod packages aren't owned by us, so we shouldn't dispose them
|
// 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();
|
package.Dispose();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -181,13 +185,11 @@ namespace OpenRA.FileSystem
|
|||||||
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
fileIndex = new Cache<string, List<IReadOnlyPackage>>(_ => new List<IReadOnlyPackage>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TrimExcess()
|
public void LoadFromManifest(Manifest manifest)
|
||||||
{
|
{
|
||||||
mountedPackages.TrimExcess();
|
UnmountAll();
|
||||||
explicitMounts.TrimExcess();
|
foreach (var kv in manifest.Packages)
|
||||||
modPackages.TrimExcess();
|
Mount(kv.Key, kv.Value);
|
||||||
foreach (var packages in fileIndex.Values)
|
|
||||||
packages.TrimExcess();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream GetFromCache(string filename)
|
Stream GetFromCache(string filename)
|
||||||
@@ -224,11 +226,14 @@ namespace OpenRA.FileSystem
|
|||||||
public bool TryOpen(string filename, out Stream s)
|
public bool TryOpen(string filename, out Stream s)
|
||||||
{
|
{
|
||||||
var explicitSplit = filename.IndexOf('|');
|
var explicitSplit = filename.IndexOf('|');
|
||||||
if (explicitSplit > 0 && explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
if (explicitSplit > 0)
|
||||||
{
|
{
|
||||||
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||||
if (s != null)
|
{
|
||||||
return true;
|
s = explicitPackage.GetStream(filename[(explicitSplit + 1)..]);
|
||||||
|
if (s != null)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s = GetFromCache(filename);
|
s = GetFromCache(filename);
|
||||||
@@ -257,20 +262,64 @@ namespace OpenRA.FileSystem
|
|||||||
public bool Exists(string filename)
|
public bool Exists(string filename)
|
||||||
{
|
{
|
||||||
var explicitSplit = filename.IndexOf('|');
|
var explicitSplit = filename.IndexOf('|');
|
||||||
if (explicitSplit > 0 &&
|
if (explicitSplit > 0)
|
||||||
explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage) &&
|
if (explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||||
explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
if (explicitPackage.Contains(filename[(explicitSplit + 1)..]))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return fileIndex.ContainsKey(filename);
|
return fileIndex.ContainsKey(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the given filename references any file outside the mod mount.
|
/// Returns true if the given filename references an external mod via an explicit mount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsExternalFile(string filename)
|
public bool IsExternalModFile(string filename)
|
||||||
{
|
{
|
||||||
return !filename.StartsWith($"{modID}|", StringComparison.Ordinal);
|
var explicitSplit = filename.IndexOf('|');
|
||||||
|
if (explicitSplit < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!explicitMounts.TryGetValue(filename[..explicitSplit], out var explicitPackage))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (installedMods[modID].Package == explicitPackage)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return modPackages.Contains(explicitPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a filesystem for an assembly, accounting for explicit and mod mounts.
|
||||||
|
/// Assemblies must exist in the native OS file system (not inside an OpenRA-defined package).
|
||||||
|
/// </summary>
|
||||||
|
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
|
||||||
|
{
|
||||||
|
var explicitSplit = path.IndexOf('|');
|
||||||
|
if (explicitSplit > 0 && !path.StartsWith("^"))
|
||||||
|
{
|
||||||
|
var parent = path[..explicitSplit];
|
||||||
|
var filename = path[(explicitSplit + 1)..];
|
||||||
|
|
||||||
|
var parentPath = manifest.Packages.FirstOrDefault(kv => kv.Value == parent).Key;
|
||||||
|
if (parentPath == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (parentPath.StartsWith("$", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
if (!installedMods.TryGetValue(parentPath[1..], out var mod))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (mod.Package is not Folder)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
path = Path.Combine(mod.Package.Name, filename);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
path = Path.Combine(parentPath, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedPath = Platform.ResolvePath(path);
|
||||||
|
return File.Exists(resolvedPath) ? resolvedPath : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ResolveCaseInsensitivePath(string path)
|
public static string ResolveCaseInsensitivePath(string path)
|
||||||
@@ -286,8 +335,7 @@ namespace OpenRA.FileSystem
|
|||||||
if (name == ".")
|
if (name == ".")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
resolved = Directory.GetFileSystemEntries(resolved)
|
resolved = Directory.GetFileSystemEntries(resolved).FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
||||||
.FirstOrDefault(e => e.Equals(Path.Combine(resolved, name), StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
|
|
||||||
if (resolved == null)
|
if (resolved == null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace OpenRA.FileSystem
|
|||||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||||
// full parent path too. We need to be careful to not add a second path
|
// full parent path too. We need to be careful to not add a second path
|
||||||
// prefix to these hacked packages.
|
// prefix to these hacked packages.
|
||||||
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
||||||
using (var s = File.Create(filePath))
|
using (var s = File.Create(filePath))
|
||||||
@@ -98,7 +98,7 @@ namespace OpenRA.FileSystem
|
|||||||
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
// in FileSystem.OpenPackage. Their internal name therefore contains the
|
||||||
// full parent path too. We need to be careful to not add a second path
|
// full parent path too. We need to be careful to not add a second path
|
||||||
// prefix to these hacked packages.
|
// prefix to these hacked packages.
|
||||||
var filePath = filename.StartsWith(Name, StringComparison.Ordinal) ? filename : Path.Combine(Name, filename);
|
var filePath = filename.StartsWith(Name) ? filename : Path.Combine(Name, filename);
|
||||||
if (Directory.Exists(filePath))
|
if (Directory.Exists(filePath))
|
||||||
Directory.Delete(filePath, true);
|
Directory.Delete(filePath, true);
|
||||||
else if (File.Exists(filePath))
|
else if (File.Exists(filePath))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace OpenRA.FileSystem
|
|||||||
{
|
{
|
||||||
const uint ZipSignature = 0x04034b50;
|
const uint ZipSignature = 0x04034b50;
|
||||||
|
|
||||||
public class ReadOnlyZipFile : IReadOnlyPackage
|
class ReadOnlyZipFile : IReadOnlyPackage
|
||||||
{
|
{
|
||||||
public string Name { get; protected set; }
|
public string Name { get; protected set; }
|
||||||
protected ZipFile pkg;
|
protected ZipFile pkg;
|
||||||
@@ -68,7 +68,6 @@ namespace OpenRA.FileSystem
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
pkg?.Close();
|
pkg?.Close();
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
|
||||||
@@ -94,7 +93,7 @@ namespace OpenRA.FileSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
sealed class ReadWriteZipFile : ReadOnlyZipFile, IReadWritePackage
|
||||||
{
|
{
|
||||||
readonly MemoryStream pkgStream = new();
|
readonly MemoryStream pkgStream = new();
|
||||||
|
|
||||||
@@ -114,15 +113,14 @@ namespace OpenRA.FileSystem
|
|||||||
pkgStream.Position = 0;
|
pkgStream.Position = 0;
|
||||||
pkg = new ZipFile(pkgStream);
|
pkg = new ZipFile(pkgStream);
|
||||||
Name = filename;
|
Name = filename;
|
||||||
|
|
||||||
// Remove subfields that can break ZIP updating.
|
|
||||||
foreach (ZipEntry entry in pkg)
|
|
||||||
entry.ExtraData = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Commit()
|
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)
|
public void Update(string filename, byte[] contents)
|
||||||
@@ -149,7 +147,7 @@ namespace OpenRA.FileSystem
|
|||||||
|
|
||||||
public ZipFolder(ReadOnlyZipFile parent, string path)
|
public ZipFolder(ReadOnlyZipFile parent, string path)
|
||||||
{
|
{
|
||||||
if (path.EndsWith('/'))
|
if (path.EndsWith("/", StringComparison.Ordinal))
|
||||||
path = path[..^1];
|
path = path[..^1];
|
||||||
|
|
||||||
Name = path;
|
Name = path;
|
||||||
|
|||||||
@@ -1,167 +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.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using Linguini.Bundle;
|
|
||||||
using Linguini.Bundle.Builder;
|
|
||||||
using Linguini.Shared.Types.Bundle;
|
|
||||||
using Linguini.Syntax.Parser;
|
|
||||||
using Linguini.Syntax.Parser.Error;
|
|
||||||
using OpenRA.FileSystem;
|
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
|
||||||
public sealed class FluentReferenceAttribute : Attribute
|
|
||||||
{
|
|
||||||
public readonly bool Optional;
|
|
||||||
public readonly string[] RequiredVariableNames;
|
|
||||||
public readonly LintDictionaryReference DictionaryReference;
|
|
||||||
|
|
||||||
public FluentReferenceAttribute() { }
|
|
||||||
|
|
||||||
public FluentReferenceAttribute(params string[] requiredVariableNames)
|
|
||||||
{
|
|
||||||
RequiredVariableNames = requiredVariableNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FluentReferenceAttribute(LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
|
|
||||||
{
|
|
||||||
DictionaryReference = dictionaryReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FluentReferenceAttribute(bool optional)
|
|
||||||
{
|
|
||||||
Optional = optional;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FluentBundle
|
|
||||||
{
|
|
||||||
readonly Linguini.Bundle.FluentBundle bundle;
|
|
||||||
|
|
||||||
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem)
|
|
||||||
: this(culture, paths, fileSystem, error => Log.Write("debug", error.Message)) { }
|
|
||||||
|
|
||||||
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text)
|
|
||||||
: this(culture, paths, fileSystem, text, error => Log.Write("debug", error.Message)) { }
|
|
||||||
|
|
||||||
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, Action<ParseError> onError)
|
|
||||||
: this(culture, paths, fileSystem, null, onError) { }
|
|
||||||
|
|
||||||
public FluentBundle(string culture, string text, Action<ParseError> onError)
|
|
||||||
: this(culture, null, null, text, onError) { }
|
|
||||||
|
|
||||||
public FluentBundle(string culture, string[] paths, IReadOnlyFileSystem fileSystem, string text, Action<ParseError> onError)
|
|
||||||
{
|
|
||||||
bundle = LinguiniBuilder.Builder()
|
|
||||||
.CultureInfo(new CultureInfo(culture))
|
|
||||||
.SkipResources()
|
|
||||||
.SetUseIsolating(false)
|
|
||||||
.UseConcurrent()
|
|
||||||
.UncheckedBuild();
|
|
||||||
|
|
||||||
if (paths != null)
|
|
||||||
{
|
|
||||||
foreach (var path in paths)
|
|
||||||
{
|
|
||||||
var stream = fileSystem.Open(path);
|
|
||||||
using (var reader = new StreamReader(stream))
|
|
||||||
{
|
|
||||||
var parser = new LinguiniParser(reader);
|
|
||||||
var resource = parser.Parse();
|
|
||||||
foreach (var error in resource.Errors)
|
|
||||||
onError(error);
|
|
||||||
|
|
||||||
bundle.AddResourceOverriding(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
var parser = new LinguiniParser(text);
|
|
||||||
var resource = parser.Parse();
|
|
||||||
foreach (var error in resource.Errors)
|
|
||||||
onError(error);
|
|
||||||
|
|
||||||
bundle.AddResourceOverriding(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetMessage(string key, object[] args = null)
|
|
||||||
{
|
|
||||||
if (!TryGetMessage(key, out var message, args))
|
|
||||||
message = key;
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetMessage(string key, out string value, object[] args = null)
|
|
||||||
{
|
|
||||||
if (key == null)
|
|
||||||
throw new ArgumentNullException(nameof(key));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!HasMessage(key))
|
|
||||||
{
|
|
||||||
value = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<string, IFluentType> fluentArgs = null;
|
|
||||||
if (args != null)
|
|
||||||
{
|
|
||||||
if (args.Length % 2 != 0)
|
|
||||||
throw new ArgumentException("Expected a comma separated list of name, value arguments " +
|
|
||||||
"but the number of arguments is not a multiple of two", nameof(args));
|
|
||||||
|
|
||||||
fluentArgs = new Dictionary<string, IFluentType>();
|
|
||||||
for (var i = 0; i < args.Length; i += 2)
|
|
||||||
{
|
|
||||||
var argKey = args[i] as string;
|
|
||||||
if (string.IsNullOrEmpty(argKey))
|
|
||||||
throw new ArgumentException($"Expected the argument at index {i} to be a non-empty string", nameof(args));
|
|
||||||
|
|
||||||
var argValue = args[i + 1];
|
|
||||||
if (argValue == null)
|
|
||||||
throw new ArgumentNullException(nameof(args), $"Expected the argument at index {i + 1} to be a non-null value");
|
|
||||||
|
|
||||||
fluentArgs.Add(argKey, argValue.ToFluentType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = bundle.TryGetAttrMessage(key, fluentArgs, out var errors, out value);
|
|
||||||
foreach (var error in errors)
|
|
||||||
Log.Write("debug", $"FluentBundle of {key}: {error}");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Log.Write("debug", $"FluentBundle of {key}: threw exception");
|
|
||||||
|
|
||||||
value = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasMessage(string key)
|
|
||||||
{
|
|
||||||
return bundle.HasAttrMessage(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +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.Text;
|
|
||||||
using OpenRA.FileSystem;
|
|
||||||
|
|
||||||
namespace OpenRA
|
|
||||||
{
|
|
||||||
public static class FluentProvider
|
|
||||||
{
|
|
||||||
// Ensure thread-safety.
|
|
||||||
static readonly object SyncObject = new();
|
|
||||||
static FluentBundle modFluentBundle;
|
|
||||||
static FluentBundle mapFluentBundle;
|
|
||||||
|
|
||||||
public static void Initialize(ModData modData, IReadOnlyFileSystem fileSystem)
|
|
||||||
{
|
|
||||||
lock (SyncObject)
|
|
||||||
{
|
|
||||||
modFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, modData.Manifest.FluentMessages, fileSystem);
|
|
||||||
if (fileSystem is Map map && map.FluentMessageDefinitions != null)
|
|
||||||
{
|
|
||||||
var files = Array.Empty<string>();
|
|
||||||
if (map.FluentMessageDefinitions.Value != null)
|
|
||||||
files = FieldLoader.GetValue<string[]>("value", map.FluentMessageDefinitions.Value);
|
|
||||||
|
|
||||||
string text = null;
|
|
||||||
if (map.FluentMessageDefinitions.Nodes.Length > 0)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
foreach (var node in map.FluentMessageDefinitions.Nodes)
|
|
||||||
if (node.Key == "base64")
|
|
||||||
builder.Append(Encoding.UTF8.GetString(Convert.FromBase64String(node.Value.Value)));
|
|
||||||
|
|
||||||
text = builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
mapFluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetMessage(string key, params object[] args)
|
|
||||||
{
|
|
||||||
lock (SyncObject)
|
|
||||||
{
|
|
||||||
// By prioritizing mod-level fluent bundles we prevent maps from overwriting string keys. We do not want to
|
|
||||||
// allow maps to change the UI nor any other strings not exposed to the map.
|
|
||||||
if (modFluentBundle.TryGetMessage(key, out var message, args))
|
|
||||||
return message;
|
|
||||||
|
|
||||||
if (mapFluentBundle != null)
|
|
||||||
return mapFluentBundle.GetMessage(key, args);
|
|
||||||
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryGetMessage(string key, out string message, params object[] args)
|
|
||||||
{
|
|
||||||
lock (SyncObject)
|
|
||||||
{
|
|
||||||
// By prioritizing mod-level bundle we prevent maps from overwriting string keys. We do not want to
|
|
||||||
// allow maps to change the UI nor any other strings not exposed to the map.
|
|
||||||
if (modFluentBundle.TryGetMessage(key, out message, args))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mapFluentBundle != null && mapFluentBundle.TryGetMessage(key, out message, args))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Should only be used by <see cref="MapPreview"/>.</summary>
|
|
||||||
internal static bool TryGetModMessage(string key, out string message, params object[] args)
|
|
||||||
{
|
|
||||||
lock (SyncObject)
|
|
||||||
{
|
|
||||||
return modFluentBundle.TryGetMessage(key, out message, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,7 +29,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class Game
|
public static class Game
|
||||||
{
|
{
|
||||||
[FluentReference("filename")]
|
[TranslationReference("filename")]
|
||||||
const string SavedScreenshot = "notification-saved-screenshot";
|
const string SavedScreenshot = "notification-saved-screenshot";
|
||||||
|
|
||||||
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
|
||||||
@@ -180,7 +180,6 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static event Action BeforeGameStart = () => { };
|
public static event Action BeforeGameStart = () => { };
|
||||||
public static event Action AfterGameStart = () => { };
|
|
||||||
internal static void StartGame(string mapUID, WorldType type)
|
internal static void StartGame(string mapUID, WorldType type)
|
||||||
{
|
{
|
||||||
// Dispose of the old world before creating a new one.
|
// Dispose of the old world before creating a new one.
|
||||||
@@ -224,12 +223,6 @@ namespace OpenRA
|
|||||||
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
// Much better to clean up now then to drop frames during gameplay for GC pauses.
|
||||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||||
GC.Collect();
|
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()
|
public static void RestartGame()
|
||||||
@@ -368,7 +361,22 @@ namespace OpenRA
|
|||||||
Settings.Game.Platform = p;
|
Settings.Game.Platform = p;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var platform = CreatePlatform(p);
|
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||||
|
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
var loader = new AssemblyLoader(rendererPath);
|
||||||
|
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
|
||||||
|
#else
|
||||||
|
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
|
||||||
|
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
|
||||||
|
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (platformType == null)
|
||||||
|
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
||||||
|
|
||||||
|
var platform = (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
||||||
Renderer = new Renderer(platform, Settings.Graphics);
|
Renderer = new Renderer(platform, Settings.Graphics);
|
||||||
Sound = new Sound(platform, Settings.Sound);
|
Sound = new Sound(platform, Settings.Sound);
|
||||||
|
|
||||||
@@ -395,7 +403,7 @@ namespace OpenRA
|
|||||||
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
||||||
Console.WriteLine("Internal mods:");
|
Console.WriteLine("Internal mods:");
|
||||||
foreach (var mod in Mods)
|
foreach (var mod in Mods)
|
||||||
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
|
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
|
||||||
|
|
||||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||||
|
|
||||||
@@ -408,7 +416,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Sanitize input from platform-specific launchers
|
// Sanitize input from platform-specific launchers
|
||||||
// Process.Start requires paths to not be quoted, even if they contain spaces
|
// Process.Start requires paths to not be quoted, even if they contain spaces
|
||||||
if (launchPath != null && launchPath[0] == '"' && launchPath.Last() == '"')
|
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
|
||||||
launchPath = launchPath[1..^1];
|
launchPath = launchPath[1..^1];
|
||||||
|
|
||||||
// Metadata registration requires an explicit launch path
|
// Metadata registration requires an explicit launch path
|
||||||
@@ -420,31 +428,11 @@ namespace OpenRA
|
|||||||
|
|
||||||
Console.WriteLine("External mods:");
|
Console.WriteLine("External mods:");
|
||||||
foreach (var mod in ExternalMods)
|
foreach (var mod in ExternalMods)
|
||||||
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
|
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
|
||||||
|
|
||||||
InitializeMod(modID, args);
|
InitializeMod(modID, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IPlatform CreatePlatform(string platformName)
|
|
||||||
{
|
|
||||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + platformName + ".dll");
|
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
|
||||||
var loader = new AssemblyLoader(rendererPath);
|
|
||||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
|
||||||
|
|
||||||
#else
|
|
||||||
// NOTE: This is currently the only use of System.Reflection in this file, so would give an unused using error if we import it above
|
|
||||||
var assembly = System.Reflection.Assembly.LoadFile(rendererPath);
|
|
||||||
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (platformType == null)
|
|
||||||
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
|
|
||||||
|
|
||||||
return (IPlatform)platformType.GetConstructor(Type.EmptyTypes).Invoke(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InitializeMod(string mod, Arguments args)
|
public static void InitializeMod(string mod, Arguments args)
|
||||||
{
|
{
|
||||||
// Clear static state if we have switched mods
|
// Clear static state if we have switched mods
|
||||||
@@ -496,11 +484,11 @@ namespace OpenRA
|
|||||||
Renderer.InitializeDepthBuffer(grid);
|
Renderer.InitializeDepthBuffer(grid);
|
||||||
|
|
||||||
Cursor?.Dispose();
|
Cursor?.Dispose();
|
||||||
Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize);
|
Cursor = new CursorManager(ModData.CursorProvider);
|
||||||
|
|
||||||
var metadata = ModData.Manifest.Metadata;
|
var metadata = ModData.Manifest.Metadata;
|
||||||
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
|
if (!string.IsNullOrEmpty(metadata.WindowTitle))
|
||||||
Renderer.Window.SetWindowTitle(metadata.WindowTitleTranslated);
|
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
|
||||||
|
|
||||||
PerfHistory.Items["render"].HasNormalTick = false;
|
PerfHistory.Items["render"].HasNormalTick = false;
|
||||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||||
@@ -536,11 +524,10 @@ namespace OpenRA
|
|||||||
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
|
.Where(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Shellmap))
|
||||||
.Select(m => m.Uid);
|
.Select(m => m.Uid);
|
||||||
|
|
||||||
var shellmap = shellmaps.RandomOrDefault(CosmeticRandom);
|
if (!shellmaps.Any())
|
||||||
if (shellmap == null)
|
|
||||||
throw new InvalidDataException("No valid shellmaps available");
|
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)
|
public static void SwitchToExternalMod(ExternalMod mod, string[] launchArguments = null, Action onFailed = null)
|
||||||
@@ -595,7 +582,7 @@ namespace OpenRA
|
|||||||
Log.Write("debug", "Taking screenshot " + path);
|
Log.Write("debug", "Taking screenshot " + path);
|
||||||
|
|
||||||
Renderer.SaveScreenshot(path);
|
Renderer.SaveScreenshot(path);
|
||||||
TextNotificationsManager.Debug(FluentProvider.GetMessage(SavedScreenshot, "filename", filename));
|
TextNotificationsManager.Debug(TranslationProvider.GetString(SavedScreenshot, Translation.Arguments("filename", filename)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,10 +601,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
if (orderManager.LastTickTime.ShouldAdvance(tick))
|
||||||
{
|
{
|
||||||
if (orderManager.GameStarted && orderManager.LocalFrameNumber == 0)
|
using (new PerfSample("tick_time"))
|
||||||
PerfHistory.Reset(); // Remove history that occurred whilst the new game was loading.
|
|
||||||
|
|
||||||
using (var sample = new PerfSample("tick_time"))
|
|
||||||
{
|
{
|
||||||
orderManager.LastTickTime.AdvanceTickTime(tick);
|
orderManager.LastTickTime.AdvanceTickTime(tick);
|
||||||
|
|
||||||
@@ -626,11 +610,7 @@ namespace OpenRA
|
|||||||
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
Sync.RunUnsynced(world, orderManager.TickImmediate);
|
||||||
|
|
||||||
if (world == null)
|
if (world == null)
|
||||||
{
|
|
||||||
if (orderManager.GameStarted)
|
|
||||||
PerfHistory.Reset(); // Remove old history when a new game starts.
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (orderManager.TryTick())
|
if (orderManager.TryTick())
|
||||||
{
|
{
|
||||||
@@ -684,7 +664,7 @@ namespace OpenRA
|
|||||||
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
// Prepare renderables (i.e. render voxels) before calling BeginFrame
|
||||||
using (new PerfSample("render_prepare"))
|
using (new PerfSample("render_prepare"))
|
||||||
{
|
{
|
||||||
worldRenderer?.BeginFrame();
|
Renderer.WorldModelRenderer.BeginFrame();
|
||||||
|
|
||||||
// World rendering is disabled while the loading screen is displayed
|
// World rendering is disabled while the loading screen is displayed
|
||||||
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
if (worldRenderer != null && !worldRenderer.World.IsLoadingGameSave)
|
||||||
@@ -694,7 +674,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ui.PrepareRenderables();
|
Ui.PrepareRenderables();
|
||||||
worldRenderer?.EndFrame();
|
Renderer.WorldModelRenderer.EndFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// worldRenderer is null during the initial install/download screen
|
// worldRenderer is null during the initial install/download screen
|
||||||
@@ -798,7 +778,7 @@ namespace OpenRA
|
|||||||
var logicWorld = worldRenderer?.World;
|
var logicWorld = worldRenderer?.World;
|
||||||
|
|
||||||
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
|
// 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;
|
logicInterval = logicWorld == OrderManager.World ? OrderManager.SuggestedTimestep : logicWorld.Timestep;
|
||||||
|
|
||||||
// Ideal time between screen updates
|
// Ideal time between screen updates
|
||||||
@@ -933,15 +913,15 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var endpoints = new List<IPEndPoint>
|
var endpoints = new List<IPEndPoint>
|
||||||
{
|
{
|
||||||
new(IPAddress.IPv6Any, settings.ListenPort),
|
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
|
||||||
new(IPAddress.Any, settings.ListenPort)
|
new IPEndPoint(IPAddress.Any, settings.ListenPort)
|
||||||
};
|
};
|
||||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
|
||||||
|
|
||||||
return server.GetEndpointForLocalConnection();
|
return server.GetEndpointForLocalConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConnectionTarget CreateLocalServer(string map, bool isSkirmish = false)
|
public static ConnectionTarget CreateLocalServer(string map)
|
||||||
{
|
{
|
||||||
var settings = new ServerSettings()
|
var settings = new ServerSettings()
|
||||||
{
|
{
|
||||||
@@ -955,9 +935,9 @@ namespace OpenRA
|
|||||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||||
var endpoints = new List<IPEndPoint>
|
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();
|
return server.GetEndpointForLocalConnection();
|
||||||
}
|
}
|
||||||
@@ -985,7 +965,7 @@ namespace OpenRA
|
|||||||
Order.Command($"state {Session.ClientState.Ready}")
|
Order.Command($"state {Session.ClientState.Ready}")
|
||||||
};
|
};
|
||||||
|
|
||||||
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.PackageName) == launchMap);
|
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Package.Name) == launchMap);
|
||||||
if (map == null)
|
if (map == null)
|
||||||
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
throw new ArgumentException($"Could not find map '{launchMap}'.");
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public class GameInformation
|
public class GameInformation
|
||||||
{
|
{
|
||||||
[FluentReference("name", "number")]
|
|
||||||
const string EnumeratedBotName = "enumerated-bot-name";
|
|
||||||
|
|
||||||
public string Mod;
|
public string Mod;
|
||||||
public string Version;
|
public string Version;
|
||||||
|
|
||||||
@@ -52,13 +49,13 @@ namespace OpenRA
|
|||||||
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameInformation Deserialize(string data, string path)
|
public static GameInformation Deserialize(string data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var info = new GameInformation();
|
var info = new GameInformation();
|
||||||
|
|
||||||
var nodes = MiniYaml.FromString(data, path);
|
var nodes = MiniYaml.FromString(data);
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var keyParts = node.Key.Split('@');
|
var keyParts = node.Key.Split('@');
|
||||||
@@ -88,7 +85,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var nodes = new List<MiniYamlNode>
|
var nodes = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new("Root", FieldSaver.Save(this))
|
new MiniYamlNode("Root", FieldSaver.Save(this))
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var i = 0; i < Players.Count; i++)
|
for (var i = 0; i < Players.Count; i++)
|
||||||
@@ -121,12 +118,11 @@ namespace OpenRA
|
|||||||
Name = runtimePlayer.PlayerName,
|
Name = runtimePlayer.PlayerName,
|
||||||
IsHuman = !runtimePlayer.IsBot,
|
IsHuman = !runtimePlayer.IsBot,
|
||||||
IsBot = runtimePlayer.IsBot,
|
IsBot = runtimePlayer.IsBot,
|
||||||
BotType = runtimePlayer.BotType,
|
|
||||||
FactionName = runtimePlayer.Faction.Name,
|
FactionName = runtimePlayer.Faction.Name,
|
||||||
FactionId = runtimePlayer.Faction.InternalName,
|
FactionId = runtimePlayer.Faction.InternalName,
|
||||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||||
Color = OpenRA.Player.GetColor(runtimePlayer),
|
Color = runtimePlayer.Color,
|
||||||
Team = client.Team,
|
Team = client.Team,
|
||||||
Handicap = client.Handicap,
|
Handicap = client.Handicap,
|
||||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||||
@@ -147,19 +143,6 @@ namespace OpenRA
|
|||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ResolvedPlayerName(Player player)
|
|
||||||
{
|
|
||||||
if (player.IsBot)
|
|
||||||
{
|
|
||||||
var number = Players.Where(p => p.BotType == player.BotType).ToList().IndexOf(player) + 1;
|
|
||||||
return FluentProvider.GetMessage(EnumeratedBotName,
|
|
||||||
"name", FluentProvider.GetMessage(player.Name),
|
|
||||||
"number", number);
|
|
||||||
}
|
|
||||||
|
|
||||||
return player.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Player
|
public class Player
|
||||||
{
|
{
|
||||||
#region Start-up information
|
#region Start-up information
|
||||||
@@ -170,7 +153,6 @@ namespace OpenRA
|
|||||||
public string Name;
|
public string Name;
|
||||||
public bool IsHuman;
|
public bool IsHuman;
|
||||||
public bool IsBot;
|
public bool IsBot;
|
||||||
public string BotType;
|
|
||||||
|
|
||||||
/// <summary>The faction's display name.</summary>
|
/// <summary>The faction's display name.</summary>
|
||||||
public string FactionName;
|
public string FactionName;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace OpenRA
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActorInfo
|
public class ActorInfo
|
||||||
{
|
{
|
||||||
public const char AbstractActorPrefix = '^';
|
public const string AbstractActorPrefix = "^";
|
||||||
public const char TraitInstanceSeparator = '@';
|
public const char TraitInstanceSeparator = '@';
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -33,7 +33,7 @@ namespace OpenRA
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
readonly TypeDictionary traits = new();
|
readonly TypeDictionary traits = new();
|
||||||
TraitInfo[] constructOrderCache = null;
|
List<TraitInfo> constructOrderCache = null;
|
||||||
|
|
||||||
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
|
||||||
{
|
{
|
||||||
@@ -130,7 +130,6 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Continue resolving traits as long as possible.
|
// Continue resolving traits as long as possible.
|
||||||
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
// Each time we resolve some traits, this means dependencies for other traits may then be possible to satisfy in the next pass.
|
||||||
#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection
|
|
||||||
var readyToResolve = more.ToList();
|
var readyToResolve = more.ToList();
|
||||||
while (readyToResolve.Count != 0)
|
while (readyToResolve.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -139,7 +138,6 @@ namespace OpenRA
|
|||||||
readyToResolve.Clear();
|
readyToResolve.Clear();
|
||||||
readyToResolve.AddRange(more);
|
readyToResolve.AddRange(more);
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1851
|
|
||||||
|
|
||||||
if (unresolved.Count != 0)
|
if (unresolved.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -162,7 +160,7 @@ namespace OpenRA
|
|||||||
throw new YamlException(exceptionString);
|
throw new YamlException(exceptionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructOrderCache = resolved.Select(r => r.Trait).ToArray();
|
constructOrderCache = resolved.Select(r => r.Trait).ToList();
|
||||||
return constructOrderCache;
|
return constructOrderCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +185,7 @@ namespace OpenRA
|
|||||||
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
public bool HasTraitInfo<T>() where T : ITraitInfoInterface { return traits.Contains<T>(); }
|
||||||
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
public T TraitInfo<T>() where T : ITraitInfoInterface { return traits.Get<T>(); }
|
||||||
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
public T TraitInfoOrDefault<T>() where T : ITraitInfoInterface { return traits.GetOrDefault<T>(); }
|
||||||
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()
|
public BitSet<TargetableType> GetAllTargetTypes()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
|
||||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
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,
|
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
|
||||||
k => new WeaponInfo(k.Value));
|
k => new WeaponInfo(k.Value));
|
||||||
@@ -182,7 +182,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors,
|
||||||
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value),
|
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,
|
var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons,
|
||||||
k => new WeaponInfo(k.Value));
|
k => new WeaponInfo(k.Value));
|
||||||
@@ -226,10 +226,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
static bool AnyCustomYaml(MiniYaml yaml)
|
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)
|
foreach (var actorNode in actors)
|
||||||
{
|
{
|
||||||
@@ -260,18 +260,18 @@ namespace OpenRA
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Any trait overrides that aren't explicitly whitelisted are flagged
|
// Any trait overrides that aren't explicitly whitelisted are flagged
|
||||||
if (mapRules == null)
|
if (mapRules != null)
|
||||||
return false;
|
|
||||||
|
|
||||||
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (mapRules.Value != null)
|
|
||||||
{
|
{
|
||||||
var mapFiles = FieldLoader.GetValue<string[]>("value", mapRules.Value);
|
if (AnyFlaggedTraits(modData, mapRules.Nodes))
|
||||||
foreach (var f in mapFiles)
|
return true;
|
||||||
if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f), f)))
|
|
||||||
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;
|
return false;
|
||||||
|
|||||||
@@ -40,16 +40,16 @@ namespace OpenRA.GameRules
|
|||||||
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<string, SoundPool>();
|
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)
|
foreach (var t in classifiction.Value.Nodes)
|
||||||
{
|
{
|
||||||
var volumeModifier = 1f;
|
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)
|
if (volumeModifierNode != null)
|
||||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||||
|
|
||||||
var interruptType = SoundPool.DefaultInterruptType;
|
var 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)
|
if (interruptTypeNode != null)
|
||||||
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);
|
||||||
|
|
||||||
|
|||||||
@@ -139,14 +139,13 @@ namespace OpenRA.GameRules
|
|||||||
{
|
{
|
||||||
// Resolve any weapon-level yaml inheritance or removals
|
// Resolve any weapon-level yaml inheritance or removals
|
||||||
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
|
||||||
content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection<MiniYamlNode>[] { content.Nodes }));
|
content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
|
||||||
FieldLoader.Load(this, content);
|
FieldLoader.Load(this, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
static object LoadProjectile(MiniYaml yaml)
|
static object LoadProjectile(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value;
|
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
|
||||||
if (proj == null)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
|
||||||
@@ -160,7 +159,7 @@ namespace OpenRA.GameRules
|
|||||||
static object LoadWarheads(MiniYaml yaml)
|
static object LoadWarheads(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var retList = new List<IWarhead>();
|
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");
|
var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
|
|||||||
@@ -10,12 +10,13 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public class GameSpeed
|
public class GameSpeed
|
||||||
{
|
{
|
||||||
[FluentReference]
|
[TranslationReference]
|
||||||
[FieldLoader.Require]
|
[FieldLoader.Require]
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ namespace OpenRA
|
|||||||
static object LoadSpeeds(MiniYaml y)
|
static object LoadSpeeds(MiniYaml y)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<string, GameSpeed>();
|
var ret = new Dictionary<string, GameSpeed>();
|
||||||
var speedsNode = y.NodeWithKeyOrDefault("Speeds");
|
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
|
||||||
if (speedsNode == null)
|
if (speedsNode == null)
|
||||||
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ namespace OpenRA.Graphics
|
|||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public bool IsDecoration { get; set; }
|
public bool IsDecoration { get; set; }
|
||||||
|
|
||||||
readonly Map map;
|
|
||||||
readonly SequenceSet sequences;
|
readonly SequenceSet sequences;
|
||||||
readonly Func<WAngle> facingFunc;
|
readonly Func<WAngle> facingFunc;
|
||||||
readonly Func<bool> paused;
|
readonly Func<bool> paused;
|
||||||
@@ -44,7 +43,6 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||||
{
|
{
|
||||||
map = world.Map;
|
|
||||||
sequences = world.Map.Sequences;
|
sequences = world.Map.Sequences;
|
||||||
Name = name.ToLowerInvariant();
|
Name = name.ToLowerInvariant();
|
||||||
this.facingFunc = facingFunc;
|
this.facingFunc = facingFunc;
|
||||||
@@ -60,18 +58,13 @@ namespace OpenRA.Graphics
|
|||||||
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
|
||||||
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
|
||||||
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
|
var (image, rotation) = CurrentSequence.GetSpriteWithRotation(CurrentFrame, facingFunc());
|
||||||
var imageRenderable = new SpriteRenderable(
|
var imageRenderable = new SpriteRenderable(image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration,
|
||||||
image, pos, offset, CurrentSequence.ZOffset + zOffset, palette,
|
rotation);
|
||||||
CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration, rotation);
|
|
||||||
|
|
||||||
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
|
||||||
if (shadow != null)
|
if (shadow != null)
|
||||||
{
|
{
|
||||||
var height = map.DistanceAboveTerrain(pos).Length;
|
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
||||||
|
|
||||||
var shadowRenderable = new SpriteRenderable(
|
|
||||||
shadow, pos, offset - new WVec(0, 0, height), CurrentSequence.ShadowZOffset + zOffset + height, palette,
|
|
||||||
CurrentSequence.Scale, 1f, float3.Ones, tintModifiers,
|
|
||||||
true, rotation);
|
true, rotation);
|
||||||
return new IRenderable[] { shadowRenderable, imageRenderable };
|
return new IRenderable[] { shadowRenderable, imageRenderable };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,12 +77,11 @@ namespace OpenRA.Graphics
|
|||||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||||
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
|
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)
|
foreach (var c in chrome)
|
||||||
if (!c.Key.StartsWith('^'))
|
if (!c.Key.StartsWith("^", StringComparison.Ordinal))
|
||||||
LoadCollection(c.Key, c.Value);
|
LoadCollection(c.Key, c.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,18 +227,14 @@ namespace OpenRA.Graphics
|
|||||||
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
|
||||||
};
|
};
|
||||||
|
|
||||||
sprites = sides
|
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
|
||||||
.Select(x =>
|
|
||||||
ps.HasSide(x.PanelSides)
|
|
||||||
? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density)
|
|
||||||
: null)
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// PERF: We don't need to search for images if there are no definitions.
|
// 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.
|
// 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>();
|
return Array.Empty<Sprite>();
|
||||||
|
|
||||||
// Support manual definitions for unusual dialog layouts
|
// Support manual definitions for unusual dialog layouts
|
||||||
|
|||||||
@@ -11,12 +11,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public sealed class CursorManager : IDisposable
|
public sealed class CursorManager
|
||||||
{
|
{
|
||||||
sealed class Cursor
|
sealed class Cursor
|
||||||
{
|
{
|
||||||
@@ -39,16 +38,13 @@ namespace OpenRA.Graphics
|
|||||||
readonly bool hardwareCursorsDisabled = false;
|
readonly bool hardwareCursorsDisabled = false;
|
||||||
bool hardwareCursorsDoubled = false;
|
bool hardwareCursorsDoubled = false;
|
||||||
|
|
||||||
public CursorManager(CursorProvider cursorProvider, int cursorSheetSize)
|
public CursorManager(CursorProvider cursorProvider)
|
||||||
{
|
{
|
||||||
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
hardwareCursorsDisabled = Game.Settings.Graphics.DisableHardwareCursors;
|
||||||
|
|
||||||
graphicSettings = Game.Settings.Graphics;
|
graphicSettings = Game.Settings.Graphics;
|
||||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, cursorSheetSize);
|
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||||
|
foreach (var kv in cursorProvider.Cursors)
|
||||||
// Sort the cursors for better packing onto the sheet.
|
|
||||||
foreach (var kv in cursorProvider.Cursors
|
|
||||||
.OrderBy(kvp => kvp.Value.Frames.Max(f => f.Size.Height)))
|
|
||||||
{
|
{
|
||||||
var frames = kv.Value.Frames;
|
var frames = kv.Value.Frames;
|
||||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||||
@@ -95,6 +91,10 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
CreateOrUpdateHardwareCursors();
|
CreateOrUpdateHardwareCursors();
|
||||||
|
|
||||||
|
foreach (var s in sheetBuilder.AllSheets)
|
||||||
|
s.ReleaseBuffer();
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +128,6 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sheetBuilder.Current.ReleaseBuffer();
|
|
||||||
|
|
||||||
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
hardwareCursorsDoubled = graphicSettings.CursorDouble;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,10 +229,6 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
var width = frame.Size.Width;
|
var width = frame.Size.Width;
|
||||||
var height = frame.Size.Height;
|
var height = frame.Size.Height;
|
||||||
|
|
||||||
if (width == 0 || height == 0)
|
|
||||||
return Array.Empty<byte>();
|
|
||||||
|
|
||||||
var data = new byte[4 * width * height];
|
var data = new byte[4 * width * height];
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ namespace OpenRA.Graphics
|
|||||||
public CursorProvider(ModData modData)
|
public CursorProvider(ModData modData)
|
||||||
{
|
{
|
||||||
var fileSystem = modData.DefaultFileSystem;
|
var fileSystem = modData.DefaultFileSystem;
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
|
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
|
// Overwrite previous definitions if there are duplicates
|
||||||
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
|
||||||
@@ -36,14 +35,14 @@ namespace OpenRA.Graphics
|
|||||||
if (p.Palette != null)
|
if (p.Palette != null)
|
||||||
pals[p.Palette] = p;
|
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)
|
.Where(p => p != null)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
|
||||||
|
|
||||||
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
|
||||||
var cursors = new Dictionary<string, CursorSequence>();
|
var cursors = new Dictionary<string, CursorSequence>();
|
||||||
foreach (var s in cursorsYaml.Nodes)
|
foreach (var s in nodesDict["Cursors"].Nodes)
|
||||||
foreach (var sequence in s.Value.Nodes)
|
foreach (var sequence in s.Value.Nodes)
|
||||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
var d = info.ToDictionary();
|
var d = info.ToDictionary();
|
||||||
|
|
||||||
Start = Exts.ParseInt32Invariant(d["Start"].Value);
|
Start = Exts.ParseIntegerInvariant(d["Start"].Value);
|
||||||
Palette = palette;
|
Palette = palette;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ namespace OpenRA.Graphics
|
|||||||
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
(d.TryGetValue("End", out yaml) && yaml.Value == "*"))
|
||||||
Length = Frames.Length;
|
Length = Frames.Length;
|
||||||
else if (d.TryGetValue("Length", out yaml))
|
else if (d.TryGetValue("Length", out yaml))
|
||||||
Length = Exts.ParseInt32Invariant(yaml.Value);
|
Length = Exts.ParseIntegerInvariant(yaml.Value);
|
||||||
else if (d.TryGetValue("End", out yaml))
|
else if (d.TryGetValue("End", out yaml))
|
||||||
Length = Exts.ParseInt32Invariant(yaml.Value) - Start;
|
Length = Exts.ParseIntegerInvariant(yaml.Value) - Start;
|
||||||
else
|
else
|
||||||
Length = 1;
|
Length = 1;
|
||||||
|
|
||||||
@@ -54,13 +54,13 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
if (d.TryGetValue("X", out yaml))
|
if (d.TryGetValue("X", out yaml))
|
||||||
{
|
{
|
||||||
Exts.TryParseInt32Invariant(yaml.Value, out var x);
|
Exts.TryParseIntegerInvariant(yaml.Value, out var x);
|
||||||
Hotspot = Hotspot.WithX(x);
|
Hotspot = Hotspot.WithX(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.TryGetValue("Y", out yaml))
|
if (d.TryGetValue("Y", out yaml))
|
||||||
{
|
{
|
||||||
Exts.TryParseInt32Invariant(yaml.Value, out var y);
|
Exts.TryParseIntegerInvariant(yaml.Value, out var y);
|
||||||
Hotspot = Hotspot.WithY(y);
|
Hotspot = Hotspot.WithY(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,10 +85,7 @@ namespace OpenRA.Graphics
|
|||||||
public void ReplacePalette(string name, IPalette p)
|
public void ReplacePalette(string name, IPalette p)
|
||||||
{
|
{
|
||||||
if (mutablePalettes.ContainsKey(name))
|
if (mutablePalettes.ContainsKey(name))
|
||||||
{
|
|
||||||
palettes[name] = new ImmutablePalette(p);
|
|
||||||
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
|
||||||
}
|
|
||||||
else if (palettes.ContainsKey(name))
|
else if (palettes.ContainsKey(name))
|
||||||
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,9 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
@@ -29,14 +30,6 @@ namespace OpenRA.Graphics
|
|||||||
Rectangle AggregateBounds { get; }
|
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 struct ModelRenderData
|
||||||
{
|
{
|
||||||
public readonly int Start;
|
public readonly int Start;
|
||||||
@@ -51,13 +44,51 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IModelCacheInfo : ITraitInfoInterface { }
|
public interface IModelCache : IDisposable
|
||||||
|
|
||||||
public interface IModelCache
|
|
||||||
{
|
{
|
||||||
IModel GetModel(string model);
|
IModel GetModel(string model);
|
||||||
IModel GetModelSequence(string model, string sequence);
|
IModel GetModelSequence(string model, string sequence);
|
||||||
bool HasModelSequence(string model, string sequence);
|
bool HasModelSequence(string model, string sequence);
|
||||||
IVertexBuffer<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; }
|
||||||
|
|
||||||
|
sealed 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlaceholderModelSequenceLoader(ModData modData) { }
|
||||||
|
|
||||||
|
public IModelCache CacheModels(IReadOnlyFileSystem fileSystem, ModData modData, IReadOnlyDictionary<string, MiniYamlNode> modelDefinitions)
|
||||||
|
{
|
||||||
|
return new PlaceholderModelCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
|
||||||
|
|
||||||
namespace OpenRA.Mods.Cnc.Traits
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public class ModelRenderProxy
|
public class ModelRenderProxy
|
||||||
{
|
{
|
||||||
@@ -34,14 +32,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
public sealed class ModelRenderer : IDisposable
|
||||||
[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
|
|
||||||
{
|
{
|
||||||
// Static constants
|
// Static constants
|
||||||
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
static readonly float[] ShadowDiffuse = new float[] { 0, 0, 0 };
|
||||||
@@ -55,32 +46,28 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
|
|
||||||
readonly Renderer renderer;
|
readonly Renderer renderer;
|
||||||
readonly IShader shader;
|
readonly IShader shader;
|
||||||
public readonly IModelCache ModelCache;
|
|
||||||
|
|
||||||
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
|
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new();
|
||||||
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
|
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new();
|
||||||
readonly List<(Sheet Sheet, Action Func)> doRender = new();
|
readonly List<(Sheet Sheet, Action Func)> doRender = new();
|
||||||
readonly int sheetSize;
|
|
||||||
|
|
||||||
SheetBuilder sheetBuilderForFrame;
|
SheetBuilder sheetBuilderForFrame;
|
||||||
bool isInFrame;
|
bool isInFrame;
|
||||||
|
|
||||||
public void SetPalette(HardwarePalette palette)
|
public ModelRenderer(Renderer renderer, IShader shader)
|
||||||
{
|
{
|
||||||
shader.SetTexture("Palette", palette.Texture);
|
this.renderer = renderer;
|
||||||
shader.SetVec("PaletteRows", palette.Height);
|
this.shader = shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelRenderer(ModelRendererInfo info, Actor self)
|
public void SetPalette(ITexture palette)
|
||||||
{
|
{
|
||||||
renderer = Game.Renderer;
|
shader.SetTexture("Palette", palette);
|
||||||
shader = renderer.CreateShader(new ModelShaderBindings());
|
}
|
||||||
renderer.WorldRenderers = renderer.WorldRenderers.Append(this).ToArray();
|
|
||||||
|
|
||||||
ModelCache = self.Trait<IModelCache>();
|
public void SetViewportParams()
|
||||||
|
{
|
||||||
sheetSize = Game.Settings.Graphics.SheetSize;
|
var a = 2f / renderer.SheetSize;
|
||||||
var a = 2f / sheetSize;
|
|
||||||
var view = new[]
|
var view = new[]
|
||||||
{
|
{
|
||||||
a, 0, 0, 0,
|
a, 0, 0, 0,
|
||||||
@@ -189,12 +176,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
|
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 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 translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
|
||||||
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, sheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
|
||||||
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
|
||||||
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
|
||||||
|
|
||||||
void RenderFunc()
|
doRender.Add((sprite.Sheet, () =>
|
||||||
{
|
{
|
||||||
foreach (var m in models)
|
foreach (var m in models)
|
||||||
{
|
{
|
||||||
@@ -226,18 +213,16 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
// Transform light vector from shadow -> world -> limb coords
|
// Transform light vector from shadow -> world -> limb coords
|
||||||
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));
|
||||||
|
|
||||||
Render(rd, ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
|
||||||
lightAmbientColor, lightDiffuseColor, color.TextureIndex, normals.TextureIndex);
|
lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);
|
||||||
|
|
||||||
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
// Disable shadow normals by forcing zero diffuse and identity ambient light
|
||||||
if (m.ShowShadow)
|
if (m.ShowShadow)
|
||||||
Render(rd, ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
|
||||||
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureIndex, normals.TextureIndex);
|
ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
|
|
||||||
doRender.Add((sprite.Sheet, RenderFunc));
|
|
||||||
|
|
||||||
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
|
var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
|
||||||
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
|
||||||
@@ -252,9 +237,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
|
|
||||||
// Width and height must be even to avoid rendering glitches
|
// Width and height must be even to avoid rendering glitches
|
||||||
if ((width & 1) == 1)
|
if ((width & 1) == 1)
|
||||||
width++;
|
width += 1;
|
||||||
if ((height & 1) == 1)
|
if ((height & 1) == 1)
|
||||||
height++;
|
height += 1;
|
||||||
|
|
||||||
size = new Size(width, height);
|
size = new Size(width, height);
|
||||||
}
|
}
|
||||||
@@ -282,17 +267,17 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
IModelCache cache,
|
IModelCache cache,
|
||||||
float[] t, float[] lightDirection,
|
float[] t, float[] lightDirection,
|
||||||
float[] ambientLight, float[] diffuseLight,
|
float[] ambientLight, float[] diffuseLight,
|
||||||
float colorPaletteTextureIndex, float normalsPaletteTextureIndex)
|
float colorPaletteTextureMidIndex, float normalsPaletteTextureMidIndex)
|
||||||
{
|
{
|
||||||
shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture());
|
shader.SetTexture("DiffuseTexture", renderData.Sheet.GetTexture());
|
||||||
shader.SetVec("Palettes", colorPaletteTextureIndex, normalsPaletteTextureIndex);
|
shader.SetVec("PaletteRows", colorPaletteTextureMidIndex, normalsPaletteTextureMidIndex);
|
||||||
shader.SetMatrix("TransformMatrix", t);
|
shader.SetMatrix("TransformMatrix", t);
|
||||||
shader.SetVec("LightDirection", lightDirection, 4);
|
shader.SetVec("LightDirection", lightDirection, 4);
|
||||||
shader.SetVec("AmbientLight", ambientLight, 3);
|
shader.SetVec("AmbientLight", ambientLight, 3);
|
||||||
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
shader.SetVec("DiffuseLight", diffuseLight, 3);
|
||||||
|
|
||||||
shader.PrepareRender();
|
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()
|
public void BeginFrame()
|
||||||
@@ -313,14 +298,14 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
fbo.Bind();
|
fbo.Bind();
|
||||||
|
|
||||||
Game.Renderer.EnableDepthBuffer();
|
Game.Renderer.Context.EnableDepthBuffer();
|
||||||
return fbo;
|
return fbo;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DisableFrameBuffer(IFrameBuffer fbo)
|
static void DisableFrameBuffer(IFrameBuffer fbo)
|
||||||
{
|
{
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
Game.Renderer.DisableDepthBuffer();
|
Game.Renderer.Context.DisableDepthBuffer();
|
||||||
fbo.Unbind();
|
fbo.Unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +353,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
return kv.Key;
|
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);
|
var sheet = new Sheet(SheetType.BGRA, framebuffer.Texture);
|
||||||
mappedBuffers.Add(sheet, framebuffer);
|
mappedBuffers.Add(sheet, framebuffer);
|
||||||
|
|
||||||
@@ -385,12 +371,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
|
|
||||||
mappedBuffers.Clear();
|
mappedBuffers.Clear();
|
||||||
unmappedBuffers.Clear();
|
unmappedBuffers.Clear();
|
||||||
renderer.WorldRenderers = renderer.WorldRenderers.Where(r => r != this).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
void INotifyActorDisposing.Disposing(Actor a)
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public static Color GetColor(this IPalette palette, int index)
|
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)
|
public static IPalette AsReadOnly(this IPalette palette)
|
||||||
@@ -103,7 +103,7 @@ namespace OpenRA.Graphics
|
|||||||
: this(p)
|
: this(p)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Palette.Size; i++)
|
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)
|
public ImmutablePalette(IPalette p)
|
||||||
@@ -142,7 +142,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public void SetColor(int index, Color color)
|
public void SetColor(int index, Color color)
|
||||||
{
|
{
|
||||||
colors[index] = color.ToArgb();
|
colors[index] = (uint)color.ToArgb();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFromPalette(IPalette p)
|
public void SetFromPalette(IPalette p)
|
||||||
@@ -153,7 +153,7 @@ namespace OpenRA.Graphics
|
|||||||
public void ApplyRemap(IPaletteRemap r)
|
public void ApplyRemap(IPaletteRemap r)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Palette.Size; i++)
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,19 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
public sealed class PaletteReference
|
public sealed class PaletteReference
|
||||||
{
|
{
|
||||||
|
readonly float index;
|
||||||
readonly HardwarePalette hardwarePalette;
|
readonly HardwarePalette hardwarePalette;
|
||||||
|
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public IPalette Palette { get; internal set; }
|
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)
|
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Palette = palette;
|
Palette = palette;
|
||||||
TextureIndex = index;
|
this.index = index;
|
||||||
this.hardwarePalette = hardwarePalette;
|
this.hardwarePalette = hardwarePalette;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ namespace OpenRA
|
|||||||
Automatic,
|
Automatic,
|
||||||
ANGLE,
|
ANGLE,
|
||||||
Modern,
|
Modern,
|
||||||
Embedded
|
Embedded,
|
||||||
|
Legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPlatform
|
public interface IPlatform
|
||||||
{
|
{
|
||||||
IPlatformWindow CreateWindow(
|
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
|
||||||
Size size, WindowMode windowMode, float scaleModifier, int vertexBatchSize, int indexBatchSize, int videoDisplay, GLProfile profile);
|
|
||||||
ISoundEngine CreateSound(string device);
|
ISoundEngine CreateSound(string device);
|
||||||
IFont CreateFont(byte[] data);
|
IFont CreateFont(byte[] data);
|
||||||
}
|
}
|
||||||
@@ -83,18 +83,16 @@ namespace OpenRA
|
|||||||
|
|
||||||
public interface IGraphicsContext : IDisposable
|
public interface IGraphicsContext : IDisposable
|
||||||
{
|
{
|
||||||
IVertexBuffer<T> CreateVertexBuffer<T>(int size) where T : struct;
|
IVertexBuffer<Vertex> CreateVertexBuffer(int size);
|
||||||
T[] CreateVertices<T>(int size) where T : struct;
|
Vertex[] CreateVertices(int size);
|
||||||
IIndexBuffer CreateIndexBuffer(uint[] indices);
|
|
||||||
ITexture CreateTexture();
|
ITexture CreateTexture();
|
||||||
IFrameBuffer CreateFrameBuffer(Size s);
|
IFrameBuffer CreateFrameBuffer(Size s);
|
||||||
IFrameBuffer CreateFrameBuffer(Size s, Color clearColor);
|
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 EnableScissor(int x, int y, int width, int height);
|
||||||
void DisableScissor();
|
void DisableScissor();
|
||||||
void Present();
|
void Present();
|
||||||
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices);
|
||||||
void DrawElements(int numIndices, int offset);
|
|
||||||
void Clear();
|
void Clear();
|
||||||
void EnableDepthBuffer();
|
void EnableDepthBuffer();
|
||||||
void DisableDepthBuffer();
|
void DisableDepthBuffer();
|
||||||
@@ -104,14 +102,7 @@ namespace OpenRA
|
|||||||
string GLVersion { get; }
|
string GLVersion { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRenderer
|
public interface IVertexBuffer<T> : IDisposable
|
||||||
{
|
|
||||||
void BeginFrame();
|
|
||||||
void EndFrame();
|
|
||||||
void SetPalette(HardwarePalette palette);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IVertexBuffer<T> : IDisposable where T : struct
|
|
||||||
{
|
{
|
||||||
void Bind();
|
void Bind();
|
||||||
void SetData(T[] vertices, int length);
|
void SetData(T[] vertices, int length);
|
||||||
@@ -123,11 +114,6 @@ namespace OpenRA
|
|||||||
void SetData(T[] vertices, int offset, int start, int length);
|
void SetData(T[] vertices, int offset, int start, int length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IIndexBuffer : IDisposable
|
|
||||||
{
|
|
||||||
void Bind();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IShader
|
public interface IShader
|
||||||
{
|
{
|
||||||
void SetBool(string name, bool value);
|
void SetBool(string name, bool value);
|
||||||
@@ -138,17 +124,6 @@ namespace OpenRA
|
|||||||
void SetTexture(string param, ITexture texture);
|
void SetTexture(string param, ITexture texture);
|
||||||
void SetMatrix(string param, float[] mtx);
|
void SetMatrix(string param, float[] mtx);
|
||||||
void PrepareRender();
|
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 }
|
public enum TextureScaleFilter { Nearest, Linear }
|
||||||
@@ -157,7 +132,6 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
void SetData(byte[] colors, int width, int height);
|
void SetData(byte[] colors, int width, int height);
|
||||||
void SetFloatData(float[] data, int width, int height);
|
void SetFloatData(float[] data, int width, int height);
|
||||||
void SetDataFromReadBuffer(Rectangle rect);
|
|
||||||
byte[] GetData();
|
byte[] GetData();
|
||||||
Size Size { get; }
|
Size Size { get; }
|
||||||
TextureScaleFilter ScaleFilter { get; set; }
|
TextureScaleFilter ScaleFilter { get; set; }
|
||||||
|
|||||||
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ namespace OpenRA.Graphics
|
|||||||
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
static readonly float3 Offset = new(0.5f, 0.5f, 0f);
|
||||||
|
|
||||||
readonly SpriteRenderer parent;
|
readonly SpriteRenderer parent;
|
||||||
readonly Vertex[] vertices = new Vertex[4];
|
readonly Vertex[] vertices = new Vertex[6];
|
||||||
|
|
||||||
public RgbaColorRenderer(SpriteRenderer parent)
|
public RgbaColorRenderer(SpriteRenderer parent)
|
||||||
{
|
{
|
||||||
@@ -45,12 +45,14 @@ namespace OpenRA.Graphics
|
|||||||
var eb = endColor.B / 255.0f;
|
var eb = endColor.B / 255.0f;
|
||||||
var ea = endColor.A / 255.0f;
|
var ea = endColor.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 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);
|
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);
|
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);
|
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)
|
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
@@ -64,11 +66,13 @@ namespace OpenRA.Graphics
|
|||||||
var b = color.B / 255.0f;
|
var b = color.B / 255.0f;
|
||||||
var a = color.A / 255.0f;
|
var a = color.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(start - corner + Offset, r, g, b, a, 0);
|
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);
|
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);
|
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);
|
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
|
||||||
parent.DrawRGBAQuad(vertices, blendMode);
|
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>
|
/// <summary>
|
||||||
@@ -153,11 +157,13 @@ namespace OpenRA.Graphics
|
|||||||
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
var cd = closed || i < limit - 1 ? IntersectionOf(end - corner, dir, end - nextCorner, nextDir) : end - corner;
|
||||||
|
|
||||||
// Fill segment
|
// Fill segment
|
||||||
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0);
|
vertices[0] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
|
||||||
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0);
|
vertices[1] = new Vertex(cb + Offset, r, g, b, a, 0, 0);
|
||||||
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0);
|
vertices[2] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||||
vertices[3] = new Vertex(cd + Offset, r, g, b, a, 0);
|
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
|
||||||
parent.DrawRGBAQuad(vertices, blendMode);
|
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
|
// Advance line segment
|
||||||
end = next;
|
end = next;
|
||||||
@@ -194,6 +200,20 @@ namespace OpenRA.Graphics
|
|||||||
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
|
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)
|
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);
|
var tr = new float3(br.X, tl.Y, tl.Z);
|
||||||
@@ -209,22 +229,25 @@ namespace OpenRA.Graphics
|
|||||||
var cb = color.B / 255.0f;
|
var cb = color.B / 255.0f;
|
||||||
var ca = color.A / 255.0f;
|
var ca = color.A / 255.0f;
|
||||||
|
|
||||||
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0);
|
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
|
||||||
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0);
|
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
|
||||||
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0);
|
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||||
vertices[3] = new Vertex(d + Offset, cr, cg, cb, ca, 0);
|
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
|
||||||
parent.DrawRGBAQuad(vertices, blendMode);
|
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,
|
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)
|
||||||
Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
|
|
||||||
{
|
{
|
||||||
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
|
||||||
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
vertices[1] = VertexWithColor(b + Offset, topRightColor);
|
||||||
vertices[2] = VertexWithColor(c + Offset, bottomRightColor);
|
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)
|
static Vertex VertexWithColor(in float3 xyz, Color color)
|
||||||
@@ -235,7 +258,7 @@ namespace OpenRA.Graphics
|
|||||||
var cb = color.B / 255.0f;
|
var cb = color.B / 255.0f;
|
||||||
var ca = color.A / 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)
|
public void FillEllipse(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
// Nodes starting with ^ are inheritable but never loaded directly
|
// Nodes starting with ^ are inheritable but never loaded directly
|
||||||
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix))
|
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
images[node.Key] = modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -132,7 +132,6 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
if (!Buffered)
|
if (!Buffered)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
releaseBufferOnCommit = true;
|
releaseBufferOnCommit = true;
|
||||||
|
|
||||||
@@ -141,29 +140,6 @@ namespace OpenRA.Graphics
|
|||||||
GetTexture();
|
GetTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ReleaseBufferAndTryTransferTo(Sheet destination)
|
|
||||||
{
|
|
||||||
if (Size != destination.Size)
|
|
||||||
throw new ArgumentException("Destination sheet does not have the same size", nameof(destination));
|
|
||||||
|
|
||||||
var buffer = data;
|
|
||||||
ReleaseBuffer();
|
|
||||||
|
|
||||||
// We aren't commiting data to the GPU, so let's not delete our data.
|
|
||||||
if (Game.Renderer == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Only transfer if the destination has no data that would be lost by overwriting.
|
|
||||||
if (buffer != null && destination.data == null && destination.texture == null)
|
|
||||||
{
|
|
||||||
Array.Clear(buffer, 0, buffer.Length);
|
|
||||||
destination.data = buffer;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
texture?.Dispose();
|
texture?.Dispose();
|
||||||
|
|||||||
@@ -82,16 +82,16 @@ namespace OpenRA.Graphics
|
|||||||
this.margin = margin;
|
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(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
||||||
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) { return Add(src, type, size, 0, float3.Zero); }
|
||||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false)
|
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
||||||
{
|
{
|
||||||
// Don't bother allocating empty sprites
|
// Don't bother allocating empty sprites
|
||||||
if (size.Width == 0 || size.Height == 0)
|
if (size.Width == 0 || size.Height == 0)
|
||||||
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha);
|
||||||
|
|
||||||
var rect = Allocate(size, zRamp, spriteOffset);
|
var rect = Allocate(size, zRamp, spriteOffset);
|
||||||
Util.FastCopyIntoChannel(rect, src, type, premultiplied);
|
Util.FastCopyIntoChannel(rect, src, type);
|
||||||
Current.CommitBufferedData();
|
Current.CommitBufferedData();
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
@@ -130,13 +130,8 @@ namespace OpenRA.Graphics
|
|||||||
var next = NextChannel(CurrentChannel);
|
var next = NextChannel(CurrentChannel);
|
||||||
if (next == null)
|
if (next == null)
|
||||||
{
|
{
|
||||||
var previous = Current;
|
Current.ReleaseBuffer();
|
||||||
Current = allocateSheet();
|
Current = allocateSheet();
|
||||||
|
|
||||||
// Reuse the backing buffer between sheets where possible.
|
|
||||||
// This avoids allocating additional buffers which the GC must clean up.
|
|
||||||
previous.ReleaseBufferAndTryTransferTo(Current);
|
|
||||||
|
|
||||||
sheets.Add(Current);
|
sheets.Add(Current);
|
||||||
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
CurrentChannel = Type == SheetType.Indexed ? TextureChannel.Red : TextureChannel.RGBA;
|
||||||
}
|
}
|
||||||
@@ -147,9 +142,7 @@ namespace OpenRA.Graphics
|
|||||||
p = int2.Zero;
|
p = int2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect = new Sprite(
|
var rect = new Sprite(Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height), zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
|
||||||
Current, new Rectangle(p.X + margin, p.Y + margin, imageSize.Width, imageSize.Height),
|
|
||||||
zRamp, spriteOffset, CurrentChannel, BlendMode.Alpha, scale);
|
|
||||||
p += new int2(imageSize.Width + margin, 0);
|
p += new int2(imageSize.Width + margin, 0);
|
||||||
|
|
||||||
return rect;
|
return rect;
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ namespace OpenRA.Graphics
|
|||||||
// in rendering a line of texels that sample outside the sprite rectangle.
|
// 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
|
// Insetting the texture coordinates by a small fraction of a pixel avoids this
|
||||||
// with negligible impact on the 1:1 rendering case.
|
// with negligible impact on the 1:1 rendering case.
|
||||||
const float Inset = 1 / 128f;
|
var inset = 1 / 128f;
|
||||||
Left = (Math.Min(bounds.Left, bounds.Right) + Inset) / sheet.Size.Width;
|
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
|
||||||
Top = (Math.Min(bounds.Top, bounds.Bottom) + Inset) / sheet.Size.Height;
|
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
|
||||||
Right = (Math.Max(bounds.Left, bounds.Right) - Inset) / sheet.Size.Width;
|
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
|
||||||
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - Inset) / sheet.Size.Height;
|
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,30 +14,28 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public delegate ISpriteFrame AdjustFrame(ISpriteFrame input, int index, int total);
|
|
||||||
|
|
||||||
public sealed class SpriteCache : IDisposable
|
public sealed class SpriteCache : IDisposable
|
||||||
{
|
{
|
||||||
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
public readonly Dictionary<SheetType, SheetBuilder> SheetBuilders;
|
||||||
readonly ISpriteLoader[] loaders;
|
readonly ISpriteLoader[] loaders;
|
||||||
readonly IReadOnlyFileSystem fileSystem;
|
readonly IReadOnlyFileSystem fileSystem;
|
||||||
|
|
||||||
readonly Dictionary<
|
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
|
||||||
int,
|
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
|
||||||
(int[] Frames, MiniYamlNode.SourceLocation Location, AdjustFrame AdjustFrame, bool Premultiplied)> spriteReservations = new();
|
|
||||||
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
readonly Dictionary<string, List<int>> reservationsByFilename = new();
|
||||||
|
|
||||||
|
readonly Dictionary<int, ISpriteFrame[]> resolvedFrames = new();
|
||||||
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
readonly Dictionary<int, Sprite[]> resolvedSprites = new();
|
||||||
|
|
||||||
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
readonly Dictionary<int, (string Filename, MiniYamlNode.SourceLocation Location)> missingFiles = new();
|
||||||
|
|
||||||
int nextReservationToken = 1;
|
int nextReservationToken = 1;
|
||||||
|
|
||||||
public SpriteCache(
|
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
||||||
IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int bgraSheetSize, int indexedSheetSize, int bgraSheetMargin = 1, int indexedSheetMargin = 1)
|
|
||||||
{
|
{
|
||||||
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
SheetBuilders = new Dictionary<SheetType, SheetBuilder>
|
||||||
{
|
{
|
||||||
@@ -49,110 +47,97 @@ namespace OpenRA.Graphics
|
|||||||
this.loaders = loaders;
|
this.loaders = loaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location,
|
public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||||
AdjustFrame adjustFrame = null, bool premultiplied = false)
|
|
||||||
{
|
{
|
||||||
var token = nextReservationToken++;
|
var token = nextReservationToken++;
|
||||||
spriteReservations[token] = (frames?.ToArray(), location, adjustFrame, premultiplied);
|
spriteReservations[token] = (frames?.ToArray(), location);
|
||||||
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
reservationsByFilename.GetOrAdd(filename, _ => new List<int>()).Add(token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ISpriteFrame[] GetFrames(IReadOnlyFileSystem fileSystem, string filename, ISpriteLoader[] loaders)
|
public int ReserveFrames(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
|
||||||
{
|
{
|
||||||
|
var token = nextReservationToken++;
|
||||||
|
frameReservations[token] = (frames?.ToArray(), location);
|
||||||
|
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))
|
if (!fileSystem.TryOpen(filename, out var stream))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
using (stream)
|
using (stream)
|
||||||
{
|
{
|
||||||
foreach (var loader in loaders)
|
foreach (var loader in loaders)
|
||||||
if (loader.TryParseSprite(stream, filename, out var frames, out _))
|
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
|
||||||
return frames;
|
return frames;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISpriteFrame[] LoadFramesUncached(string filename)
|
|
||||||
{
|
|
||||||
return GetFrames(fileSystem, filename, loaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadReservations(ModData modData)
|
public void LoadReservations(ModData modData)
|
||||||
{
|
{
|
||||||
var pendingResolve = new List<(
|
foreach (var sb in SheetBuilders.Values)
|
||||||
string Filename,
|
sb.Current.CreateBuffer();
|
||||||
int FrameIndex,
|
|
||||||
bool Premultiplied,
|
var spriteCache = new Dictionary<int, Sprite>();
|
||||||
AdjustFrame AdjustFrame,
|
|
||||||
ISpriteFrame Frame,
|
|
||||||
Sprite[] SpritesForToken)>();
|
|
||||||
foreach (var (filename, tokens) in reservationsByFilename)
|
foreach (var (filename, tokens) in reservationsByFilename)
|
||||||
{
|
{
|
||||||
modData.LoadScreen?.Display();
|
modData.LoadScreen?.Display();
|
||||||
var loadedFrames = GetFrames(fileSystem, filename, loaders);
|
var loadedFrames = GetFrames(fileSystem, filename, loaders, out _);
|
||||||
foreach (var token in tokens)
|
foreach (var token in tokens)
|
||||||
{
|
{
|
||||||
if (spriteReservations.TryGetValue(token, out var rs))
|
if (frameReservations.TryGetValue(token, out var r))
|
||||||
|
{
|
||||||
|
if (loadedFrames != null)
|
||||||
|
{
|
||||||
|
if (r.Frames != null)
|
||||||
|
{
|
||||||
|
var resolved = new ISpriteFrame[loadedFrames.Length];
|
||||||
|
foreach (var i in r.Frames)
|
||||||
|
resolved[i] = loadedFrames[i];
|
||||||
|
resolvedFrames[token] = resolved;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
resolvedFrames[token] = loadedFrames;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedFrames[token] = null;
|
||||||
|
missingFiles[token] = (filename, r.Location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spriteReservations.TryGetValue(token, out r))
|
||||||
{
|
{
|
||||||
if (loadedFrames != null)
|
if (loadedFrames != null)
|
||||||
{
|
{
|
||||||
var resolved = new Sprite[loadedFrames.Length];
|
var resolved = new Sprite[loadedFrames.Length];
|
||||||
resolvedSprites[token] = resolved;
|
var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
||||||
if (rs.Frames != null && rs.Frames.Any(i => i >= loadedFrames.Length))
|
|
||||||
throw new InvalidOperationException($"{rs.Location}: {filename} does not contain frames: " +
|
|
||||||
string.Join(',', rs.Frames.Where(f => f >= loadedFrames.Length)));
|
|
||||||
|
|
||||||
var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length);
|
|
||||||
var total = rs.Frames?.Length ?? loadedFrames.Length;
|
|
||||||
|
|
||||||
var j = 0;
|
|
||||||
foreach (var i in frames)
|
foreach (var i in frames)
|
||||||
{
|
resolved[i] = spriteCache.GetOrAdd(i,
|
||||||
var frame = loadedFrames[i];
|
f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f]));
|
||||||
if (rs.AdjustFrame != null)
|
|
||||||
frame = rs.AdjustFrame(frame, j++, total);
|
resolvedSprites[token] = resolved;
|
||||||
pendingResolve.Add((filename, i, rs.Premultiplied, rs.AdjustFrame, frame, resolved));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resolvedSprites[token] = null;
|
resolvedSprites[token] = null;
|
||||||
missingFiles[token] = (filename, rs.Location);
|
missingFiles[token] = (filename, r.Location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spriteCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
spriteReservations.Clear();
|
spriteReservations.Clear();
|
||||||
spriteReservations.TrimExcess();
|
frameReservations.Clear();
|
||||||
reservationsByFilename.Clear();
|
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,
|
|
||||||
AdjustFrame 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)
|
foreach (var sb in SheetBuilders.Values)
|
||||||
sb.Current.ReleaseBuffer();
|
sb.Current.ReleaseBuffer();
|
||||||
@@ -160,11 +145,18 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public Sprite[] ResolveSprites(int token)
|
public Sprite[] ResolveSprites(int token)
|
||||||
{
|
{
|
||||||
if (!resolvedSprites.Remove(token, out var resolved))
|
var resolved = resolvedSprites[token];
|
||||||
throw new InvalidOperationException($"{nameof(token)} {token} has either already been resolved, or was never reserved via {nameof(ReserveSprites)}");
|
resolvedSprites.Remove(token);
|
||||||
|
if (missingFiles.TryGetValue(token, out var r))
|
||||||
|
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||||
|
|
||||||
resolvedSprites.TrimExcess();
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISpriteFrame[] ResolveFrames(int token)
|
||||||
|
{
|
||||||
|
var resolved = resolvedFrames[token];
|
||||||
|
resolvedFrames.Remove(token);
|
||||||
if (missingFiles.TryGetValue(token, out var r))
|
if (missingFiles.TryGetValue(token, out var r))
|
||||||
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
throw new FileNotFoundException($"{r.Location}: {r.Filename} not found", r.Filename);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
float deviceScale;
|
float deviceScale;
|
||||||
|
|
||||||
public SpriteFont(IPlatform platform, string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
|
||||||
{
|
{
|
||||||
if (builder.Type != SheetType.BGRA)
|
if (builder.Type != SheetType.BGRA)
|
||||||
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
|
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
|
||||||
@@ -36,7 +36,7 @@ namespace OpenRA.Graphics
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
|
|
||||||
font = platform.CreateFont(data);
|
font = Game.Renderer.CreateFont(data);
|
||||||
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
|
||||||
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
|
||||||
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
|
||||||
|
|||||||
@@ -22,30 +22,20 @@ namespace OpenRA.Graphics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SpriteFrameType
|
public enum SpriteFrameType
|
||||||
{
|
{
|
||||||
/// <summary>
|
// 8 bit index into an external palette
|
||||||
/// 8 bit index into an external palette.
|
|
||||||
/// </summary>
|
|
||||||
Indexed8,
|
Indexed8,
|
||||||
|
|
||||||
/// <summary>
|
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
||||||
/// 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!)
|
||||||
/// (remember that little-endian systems place the little bits in the first byte).
|
|
||||||
/// </summary>
|
|
||||||
Bgra32,
|
Bgra32,
|
||||||
|
|
||||||
/// <summary>
|
// Like BGRA, but without an alpha channel
|
||||||
/// Like BGRA, but without an alpha channel.
|
|
||||||
/// </summary>
|
|
||||||
Bgr24,
|
Bgr24,
|
||||||
|
|
||||||
/// <summary>
|
// 32 bit color in big-endian format, like png
|
||||||
/// 32 bit color in big-endian format, like png.
|
|
||||||
/// </summary>
|
|
||||||
Rgba32,
|
Rgba32,
|
||||||
|
|
||||||
/// <summary>
|
// Like RGBA, but without an alpha channel
|
||||||
/// Like RGBA, but without an alpha channel.
|
|
||||||
/// </summary>
|
|
||||||
Rgb24
|
Rgb24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
@@ -20,7 +19,6 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
public const int SheetCount = 8;
|
public const int SheetCount = 8;
|
||||||
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
|
||||||
static readonly int UintSize = Marshal.SizeOf<uint>();
|
|
||||||
|
|
||||||
readonly Renderer renderer;
|
readonly Renderer renderer;
|
||||||
readonly IShader shader;
|
readonly IShader shader;
|
||||||
@@ -29,21 +27,21 @@ namespace OpenRA.Graphics
|
|||||||
readonly Sheet[] sheets = new Sheet[SheetCount];
|
readonly Sheet[] sheets = new Sheet[SheetCount];
|
||||||
|
|
||||||
BlendMode currentBlend = BlendMode.Alpha;
|
BlendMode currentBlend = BlendMode.Alpha;
|
||||||
int vertexCount = 0;
|
int nv = 0;
|
||||||
int sheetCount = 0;
|
int ns = 0;
|
||||||
|
|
||||||
public SpriteRenderer(Renderer renderer, IShader shader)
|
public SpriteRenderer(Renderer renderer, IShader shader)
|
||||||
{
|
{
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
this.shader = shader;
|
this.shader = shader;
|
||||||
vertices = renderer.Context.CreateVertices<Vertex>(renderer.TempVertexBufferSize);
|
vertices = renderer.Context.CreateVertices(renderer.TempBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Flush()
|
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());
|
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
|
||||||
sheets[i] = null;
|
sheets[i] = null;
|
||||||
@@ -52,11 +50,12 @@ namespace OpenRA.Graphics
|
|||||||
renderer.Context.SetBlendMode(currentBlend);
|
renderer.Context.SetBlendMode(currentBlend);
|
||||||
shader.PrepareRender();
|
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);
|
renderer.Context.SetBlendMode(BlendMode.None);
|
||||||
|
|
||||||
vertexCount = 0;
|
nv = 0;
|
||||||
sheetCount = 0;
|
ns = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ namespace OpenRA.Graphics
|
|||||||
{
|
{
|
||||||
renderer.CurrentBatchRenderer = this;
|
renderer.CurrentBatchRenderer = this;
|
||||||
|
|
||||||
if (s.BlendMode != currentBlend || vertexCount + 4 > renderer.TempVertexBufferSize)
|
if (s.BlendMode != currentBlend || nv + 6 > renderer.TempBufferSize)
|
||||||
Flush();
|
Flush();
|
||||||
|
|
||||||
currentBlend = s.BlendMode;
|
currentBlend = s.BlendMode;
|
||||||
@@ -72,7 +71,7 @@ namespace OpenRA.Graphics
|
|||||||
// Check if the sheet (or secondary data sheet) have already been mapped
|
// Check if the sheet (or secondary data sheet) have already been mapped
|
||||||
var sheet = s.Sheet;
|
var sheet = s.Sheet;
|
||||||
var sheetIndex = 0;
|
var sheetIndex = 0;
|
||||||
for (; sheetIndex < sheetCount; sheetIndex++)
|
for (; sheetIndex < ns; sheetIndex++)
|
||||||
if (sheets[sheetIndex] == sheet)
|
if (sheets[sheetIndex] == sheet)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ namespace OpenRA.Graphics
|
|||||||
if (ss != null)
|
if (ss != null)
|
||||||
{
|
{
|
||||||
var secondarySheet = ss.SecondarySheet;
|
var secondarySheet = ss.SecondarySheet;
|
||||||
for (; secondarySheetIndex < sheetCount; secondarySheetIndex++)
|
for (; secondarySheetIndex < ns; secondarySheetIndex++)
|
||||||
if (sheets[secondarySheetIndex] == secondarySheet)
|
if (sheets[secondarySheetIndex] == secondarySheet)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -100,22 +99,22 @@ namespace OpenRA.Graphics
|
|||||||
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sheetIndex >= sheetCount)
|
if (sheetIndex >= ns)
|
||||||
{
|
{
|
||||||
sheets[sheetIndex] = sheet;
|
sheets[sheetIndex] = sheet;
|
||||||
sheetCount++;
|
ns++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secondarySheetIndex >= sheetCount && ss != null)
|
if (secondarySheetIndex >= ns && ss != null)
|
||||||
{
|
{
|
||||||
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
sheets[secondarySheetIndex] = ss.SecondarySheet;
|
||||||
sheetCount++;
|
ns++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new int2(sheetIndex, secondarySheetIndex);
|
return new int2(sheetIndex, secondarySheetIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ResolveTextureIndex(Sprite s, PaletteReference pal)
|
static float ResolveTextureIndex(Sprite s, PaletteReference pal)
|
||||||
{
|
{
|
||||||
if (pal == null)
|
if (pal == null)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -129,20 +128,20 @@ namespace OpenRA.Graphics
|
|||||||
return pal.TextureIndex;
|
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);
|
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);
|
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);
|
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);
|
1f, rotation);
|
||||||
vertexCount += 4;
|
nv += 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f, float rotation = 0f)
|
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);
|
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)
|
float rotation = 0f)
|
||||||
{
|
{
|
||||||
var samplers = SetRenderStateForSprite(s);
|
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);
|
rotation);
|
||||||
vertexCount += 4;
|
nv += 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha,
|
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);
|
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);
|
var samplers = SetRenderStateForSprite(s);
|
||||||
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, vertexCount);
|
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
|
||||||
vertexCount += 4;
|
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;
|
var i = 0;
|
||||||
foreach (var s in sheets)
|
foreach (var s in sheets)
|
||||||
@@ -186,7 +185,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
renderer.Context.SetBlendMode(blendMode);
|
renderer.Context.SetBlendMode(blendMode);
|
||||||
shader.PrepareRender();
|
shader.PrepareRender();
|
||||||
renderer.DrawQuadBatch(buffer, indices, shader, length, UintSize * start);
|
renderer.DrawBatch(buffer, start, length, type);
|
||||||
renderer.Context.SetBlendMode(BlendMode.None);
|
renderer.Context.SetBlendMode(BlendMode.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,32 +196,29 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For RGBAColorRenderer
|
// For RGBAColorRenderer
|
||||||
internal void DrawRGBAQuad(Vertex[] v, BlendMode blendMode)
|
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
|
||||||
{
|
{
|
||||||
renderer.CurrentBatchRenderer = this;
|
renderer.CurrentBatchRenderer = this;
|
||||||
|
|
||||||
if (currentBlend != blendMode || vertexCount + 4 > renderer.TempVertexBufferSize)
|
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
|
||||||
Flush();
|
Flush();
|
||||||
|
|
||||||
currentBlend = blendMode;
|
currentBlend = blendMode;
|
||||||
|
Array.Copy(v, 0, vertices, nv, v.Length);
|
||||||
Array.Copy(v, 0, vertices, vertexCount, v.Length);
|
nv += v.Length;
|
||||||
vertexCount += 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPalette(HardwarePalette palette)
|
public void SetPalette(ITexture palette, ITexture colorShifts)
|
||||||
{
|
{
|
||||||
shader.SetTexture("Palette", palette.Texture);
|
shader.SetTexture("Palette", palette);
|
||||||
shader.SetTexture("ColorShifts", palette.ColorShifts);
|
shader.SetTexture("ColorShifts", colorShifts);
|
||||||
shader.SetVec("PaletteRows", palette.Height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
|
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
|
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
|
||||||
// using p1 to values [0, 2] and then subtract by 1 using p2, where p stands for projection. It's
|
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
|
||||||
// standard practice for shaders to use a projection matrix, but as we project orthographically
|
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
|
||||||
// we are able to send less data to the GPU.
|
|
||||||
var width = 2f / (downscale * sheetSize.Width);
|
var width = 2f / (downscale * sheetSize.Width);
|
||||||
var height = 2f / (downscale * sheetSize.Height);
|
var height = 2f / (downscale * sheetSize.Height);
|
||||||
|
|
||||||
@@ -243,8 +239,8 @@ namespace OpenRA.Graphics
|
|||||||
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
|
||||||
shader.SetVec("DepthTextureScale", 128 * depth);
|
shader.SetVec("DepthTextureScale", 128 * depth);
|
||||||
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
|
||||||
shader.SetVec("p1", width, height, -depth);
|
shader.SetVec("r1", width, height, -depth);
|
||||||
shader.SetVec("p2", -1, -1, depthMargin != 0f ? 1 : 0);
|
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
public void SetDepthPreview(bool enabled, float contrast, float offset)
|
||||||
@@ -253,9 +249,9 @@ namespace OpenRA.Graphics
|
|||||||
shader.SetVec("DepthPreviewParams", contrast, offset);
|
shader.SetVec("DepthPreviewParams", contrast, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnablePixelArtScaling(bool enabled)
|
public void SetAntialiasingPixelsPerTexel(float pxPerTx)
|
||||||
{
|
{
|
||||||
shader.SetBool("EnablePixelArtScaling", enabled);
|
shader.SetVec("AntialiasPixelsPerTexel", pxPerTx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public sealed class TerrainSpriteLayer : IDisposable
|
public sealed class TerrainSpriteLayer : IDisposable
|
||||||
{
|
{
|
||||||
// PERF: we can reuse the IndexBuffer as all layers have the same size.
|
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
|
||||||
static readonly ConditionalWeakTable<World, IndexBufferRc> IndexBuffers = new();
|
|
||||||
readonly IndexBufferRc indexBufferWrapper;
|
|
||||||
|
|
||||||
public readonly BlendMode BlendMode;
|
public readonly BlendMode BlendMode;
|
||||||
|
|
||||||
@@ -31,8 +28,7 @@ namespace OpenRA.Graphics
|
|||||||
readonly Vertex[] vertices;
|
readonly Vertex[] vertices;
|
||||||
readonly bool[] ignoreTint;
|
readonly bool[] ignoreTint;
|
||||||
readonly HashSet<int> dirtyRows = new();
|
readonly HashSet<int> dirtyRows = new();
|
||||||
readonly int indexRowStride;
|
readonly int rowStride;
|
||||||
readonly int vertexRowStride;
|
|
||||||
readonly bool restrictToBounds;
|
readonly bool restrictToBounds;
|
||||||
|
|
||||||
readonly WorldRenderer worldRenderer;
|
readonly WorldRenderer worldRenderer;
|
||||||
@@ -47,25 +43,19 @@ namespace OpenRA.Graphics
|
|||||||
this.emptySprite = emptySprite;
|
this.emptySprite = emptySprite;
|
||||||
sheets = new Sheet[SpriteRenderer.SheetCount];
|
sheets = new Sheet[SpriteRenderer.SheetCount];
|
||||||
BlendMode = blendMode;
|
BlendMode = blendMode;
|
||||||
|
|
||||||
map = world.Map;
|
map = world.Map;
|
||||||
|
rowStride = 6 * map.MapSize.X;
|
||||||
|
|
||||||
vertexRowStride = 4 * map.MapSize.X;
|
vertices = new Vertex[rowStride * map.MapSize.Y];
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
|
||||||
|
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
|
||||||
|
|
||||||
wr.PaletteInvalidated += UpdatePaletteIndices;
|
wr.PaletteInvalidated += UpdatePaletteIndices;
|
||||||
|
|
||||||
if (wr.TerrainLighting != null)
|
if (wr.TerrainLighting != null)
|
||||||
{
|
{
|
||||||
ignoreTint = new bool[vertexRowStride * map.MapSize.Y];
|
ignoreTint = new bool[rowStride * map.MapSize.Y];
|
||||||
wr.TerrainLighting.CellChanged += UpdateTint;
|
wr.TerrainLighting.CellChanged += UpdateTint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,9 +65,8 @@ namespace OpenRA.Graphics
|
|||||||
for (var i = 0; i < vertices.Length; i++)
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
{
|
{
|
||||||
var v = vertices[i];
|
var v = vertices[i];
|
||||||
var p = palettes[i / 4]?.TextureIndex ?? 0;
|
var p = palettes[i / 6]?.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, p, v.C, v.R, v.G, v.B, v.A);
|
||||||
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, c, v.R, v.G, v.B, v.A);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var row = 0; row < map.MapSize.Y; row++)
|
for (var row = 0; row < map.MapSize.Y; row++)
|
||||||
@@ -108,13 +97,13 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
void UpdateTint(MPos uv)
|
void UpdateTint(MPos uv)
|
||||||
{
|
{
|
||||||
var offset = vertexRowStride * uv.V + 4 * uv.U;
|
var offset = rowStride * uv.V + 6 * uv.U;
|
||||||
if (ignoreTint[offset])
|
if (ignoreTint[offset])
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 4; i++)
|
for (var i = 0; i < 6; i++)
|
||||||
{
|
{
|
||||||
var v = vertices[offset + i];
|
var v = vertices[offset + i];
|
||||||
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.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;
|
return;
|
||||||
@@ -136,10 +125,10 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
// Apply tint directly to the underlying vertices
|
// Apply tint directly to the underlying vertices
|
||||||
// This saves us from having to re-query the sprite information, which has not changed
|
// 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];
|
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);
|
dirtyRows.Add(uv.V);
|
||||||
@@ -191,7 +180,7 @@ namespace OpenRA.Graphics
|
|||||||
if (!map.Tiles.Contains(uv))
|
if (!map.Tiles.Contains(uv))
|
||||||
return;
|
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);
|
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;
|
palettes[uv.V * map.MapSize.X + uv.U] = palette;
|
||||||
|
|
||||||
@@ -220,13 +209,13 @@ namespace OpenRA.Graphics
|
|||||||
if (!dirtyRows.Remove(row))
|
if (!dirtyRows.Remove(row))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rowOffset = vertexRowStride * row;
|
var rowOffset = rowStride * row;
|
||||||
vertexBuffer.SetData(vertices, rowOffset, rowOffset, vertexRowStride);
|
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
|
||||||
vertexBuffer, indexBufferWrapper.Buffer, indexRowStride * firstRow,
|
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
|
||||||
indexRowStride * (lastRow - firstRow), sheets, BlendMode);
|
PrimitiveType.TriangleList, sheets, BlendMode);
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
Game.Renderer.Flush();
|
||||||
}
|
}
|
||||||
@@ -238,29 +227,6 @@ namespace OpenRA.Graphics
|
|||||||
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
|
||||||
|
|
||||||
vertexBuffer.Dispose();
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ namespace OpenRA.Graphics
|
|||||||
readonly float alpha;
|
readonly float alpha;
|
||||||
readonly float rotation = 0f;
|
readonly float rotation = 0f;
|
||||||
|
|
||||||
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette,
|
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f, float rotation = 0f)
|
||||||
float scale = 1f, float alpha = 1f, float rotation = 0f)
|
|
||||||
{
|
{
|
||||||
this.sprite = sprite;
|
this.sprite = sprite;
|
||||||
Pos = effectiveWorldPos;
|
Pos = effectiveWorldPos;
|
||||||
@@ -48,8 +47,7 @@ namespace OpenRA.Graphics
|
|||||||
public PaletteReference Palette { get; }
|
public PaletteReference Palette { get; }
|
||||||
public int ZOffset { get; }
|
public int ZOffset { get; }
|
||||||
|
|
||||||
public IPalettedRenderable WithPalette(PaletteReference newPalette) =>
|
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation); }
|
||||||
new UISpriteRenderable(sprite, Pos, screenPos, ZOffset, newPalette, scale, alpha, rotation);
|
|
||||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||||
public IRenderable OffsetBy(in WVec vec) { return this; }
|
public IRenderable OffsetBy(in WVec vec) { return this; }
|
||||||
public IRenderable AsDecoration() { return this; }
|
public IRenderable AsDecoration() { return this; }
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
@@ -21,17 +20,7 @@ namespace OpenRA.Graphics
|
|||||||
// yes, our channel order is nuts.
|
// yes, our channel order is nuts.
|
||||||
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
|
||||||
|
|
||||||
public static uint[] CreateQuadIndices(int quads)
|
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv,
|
||||||
{
|
|
||||||
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,
|
|
||||||
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
in float3 size, in float3 tint, float alpha, float rotation = 0f)
|
||||||
{
|
{
|
||||||
float3 a, b, c, d;
|
float3 a, b, c, d;
|
||||||
@@ -73,7 +62,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public static void FastCreateQuad(Vertex[] vertices,
|
public static void FastCreateQuad(Vertex[] vertices,
|
||||||
in float3 a, in float3 b, in float3 c, in float3 d,
|
in float3 a, in float3 b, in float3 c, in float3 d,
|
||||||
Sprite r, int2 samplers, int paletteTextureIndex,
|
Sprite r, int2 samplers, float paletteTextureIndex,
|
||||||
in float3 tint, float alpha, int nv)
|
in float3 tint, float alpha, int nv)
|
||||||
{
|
{
|
||||||
float sl = 0;
|
float sl = 0;
|
||||||
@@ -95,33 +84,76 @@ namespace OpenRA.Graphics
|
|||||||
attribC |= samplers.Y << 9;
|
attribC |= samplers.Y << 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
attribC |= (paletteTextureIndex & 0xFFFF) << 16;
|
var fAttribC = (float)attribC;
|
||||||
|
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||||
var uAttribC = (uint)attribC;
|
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
|
||||||
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, uAttribC, tint, alpha);
|
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||||
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, uAttribC, tint, alpha);
|
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||||
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, uAttribC, tint, alpha);
|
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
|
||||||
vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, 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 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 width = dest.Bounds.Width;
|
||||||
var height = dest.Bounds.Height;
|
var height = dest.Bounds.Height;
|
||||||
|
|
||||||
if (dest.Channel == TextureChannel.RGBA)
|
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
|
else
|
||||||
{
|
{
|
||||||
// Copy into single channel of destination.
|
var destStride = dest.Sheet.Size.Width * 4;
|
||||||
var destStride = stride * 4;
|
var destOffset = destStride * dest.Bounds.Top + dest.Bounds.Left * 4 + ChannelMasks[(int)dest.Channel];
|
||||||
var destOffset = destStride * y + x * 4 + ChannelMasks[(int)dest.Channel];
|
|
||||||
var destSkip = destStride - 4 * width;
|
var destSkip = destStride - 4 * width;
|
||||||
|
|
||||||
var srcOffset = 0;
|
var srcOffset = 0;
|
||||||
@@ -138,119 +170,56 @@ 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)
|
public static void FastCopyIntoSprite(Sprite dest, Png src)
|
||||||
{
|
{
|
||||||
var destData = dest.Sheet.GetData();
|
var destData = dest.Sheet.GetData();
|
||||||
var stride = dest.Sheet.Size.Width;
|
var destStride = dest.Sheet.Size.Width;
|
||||||
var x = dest.Bounds.Left;
|
|
||||||
var y = dest.Bounds.Top;
|
|
||||||
var width = dest.Bounds.Width;
|
var width = dest.Bounds.Width;
|
||||||
var height = dest.Bounds.Height;
|
var height = dest.Bounds.Height;
|
||||||
|
|
||||||
var si = 0;
|
unsafe
|
||||||
var di = y * stride + x;
|
|
||||||
var d = MemoryMarshal.Cast<byte, uint>(destData);
|
|
||||||
|
|
||||||
for (var h = 0; h < height; h++)
|
|
||||||
{
|
{
|
||||||
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;
|
var data = (int*)bd;
|
||||||
switch (src.Type)
|
var x = dest.Bounds.Left;
|
||||||
|
var y = dest.Bounds.Top;
|
||||||
|
|
||||||
|
var k = 0;
|
||||||
|
for (var j = 0; j < height; j++)
|
||||||
{
|
{
|
||||||
case SpriteFrameType.Indexed8:
|
for (var i = 0; i < width; i++)
|
||||||
c = src.Palette[src.Data[si++]];
|
{
|
||||||
break;
|
Color cc;
|
||||||
|
switch (src.Type)
|
||||||
|
{
|
||||||
|
case SpriteFrameType.Indexed8:
|
||||||
|
{
|
||||||
|
cc = src.Palette[src.Data[k++]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case SpriteFrameType.Rgba32:
|
case SpriteFrameType.Rgba32:
|
||||||
case SpriteFrameType.Rgb24:
|
case SpriteFrameType.Rgb24:
|
||||||
var r = src.Data[si++];
|
{
|
||||||
var g = src.Data[si++];
|
var r = src.Data[k++];
|
||||||
var b = src.Data[si++];
|
var g = src.Data[k++];
|
||||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[si++] : byte.MaxValue;
|
var b = src.Data[k++];
|
||||||
c = Color.FromArgb(a, r, g, b);
|
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
|
||||||
break;
|
cc = Color.FromArgb(a, r, g, b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// PNGs don't support BGR[A], so no need to include them here
|
// Pngs don't support BGR[A], so no need to include them here
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d[di++] = PremultiplyAlpha(c).ToArgb();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
di += stride - width;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.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))));
|
(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,42 +23,27 @@ namespace OpenRA.Graphics
|
|||||||
public readonly float S, T, U, V;
|
public readonly float S, T, U, V;
|
||||||
|
|
||||||
// Palette and channel flags
|
// Palette and channel flags
|
||||||
public readonly uint C;
|
public readonly float P, C;
|
||||||
|
|
||||||
// Color tint
|
// Color tint
|
||||||
public readonly float R, G, B, A;
|
public readonly float R, G, B, A;
|
||||||
|
|
||||||
public Vertex(in float3 xyz, float s, float t, float u, float v, uint c)
|
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
|
||||||
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, c, float3.Ones, 1f) { }
|
: 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)
|
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, c, tint.X, tint.Y, tint.Z, a) { }
|
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
|
||||||
|
|
||||||
public Vertex(float x, float y, float z, float s, float t, float u, float v, uint c, in float3 tint, float a)
|
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
|
||||||
: this(x, y, z, s, t, u, v, c, tint.X, tint.Y, tint.Z, 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;
|
X = x; Y = y; Z = z;
|
||||||
S = s; T = t;
|
S = s; T = t;
|
||||||
U = u; V = v;
|
U = u; V = v;
|
||||||
C = c;
|
P = p; C = c;
|
||||||
R = r; G = g; B = b; A = a;
|
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,9 +215,7 @@ namespace OpenRA.Graphics
|
|||||||
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
MinZoom = CalculateMinimumZoom(range.X, range.Y) * viewportSizes.DefaultScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
MaxZoom = Math.Min(
|
MaxZoom = Math.Min(MinZoom * viewportSizes.MaxZoomScale, Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
||||||
MinZoom * viewportSizes.MaxZoomScale,
|
|
||||||
Game.Renderer.NativeResolution.Height * viewportSizes.DefaultScale / viewportSizes.MaxZoomWindowHeight);
|
|
||||||
|
|
||||||
if (unlockMinZoom)
|
if (unlockMinZoom)
|
||||||
{
|
{
|
||||||
@@ -233,12 +231,11 @@ namespace OpenRA.Graphics
|
|||||||
else
|
else
|
||||||
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
|
Zoom = Zoom.Clamp(MinZoom, MaxZoom);
|
||||||
|
|
||||||
var minZoom = unlockMinZoom ? unlockedMinZoom : MinZoom;
|
var maxSize = 1f / (unlockMinZoom ? unlockedMinZoom : MinZoom) * new float2(Game.Renderer.NativeResolution);
|
||||||
var maxSize = 1f / minZoom * new float2(Game.Renderer.NativeResolution);
|
|
||||||
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
|
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
|
||||||
|
|
||||||
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
||||||
t.ViewportZoomExtentsChanged(minZoom, MaxZoom);
|
t.ViewportZoomExtentsChanged(MinZoom, MaxZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CPos ViewToWorld(int2 view)
|
public CPos ViewToWorld(int2 view)
|
||||||
@@ -285,23 +282,11 @@ namespace OpenRA.Graphics
|
|||||||
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
IEnumerable<MPos> CandidateMouseoverCells(int2 world)
|
||||||
{
|
{
|
||||||
var map = worldRenderer.World.Map;
|
var map = worldRenderer.World.Map;
|
||||||
var tileScale = map.Grid.TileScale / 2;
|
|
||||||
var minPos = worldRenderer.ProjectedPosition(world);
|
var minPos = worldRenderer.ProjectedPosition(world);
|
||||||
|
|
||||||
// Find all the cells that could potentially have been clicked.
|
// Find all the cells that could potentially have been clicked
|
||||||
MPos a;
|
var a = map.CellContaining(minPos - new WVec(1024, 0, 0)).ToMPos(map.Grid.Type);
|
||||||
MPos b;
|
var b = map.CellContaining(minPos + new WVec(512, 512 * map.Grid.MaximumTerrainHeight, 0)).ToMPos(map.Grid.Type);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var v = b.V; v >= a.V; v--)
|
for (var v = b.V; v >= a.V; v--)
|
||||||
for (var u = b.U; u >= a.U; u--)
|
for (var u = b.U; u >= a.U; u--)
|
||||||
@@ -314,13 +299,10 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public void Center(IEnumerable<Actor> actors)
|
public void Center(IEnumerable<Actor> actors)
|
||||||
{
|
{
|
||||||
var actorsCollection = actors as IReadOnlyCollection<Actor>;
|
if (!actors.Any())
|
||||||
actorsCollection ??= actors.ToList();
|
|
||||||
|
|
||||||
if (actorsCollection.Count == 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Center(actorsCollection.Select(a => a.CenterPosition).Average());
|
Center(actors.Select(a => a.CenterPosition).Average());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Center(WPos pos)
|
public void Center(WPos pos)
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ namespace OpenRA.Graphics
|
|||||||
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new();
|
||||||
|
|
||||||
readonly List<IRenderable> renderablesBuffer = new();
|
readonly List<IRenderable> renderablesBuffer = new();
|
||||||
readonly IRenderer[] renderers;
|
|
||||||
readonly IRenderPostProcessPass[] postProcessPasses;
|
|
||||||
|
|
||||||
internal WorldRenderer(ModData modData, World world)
|
internal WorldRenderer(ModData modData, World world)
|
||||||
{
|
{
|
||||||
@@ -62,29 +60,15 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
foreach (var pal in world.TraitDict.ActorsWithTrait<ILoadsPalettes>())
|
||||||
pal.Trait.LoadPalettes(this);
|
pal.Trait.LoadPalettes(this);
|
||||||
|
|
||||||
Player.SetupRelationshipColors(world.Players, world.LocalPlayer, this, true);
|
foreach (var p in world.Players)
|
||||||
|
UpdatePalettesForPlayer(p.InternalName, p.Color, false);
|
||||||
|
|
||||||
palette.Initialize();
|
palette.Initialize();
|
||||||
|
|
||||||
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
|
||||||
renderers = world.WorldActor.TraitsImplementing<IRenderer>().ToArray();
|
|
||||||
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
|
||||||
|
|
||||||
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
|
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)
|
public void UpdatePalettesForPlayer(string internalName, Color color, bool replaceExisting)
|
||||||
@@ -286,8 +270,6 @@ namespace OpenRA.Graphics
|
|||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
Game.Renderer.ClearDepthBuffer();
|
Game.Renderer.ClearDepthBuffer();
|
||||||
|
|
||||||
ApplyPostProcessing(PostProcessPassType.AfterActors);
|
|
||||||
|
|
||||||
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
World.ApplyToActorsWithTrait<IRenderAboveWorld>((actor, trait) =>
|
||||||
{
|
{
|
||||||
if (actor.IsInWorld && !actor.Disposed)
|
if (actor.IsInWorld && !actor.Disposed)
|
||||||
@@ -297,8 +279,6 @@ namespace OpenRA.Graphics
|
|||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
Game.Renderer.ClearDepthBuffer();
|
Game.Renderer.ClearDepthBuffer();
|
||||||
|
|
||||||
ApplyPostProcessing(PostProcessPassType.AfterWorld);
|
|
||||||
|
|
||||||
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
World.ApplyToActorsWithTrait<IRenderShroud>((actor, trait) => trait.RenderShroud(this));
|
||||||
|
|
||||||
if (enableDepthBuffer)
|
if (enableDepthBuffer)
|
||||||
@@ -312,23 +292,9 @@ namespace OpenRA.Graphics
|
|||||||
foreach (var r in g)
|
foreach (var r in g)
|
||||||
r.Render(this);
|
r.Render(this);
|
||||||
|
|
||||||
ApplyPostProcessing(PostProcessPassType.AfterShroud);
|
|
||||||
|
|
||||||
Game.Renderer.Flush();
|
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()
|
public void DrawAnnotations()
|
||||||
{
|
{
|
||||||
Game.Renderer.EnableAntialiasingFilter();
|
Game.Renderer.EnableAntialiasingFilter();
|
||||||
|
|||||||
@@ -16,19 +16,11 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public sealed class HotkeyDefinition
|
public sealed class HotkeyDefinition
|
||||||
{
|
{
|
||||||
public const string ContextFluentPrefix = "hotkey-context";
|
|
||||||
|
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
public readonly Hotkey Default = Hotkey.Invalid;
|
public readonly Hotkey Default = Hotkey.Invalid;
|
||||||
|
|
||||||
[FluentReference]
|
|
||||||
public readonly string Description = "";
|
public readonly string Description = "";
|
||||||
|
|
||||||
public readonly HashSet<string> Types = new();
|
public readonly HashSet<string> Types = new();
|
||||||
|
|
||||||
[FluentReference]
|
|
||||||
public readonly HashSet<string> Contexts = new();
|
public readonly HashSet<string> Contexts = new();
|
||||||
|
|
||||||
public readonly bool Readonly = false;
|
public readonly bool Readonly = false;
|
||||||
public bool HasDuplicates { get; internal set; }
|
public bool HasDuplicates { get; internal set; }
|
||||||
|
|
||||||
@@ -39,27 +31,29 @@ namespace OpenRA
|
|||||||
if (!string.IsNullOrEmpty(node.Value))
|
if (!string.IsNullOrEmpty(node.Value))
|
||||||
Default = FieldLoader.GetValue<Hotkey>("value", 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))
|
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
|
||||||
Description = descriptionYaml.Value;
|
if (typesNode != null)
|
||||||
|
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Types", out var typesYaml))
|
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
|
||||||
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
|
if (contextsNode != null)
|
||||||
|
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
|
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
|
||||||
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value)
|
if (platformNode != null)
|
||||||
.Select(c => ContextFluentPrefix + "." + c).ToHashSet();
|
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Platform", out var platformYaml))
|
|
||||||
{
|
{
|
||||||
var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString());
|
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
|
||||||
if (platformOverride != null)
|
if (platformOverride != null)
|
||||||
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeDict.TryGetValue("Readonly", out var readonlyYaml))
|
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
|
||||||
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyYaml.Value);
|
if (readonlyNode != null)
|
||||||
|
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,16 +91,16 @@ namespace OpenRA
|
|||||||
var ret = KeycodeExts.DisplayString(Key);
|
var ret = KeycodeExts.DisplayString(Key);
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Shift))
|
if (Modifiers.HasModifier(Modifiers.Shift))
|
||||||
ret = $"{ModifiersExts.DisplayString(Modifiers.Shift)} + {ret}";
|
ret = "Shift + " + ret;
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Alt))
|
if (Modifiers.HasModifier(Modifiers.Alt))
|
||||||
ret = $"{ModifiersExts.DisplayString(Modifiers.Alt)} + {ret}";
|
ret = "Alt + " + ret;
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Ctrl))
|
if (Modifiers.HasModifier(Modifiers.Ctrl))
|
||||||
ret = $"{ModifiersExts.DisplayString(Modifiers.Ctrl)} + {ret}";
|
ret = "Ctrl + " + ret;
|
||||||
|
|
||||||
if (Modifiers.HasModifier(Modifiers.Meta))
|
if (Modifiers.HasModifier(Modifiers.Meta))
|
||||||
ret = $"{ModifiersExts.DisplayString(Modifiers.Meta)} + {ret}";
|
ret = (Platform.CurrentPlatform == PlatformType.OSX ? "Cmd + " : "Meta + ") + ret;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -62,33 +61,6 @@ namespace OpenRA
|
|||||||
Meta = 8,
|
Meta = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ModifiersExts
|
|
||||||
{
|
|
||||||
[FluentReference]
|
|
||||||
public const string Cmd = "keycode-modifier.cmd";
|
|
||||||
|
|
||||||
[FluentReference(Traits.LintDictionaryReference.Values)]
|
|
||||||
public static readonly IReadOnlyDictionary<Modifiers, string> ModifierFluentKeys = new Dictionary<Modifiers, string>()
|
|
||||||
{
|
|
||||||
{ Modifiers.None, "keycode-modifier.none" },
|
|
||||||
{ Modifiers.Shift, "keycode-modifier.shift" },
|
|
||||||
{ Modifiers.Alt, "keycode-modifier.alt" },
|
|
||||||
{ Modifiers.Ctrl, "keycode-modifier.ctrl" },
|
|
||||||
{ Modifiers.Meta, "keycode-modifier.meta" },
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string DisplayString(Modifiers m)
|
|
||||||
{
|
|
||||||
if (m == Modifiers.Meta && Platform.CurrentPlatform == PlatformType.OSX)
|
|
||||||
return FluentProvider.GetMessage(Cmd);
|
|
||||||
|
|
||||||
if (!ModifierFluentKeys.TryGetValue(m, out var fluentKey))
|
|
||||||
return m.ToString();
|
|
||||||
|
|
||||||
return FluentProvider.GetMessage(fluentKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum KeyInputEvent { Down, Up }
|
public enum KeyInputEvent { Down, Up }
|
||||||
public struct KeyInput
|
public struct KeyInput
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -259,255 +259,254 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static class KeycodeExts
|
public static class KeycodeExts
|
||||||
{
|
{
|
||||||
[FluentReference(Traits.LintDictionaryReference.Values)]
|
static readonly Dictionary<Keycode, string> KeyNames = new()
|
||||||
public static readonly IReadOnlyDictionary<Keycode, string> KeycodeFluentKeys = new Dictionary<Keycode, string>()
|
|
||||||
{
|
{
|
||||||
{ Keycode.UNKNOWN, "keycode.unknown" },
|
{ Keycode.UNKNOWN, "Undefined" },
|
||||||
{ Keycode.RETURN, "keycode.return" },
|
{ Keycode.RETURN, "Return" },
|
||||||
{ Keycode.ESCAPE, "keycode.escape" },
|
{ Keycode.ESCAPE, "Escape" },
|
||||||
{ Keycode.BACKSPACE, "keycode.backspace" },
|
{ Keycode.BACKSPACE, "Backspace" },
|
||||||
{ Keycode.TAB, "keycode.tab" },
|
{ Keycode.TAB, "Tab" },
|
||||||
{ Keycode.SPACE, "keycode.space" },
|
{ Keycode.SPACE, "Space" },
|
||||||
{ Keycode.EXCLAIM, "keycode.exclaim" },
|
{ Keycode.EXCLAIM, "!" },
|
||||||
{ Keycode.QUOTEDBL, "keycode.quotedbl" },
|
{ Keycode.QUOTEDBL, "\"" },
|
||||||
{ Keycode.HASH, "keycode.hash" },
|
{ Keycode.HASH, "#" },
|
||||||
{ Keycode.PERCENT, "keycode.percent" },
|
{ Keycode.PERCENT, "%" },
|
||||||
{ Keycode.DOLLAR, "keycode.dollar" },
|
{ Keycode.DOLLAR, "$" },
|
||||||
{ Keycode.AMPERSAND, "keycode.ampersand" },
|
{ Keycode.AMPERSAND, "&" },
|
||||||
{ Keycode.QUOTE, "keycode.quote" },
|
{ Keycode.QUOTE, "'" },
|
||||||
{ Keycode.LEFTPAREN, "keycode.leftparen" },
|
{ Keycode.LEFTPAREN, "(" },
|
||||||
{ Keycode.RIGHTPAREN, "keycode.rightparen" },
|
{ Keycode.RIGHTPAREN, ")" },
|
||||||
{ Keycode.ASTERISK, "keycode.asterisk" },
|
{ Keycode.ASTERISK, "*" },
|
||||||
{ Keycode.PLUS, "keycode.plus" },
|
{ Keycode.PLUS, "+" },
|
||||||
{ Keycode.COMMA, "keycode.comma" },
|
{ Keycode.COMMA, "," },
|
||||||
{ Keycode.MINUS, "keycode.minus" },
|
{ Keycode.MINUS, "-" },
|
||||||
{ Keycode.PERIOD, "keycode.period" },
|
{ Keycode.PERIOD, "." },
|
||||||
{ Keycode.SLASH, "keycode.slash" },
|
{ Keycode.SLASH, "/" },
|
||||||
{ Keycode.NUMBER_0, "keycode.number_0" },
|
{ Keycode.NUMBER_0, "0" },
|
||||||
{ Keycode.NUMBER_1, "keycode.number_1" },
|
{ Keycode.NUMBER_1, "1" },
|
||||||
{ Keycode.NUMBER_2, "keycode.number_2" },
|
{ Keycode.NUMBER_2, "2" },
|
||||||
{ Keycode.NUMBER_3, "keycode.number_3" },
|
{ Keycode.NUMBER_3, "3" },
|
||||||
{ Keycode.NUMBER_4, "keycode.number_4" },
|
{ Keycode.NUMBER_4, "4" },
|
||||||
{ Keycode.NUMBER_5, "keycode.number_5" },
|
{ Keycode.NUMBER_5, "5" },
|
||||||
{ Keycode.NUMBER_6, "keycode.number_6" },
|
{ Keycode.NUMBER_6, "6" },
|
||||||
{ Keycode.NUMBER_7, "keycode.number_7" },
|
{ Keycode.NUMBER_7, "7" },
|
||||||
{ Keycode.NUMBER_8, "keycode.number_8" },
|
{ Keycode.NUMBER_8, "8" },
|
||||||
{ Keycode.NUMBER_9, "keycode.number_9" },
|
{ Keycode.NUMBER_9, "9" },
|
||||||
{ Keycode.COLON, "keycode.colon" },
|
{ Keycode.COLON, ":" },
|
||||||
{ Keycode.SEMICOLON, "keycode.semicolon" },
|
{ Keycode.SEMICOLON, ";" },
|
||||||
{ Keycode.LESS, "keycode.less" },
|
{ Keycode.LESS, "<" },
|
||||||
{ Keycode.EQUALS, "keycode.equals" },
|
{ Keycode.EQUALS, "=" },
|
||||||
{ Keycode.GREATER, "keycode.greater" },
|
{ Keycode.GREATER, ">" },
|
||||||
{ Keycode.QUESTION, "keycode.question" },
|
{ Keycode.QUESTION, "?" },
|
||||||
{ Keycode.AT, "keycode.at" },
|
{ Keycode.AT, "@" },
|
||||||
{ Keycode.LEFTBRACKET, "keycode.leftbracket" },
|
{ Keycode.LEFTBRACKET, "[" },
|
||||||
{ Keycode.BACKSLASH, "keycode.backslash" },
|
{ Keycode.BACKSLASH, "\\" },
|
||||||
{ Keycode.RIGHTBRACKET, "keycode.rightbracket" },
|
{ Keycode.RIGHTBRACKET, "]" },
|
||||||
{ Keycode.CARET, "keycode.caret" },
|
{ Keycode.CARET, "^" },
|
||||||
{ Keycode.UNDERSCORE, "keycode.underscore" },
|
{ Keycode.UNDERSCORE, "_" },
|
||||||
{ Keycode.BACKQUOTE, "keycode.backquote" },
|
{ Keycode.BACKQUOTE, "`" },
|
||||||
{ Keycode.A, "keycode.a" },
|
{ Keycode.A, "A" },
|
||||||
{ Keycode.B, "keycode.b" },
|
{ Keycode.B, "B" },
|
||||||
{ Keycode.C, "keycode.c" },
|
{ Keycode.C, "C" },
|
||||||
{ Keycode.D, "keycode.d" },
|
{ Keycode.D, "D" },
|
||||||
{ Keycode.E, "keycode.e" },
|
{ Keycode.E, "E" },
|
||||||
{ Keycode.F, "keycode.f" },
|
{ Keycode.F, "F" },
|
||||||
{ Keycode.G, "keycode.g" },
|
{ Keycode.G, "G" },
|
||||||
{ Keycode.H, "keycode.h" },
|
{ Keycode.H, "H" },
|
||||||
{ Keycode.I, "keycode.i" },
|
{ Keycode.I, "I" },
|
||||||
{ Keycode.J, "keycode.j" },
|
{ Keycode.J, "J" },
|
||||||
{ Keycode.K, "keycode.k" },
|
{ Keycode.K, "K" },
|
||||||
{ Keycode.L, "keycode.l" },
|
{ Keycode.L, "L" },
|
||||||
{ Keycode.M, "keycode.m" },
|
{ Keycode.M, "M" },
|
||||||
{ Keycode.N, "keycode.n" },
|
{ Keycode.N, "N" },
|
||||||
{ Keycode.O, "keycode.o" },
|
{ Keycode.O, "O" },
|
||||||
{ Keycode.P, "keycode.p" },
|
{ Keycode.P, "P" },
|
||||||
{ Keycode.Q, "keycode.q" },
|
{ Keycode.Q, "Q" },
|
||||||
{ Keycode.R, "keycode.r" },
|
{ Keycode.R, "R" },
|
||||||
{ Keycode.S, "keycode.s" },
|
{ Keycode.S, "S" },
|
||||||
{ Keycode.T, "keycode.t" },
|
{ Keycode.T, "T" },
|
||||||
{ Keycode.U, "keycode.u" },
|
{ Keycode.U, "U" },
|
||||||
{ Keycode.V, "keycode.v" },
|
{ Keycode.V, "V" },
|
||||||
{ Keycode.W, "keycode.w" },
|
{ Keycode.W, "W" },
|
||||||
{ Keycode.X, "keycode.x" },
|
{ Keycode.X, "X" },
|
||||||
{ Keycode.Y, "keycode.y" },
|
{ Keycode.Y, "Y" },
|
||||||
{ Keycode.Z, "keycode.z" },
|
{ Keycode.Z, "Z" },
|
||||||
{ Keycode.CAPSLOCK, "keycode.capslock" },
|
{ Keycode.CAPSLOCK, "CapsLock" },
|
||||||
{ Keycode.F1, "keycode.f1" },
|
{ Keycode.F1, "F1" },
|
||||||
{ Keycode.F2, "keycode.f2" },
|
{ Keycode.F2, "F2" },
|
||||||
{ Keycode.F3, "keycode.f3" },
|
{ Keycode.F3, "F3" },
|
||||||
{ Keycode.F4, "keycode.f4" },
|
{ Keycode.F4, "F4" },
|
||||||
{ Keycode.F5, "keycode.f5" },
|
{ Keycode.F5, "F5" },
|
||||||
{ Keycode.F6, "keycode.f6" },
|
{ Keycode.F6, "F6" },
|
||||||
{ Keycode.F7, "keycode.f7" },
|
{ Keycode.F7, "F7" },
|
||||||
{ Keycode.F8, "keycode.f8" },
|
{ Keycode.F8, "F8" },
|
||||||
{ Keycode.F9, "keycode.f9" },
|
{ Keycode.F9, "F9" },
|
||||||
{ Keycode.F10, "keycode.f10" },
|
{ Keycode.F10, "F10" },
|
||||||
{ Keycode.F11, "keycode.f11" },
|
{ Keycode.F11, "F11" },
|
||||||
{ Keycode.F12, "keycode.f12" },
|
{ Keycode.F12, "F12" },
|
||||||
{ Keycode.PRINTSCREEN, "keycode.printscreen" },
|
{ Keycode.PRINTSCREEN, "PrintScreen" },
|
||||||
{ Keycode.SCROLLLOCK, "keycode.scrolllock" },
|
{ Keycode.SCROLLLOCK, "ScrollLock" },
|
||||||
{ Keycode.PAUSE, "keycode.pause" },
|
{ Keycode.PAUSE, "Pause" },
|
||||||
{ Keycode.INSERT, "keycode.insert" },
|
{ Keycode.INSERT, "Insert" },
|
||||||
{ Keycode.HOME, "keycode.home" },
|
{ Keycode.HOME, "Home" },
|
||||||
{ Keycode.PAGEUP, "keycode.pageup" },
|
{ Keycode.PAGEUP, "PageUp" },
|
||||||
{ Keycode.DELETE, "keycode.delete" },
|
{ Keycode.DELETE, "Delete" },
|
||||||
{ Keycode.END, "keycode.end" },
|
{ Keycode.END, "End" },
|
||||||
{ Keycode.PAGEDOWN, "keycode.pagedown" },
|
{ Keycode.PAGEDOWN, "PageDown" },
|
||||||
{ Keycode.RIGHT, "keycode.right" },
|
{ Keycode.RIGHT, "Right" },
|
||||||
{ Keycode.LEFT, "keycode.left" },
|
{ Keycode.LEFT, "Left" },
|
||||||
{ Keycode.DOWN, "keycode.down" },
|
{ Keycode.DOWN, "Down" },
|
||||||
{ Keycode.UP, "keycode.up" },
|
{ Keycode.UP, "Up" },
|
||||||
{ Keycode.NUMLOCKCLEAR, "keycode.numlockclear" },
|
{ Keycode.NUMLOCKCLEAR, "Numlock" },
|
||||||
{ Keycode.KP_DIVIDE, "keycode.kp_divide" },
|
{ Keycode.KP_DIVIDE, "Keypad /" },
|
||||||
{ Keycode.KP_MULTIPLY, "keycode.kp_multiply" },
|
{ Keycode.KP_MULTIPLY, "Keypad *" },
|
||||||
{ Keycode.KP_MINUS, "keycode.kp_minus" },
|
{ Keycode.KP_MINUS, "Keypad -" },
|
||||||
{ Keycode.KP_PLUS, "keycode.kp_plus" },
|
{ Keycode.KP_PLUS, "Keypad +" },
|
||||||
{ Keycode.KP_ENTER, "keycode.kp_enter" },
|
{ Keycode.KP_ENTER, "Keypad Enter" },
|
||||||
{ Keycode.KP_1, "keycode.kp_1" },
|
{ Keycode.KP_1, "Keypad 1" },
|
||||||
{ Keycode.KP_2, "keycode.kp_2" },
|
{ Keycode.KP_2, "Keypad 2" },
|
||||||
{ Keycode.KP_3, "keycode.kp_3" },
|
{ Keycode.KP_3, "Keypad 3" },
|
||||||
{ Keycode.KP_4, "keycode.kp_4" },
|
{ Keycode.KP_4, "Keypad 4" },
|
||||||
{ Keycode.KP_5, "keycode.kp_5" },
|
{ Keycode.KP_5, "Keypad 5" },
|
||||||
{ Keycode.KP_6, "keycode.kp_6" },
|
{ Keycode.KP_6, "Keypad 6" },
|
||||||
{ Keycode.KP_7, "keycode.kp_7" },
|
{ Keycode.KP_7, "Keypad 7" },
|
||||||
{ Keycode.KP_8, "keycode.kp_8" },
|
{ Keycode.KP_8, "Keypad 8" },
|
||||||
{ Keycode.KP_9, "keycode.kp_9" },
|
{ Keycode.KP_9, "Keypad 9" },
|
||||||
{ Keycode.KP_0, "keycode.kp_0" },
|
{ Keycode.KP_0, "Keypad 0" },
|
||||||
{ Keycode.KP_PERIOD, "keycode.kp_period" },
|
{ Keycode.KP_PERIOD, "Keypad ." },
|
||||||
{ Keycode.APPLICATION, "keycode.application" },
|
{ Keycode.APPLICATION, "Application" },
|
||||||
{ Keycode.POWER, "keycode.power" },
|
{ Keycode.POWER, "Power" },
|
||||||
{ Keycode.KP_EQUALS, "keycode.kp_equals" },
|
{ Keycode.KP_EQUALS, "Keypad =" },
|
||||||
{ Keycode.F13, "keycode.f13" },
|
{ Keycode.F13, "F13" },
|
||||||
{ Keycode.F14, "keycode.f14" },
|
{ Keycode.F14, "F14" },
|
||||||
{ Keycode.F15, "keycode.f15" },
|
{ Keycode.F15, "F15" },
|
||||||
{ Keycode.F16, "keycode.f16" },
|
{ Keycode.F16, "F16" },
|
||||||
{ Keycode.F17, "keycode.f17" },
|
{ Keycode.F17, "F17" },
|
||||||
{ Keycode.F18, "keycode.f18" },
|
{ Keycode.F18, "F18" },
|
||||||
{ Keycode.F19, "keycode.f19" },
|
{ Keycode.F19, "F19" },
|
||||||
{ Keycode.F20, "keycode.f20" },
|
{ Keycode.F20, "F20" },
|
||||||
{ Keycode.F21, "keycode.f21" },
|
{ Keycode.F21, "F21" },
|
||||||
{ Keycode.F22, "keycode.f22" },
|
{ Keycode.F22, "F22" },
|
||||||
{ Keycode.F23, "keycode.f23" },
|
{ Keycode.F23, "F23" },
|
||||||
{ Keycode.F24, "keycode.f24" },
|
{ Keycode.F24, "F24" },
|
||||||
{ Keycode.EXECUTE, "keycode.execute" },
|
{ Keycode.EXECUTE, "Execute" },
|
||||||
{ Keycode.HELP, "keycode.help" },
|
{ Keycode.HELP, "Help" },
|
||||||
{ Keycode.MENU, "keycode.menu" },
|
{ Keycode.MENU, "Menu" },
|
||||||
{ Keycode.SELECT, "keycode.select" },
|
{ Keycode.SELECT, "Select" },
|
||||||
{ Keycode.STOP, "keycode.stop" },
|
{ Keycode.STOP, "Stop" },
|
||||||
{ Keycode.AGAIN, "keycode.again" },
|
{ Keycode.AGAIN, "Again" },
|
||||||
{ Keycode.UNDO, "keycode.undo" },
|
{ Keycode.UNDO, "Undo" },
|
||||||
{ Keycode.CUT, "keycode.cut" },
|
{ Keycode.CUT, "Cut" },
|
||||||
{ Keycode.COPY, "keycode.copy" },
|
{ Keycode.COPY, "Copy" },
|
||||||
{ Keycode.PASTE, "keycode.paste" },
|
{ Keycode.PASTE, "Paste" },
|
||||||
{ Keycode.FIND, "keycode.find" },
|
{ Keycode.FIND, "Find" },
|
||||||
{ Keycode.MUTE, "keycode.mute" },
|
{ Keycode.MUTE, "Mute" },
|
||||||
{ Keycode.VOLUMEUP, "keycode.volumeup" },
|
{ Keycode.VOLUMEUP, "VolumeUp" },
|
||||||
{ Keycode.VOLUMEDOWN, "keycode.volumedown" },
|
{ Keycode.VOLUMEDOWN, "VolumeDown" },
|
||||||
{ Keycode.KP_COMMA, "keycode.kp_comma" },
|
{ Keycode.KP_COMMA, "Keypad }," },
|
||||||
{ Keycode.KP_EQUALSAS400, "keycode.kp_equalsas400" },
|
{ Keycode.KP_EQUALSAS400, "Keypad, (AS400)" },
|
||||||
{ Keycode.ALTERASE, "keycode.alterase" },
|
{ Keycode.ALTERASE, "AltErase" },
|
||||||
{ Keycode.SYSREQ, "keycode.sysreq" },
|
{ Keycode.SYSREQ, "SysReq" },
|
||||||
{ Keycode.CANCEL, "keycode.cancel" },
|
{ Keycode.CANCEL, "Cancel" },
|
||||||
{ Keycode.CLEAR, "keycode.clear" },
|
{ Keycode.CLEAR, "Clear" },
|
||||||
{ Keycode.PRIOR, "keycode.prior" },
|
{ Keycode.PRIOR, "Prior" },
|
||||||
{ Keycode.RETURN2, "keycode.return2" },
|
{ Keycode.RETURN2, "Return" },
|
||||||
{ Keycode.SEPARATOR, "keycode.separator" },
|
{ Keycode.SEPARATOR, "Separator" },
|
||||||
{ Keycode.OUT, "keycode.out" },
|
{ Keycode.OUT, "Out" },
|
||||||
{ Keycode.OPER, "keycode.oper" },
|
{ Keycode.OPER, "Oper" },
|
||||||
{ Keycode.CLEARAGAIN, "keycode.clearagain" },
|
{ Keycode.CLEARAGAIN, "Clear / Again" },
|
||||||
{ Keycode.CRSEL, "keycode.crsel" },
|
{ Keycode.CRSEL, "CrSel" },
|
||||||
{ Keycode.EXSEL, "keycode.exsel" },
|
{ Keycode.EXSEL, "ExSel" },
|
||||||
{ Keycode.KP_00, "keycode.kp_00" },
|
{ Keycode.KP_00, "Keypad 00" },
|
||||||
{ Keycode.KP_000, "keycode.kp_000" },
|
{ Keycode.KP_000, "Keypad 000" },
|
||||||
{ Keycode.THOUSANDSSEPARATOR, "keycode.thousandsseparator" },
|
{ Keycode.THOUSANDSSEPARATOR, "ThousandsSeparator" },
|
||||||
{ Keycode.DECIMALSEPARATOR, "keycode.decimalseparator" },
|
{ Keycode.DECIMALSEPARATOR, "DecimalSeparator" },
|
||||||
{ Keycode.CURRENCYUNIT, "keycode.currencyunit" },
|
{ Keycode.CURRENCYUNIT, "CurrencyUnit" },
|
||||||
{ Keycode.CURRENCYSUBUNIT, "keycode.currencysubunit" },
|
{ Keycode.CURRENCYSUBUNIT, "CurrencySubUnit" },
|
||||||
{ Keycode.KP_LEFTPAREN, "keycode.kp_leftparen" },
|
{ Keycode.KP_LEFTPAREN, "Keypad (" },
|
||||||
{ Keycode.KP_RIGHTPAREN, "keycode.kp_rightparen" },
|
{ Keycode.KP_RIGHTPAREN, "Keypad )" },
|
||||||
{ Keycode.KP_LEFTBRACE, "keycode.kp_leftbrace" },
|
{ Keycode.KP_LEFTBRACE, "Keypad {" },
|
||||||
{ Keycode.KP_RIGHTBRACE, "keycode.kp_rightbrace" },
|
{ Keycode.KP_RIGHTBRACE, "Keypad }" },
|
||||||
{ Keycode.KP_TAB, "keycode.kp_tab" },
|
{ Keycode.KP_TAB, "Keypad Tab" },
|
||||||
{ Keycode.KP_BACKSPACE, "keycode.kp_backspace" },
|
{ Keycode.KP_BACKSPACE, "Keypad Backspace" },
|
||||||
{ Keycode.KP_A, "keycode.kp_a" },
|
{ Keycode.KP_A, "Keypad A" },
|
||||||
{ Keycode.KP_B, "keycode.kp_b" },
|
{ Keycode.KP_B, "Keypad B" },
|
||||||
{ Keycode.KP_C, "keycode.kp_c" },
|
{ Keycode.KP_C, "Keypad C" },
|
||||||
{ Keycode.KP_D, "keycode.kp_d" },
|
{ Keycode.KP_D, "Keypad D" },
|
||||||
{ Keycode.KP_E, "keycode.kp_e" },
|
{ Keycode.KP_E, "Keypad E" },
|
||||||
{ Keycode.KP_F, "keycode.kp_f" },
|
{ Keycode.KP_F, "Keypad F" },
|
||||||
{ Keycode.KP_XOR, "keycode.kp_xor" },
|
{ Keycode.KP_XOR, "Keypad XOR" },
|
||||||
{ Keycode.KP_POWER, "keycode.kp_power" },
|
{ Keycode.KP_POWER, "Keypad ^" },
|
||||||
{ Keycode.KP_PERCENT, "keycode.kp_percent" },
|
{ Keycode.KP_PERCENT, "Keypad %" },
|
||||||
{ Keycode.KP_LESS, "keycode.kp_less" },
|
{ Keycode.KP_LESS, "Keypad <" },
|
||||||
{ Keycode.KP_GREATER, "keycode.kp_greater" },
|
{ Keycode.KP_GREATER, "Keypad >" },
|
||||||
{ Keycode.KP_AMPERSAND, "keycode.kp_ampersand" },
|
{ Keycode.KP_AMPERSAND, "Keypad &" },
|
||||||
{ Keycode.KP_DBLAMPERSAND, "keycode.kp_dblampersand" },
|
{ Keycode.KP_DBLAMPERSAND, "Keypad &&" },
|
||||||
{ Keycode.KP_VERTICALBAR, "keycode.kp_verticalbar" },
|
{ Keycode.KP_VERTICALBAR, "Keypad |" },
|
||||||
{ Keycode.KP_DBLVERTICALBAR, "keycode.kp_dblverticalbar" },
|
{ Keycode.KP_DBLVERTICALBAR, "Keypad ||" },
|
||||||
{ Keycode.KP_COLON, "keycode.kp_colon" },
|
{ Keycode.KP_COLON, "Keypad :" },
|
||||||
{ Keycode.KP_HASH, "keycode.kp_hash" },
|
{ Keycode.KP_HASH, "Keypad #" },
|
||||||
{ Keycode.KP_SPACE, "keycode.kp_space" },
|
{ Keycode.KP_SPACE, "Keypad Space" },
|
||||||
{ Keycode.KP_AT, "keycode.kp_at" },
|
{ Keycode.KP_AT, "Keypad @" },
|
||||||
{ Keycode.KP_EXCLAM, "keycode.kp_exclam" },
|
{ Keycode.KP_EXCLAM, "Keypad !" },
|
||||||
{ Keycode.KP_MEMSTORE, "keycode.kp_memstore" },
|
{ Keycode.KP_MEMSTORE, "Keypad MemStore" },
|
||||||
{ Keycode.KP_MEMRECALL, "keycode.kp_memrecall" },
|
{ Keycode.KP_MEMRECALL, "Keypad MemRecall" },
|
||||||
{ Keycode.KP_MEMCLEAR, "keycode.kp_memclear" },
|
{ Keycode.KP_MEMCLEAR, "Keypad MemClear" },
|
||||||
{ Keycode.KP_MEMADD, "keycode.kp_memadd" },
|
{ Keycode.KP_MEMADD, "Keypad MemAdd" },
|
||||||
{ Keycode.KP_MEMSUBTRACT, "keycode.kp_memsubtract" },
|
{ Keycode.KP_MEMSUBTRACT, "Keypad MemSubtract" },
|
||||||
{ Keycode.KP_MEMMULTIPLY, "keycode.kp_memmultiply" },
|
{ Keycode.KP_MEMMULTIPLY, "Keypad MemMultiply" },
|
||||||
{ Keycode.KP_MEMDIVIDE, "keycode.kp_memdivide" },
|
{ Keycode.KP_MEMDIVIDE, "Keypad MemDivide" },
|
||||||
{ Keycode.KP_PLUSMINUS, "keycode.kp_plusminus" },
|
{ Keycode.KP_PLUSMINUS, "Keypad +/-" },
|
||||||
{ Keycode.KP_CLEAR, "keycode.kp_clear" },
|
{ Keycode.KP_CLEAR, "Keypad Clear" },
|
||||||
{ Keycode.KP_CLEARENTRY, "keycode.kp_clearentry" },
|
{ Keycode.KP_CLEARENTRY, "Keypad ClearEntry" },
|
||||||
{ Keycode.KP_BINARY, "keycode.kp_binary" },
|
{ Keycode.KP_BINARY, "Keypad Binary" },
|
||||||
{ Keycode.KP_OCTAL, "keycode.kp_octal" },
|
{ Keycode.KP_OCTAL, "Keypad Octal" },
|
||||||
{ Keycode.KP_DECIMAL, "keycode.kp_decimal" },
|
{ Keycode.KP_DECIMAL, "Keypad Decimal" },
|
||||||
{ Keycode.KP_HEXADECIMAL, "keycode.kp_hexadecimal" },
|
{ Keycode.KP_HEXADECIMAL, "Keypad Hexadecimal" },
|
||||||
{ Keycode.LCTRL, "keycode.lctrl" },
|
{ Keycode.LCTRL, "Left Ctrl" },
|
||||||
{ Keycode.LSHIFT, "keycode.lshift" },
|
{ Keycode.LSHIFT, "Left Shift" },
|
||||||
{ Keycode.LALT, "keycode.lalt" },
|
{ Keycode.LALT, "Left Alt" },
|
||||||
{ Keycode.LGUI, "keycode.lgui" },
|
{ Keycode.LGUI, "Left GUI" },
|
||||||
{ Keycode.RCTRL, "keycode.rctrl" },
|
{ Keycode.RCTRL, "Right Ctrl" },
|
||||||
{ Keycode.RSHIFT, "keycode.rshift" },
|
{ Keycode.RSHIFT, "Right Shift" },
|
||||||
{ Keycode.RALT, "keycode.ralt" },
|
{ Keycode.RALT, "Right Alt" },
|
||||||
{ Keycode.RGUI, "keycode.rgui" },
|
{ Keycode.RGUI, "Right GUI" },
|
||||||
{ Keycode.MODE, "keycode.mode" },
|
{ Keycode.MODE, "ModeSwitch" },
|
||||||
{ Keycode.AUDIONEXT, "keycode.audionext" },
|
{ Keycode.AUDIONEXT, "AudioNext" },
|
||||||
{ Keycode.AUDIOPREV, "keycode.audioprev" },
|
{ Keycode.AUDIOPREV, "AudioPrev" },
|
||||||
{ Keycode.AUDIOSTOP, "keycode.audiostop" },
|
{ Keycode.AUDIOSTOP, "AudioStop" },
|
||||||
{ Keycode.AUDIOPLAY, "keycode.audioplay" },
|
{ Keycode.AUDIOPLAY, "AudioPlay" },
|
||||||
{ Keycode.AUDIOMUTE, "keycode.audiomute" },
|
{ Keycode.AUDIOMUTE, "AudioMute" },
|
||||||
{ Keycode.MEDIASELECT, "keycode.mediaselect" },
|
{ Keycode.MEDIASELECT, "MediaSelect" },
|
||||||
{ Keycode.WWW, "keycode.www" },
|
{ Keycode.WWW, "WWW" },
|
||||||
{ Keycode.MAIL, "keycode.mail" },
|
{ Keycode.MAIL, "Mail" },
|
||||||
{ Keycode.CALCULATOR, "keycode.calculator" },
|
{ Keycode.CALCULATOR, "Calculator" },
|
||||||
{ Keycode.COMPUTER, "keycode.computer" },
|
{ Keycode.COMPUTER, "Computer" },
|
||||||
{ Keycode.AC_SEARCH, "keycode.ac_search" },
|
{ Keycode.AC_SEARCH, "AC Search" },
|
||||||
{ Keycode.AC_HOME, "keycode.ac_home" },
|
{ Keycode.AC_HOME, "AC Home" },
|
||||||
{ Keycode.AC_BACK, "keycode.ac_back" },
|
{ Keycode.AC_BACK, "AC Back" },
|
||||||
{ Keycode.AC_FORWARD, "keycode.ac_forward" },
|
{ Keycode.AC_FORWARD, "AC Forward" },
|
||||||
{ Keycode.AC_STOP, "keycode.ac_stop" },
|
{ Keycode.AC_STOP, "AC Stop" },
|
||||||
{ Keycode.AC_REFRESH, "keycode.ac_refresh" },
|
{ Keycode.AC_REFRESH, "AC Refresh" },
|
||||||
{ Keycode.AC_BOOKMARKS, "keycode.ac_bookmarks" },
|
{ Keycode.AC_BOOKMARKS, "AC Bookmarks" },
|
||||||
{ Keycode.BRIGHTNESSDOWN, "keycode.brightnessdown" },
|
{ Keycode.BRIGHTNESSDOWN, "BrightnessDown" },
|
||||||
{ Keycode.BRIGHTNESSUP, "keycode.brightnessup" },
|
{ Keycode.BRIGHTNESSUP, "BrightnessUp" },
|
||||||
{ Keycode.DISPLAYSWITCH, "keycode.displayswitch" },
|
{ Keycode.DISPLAYSWITCH, "DisplaySwitch" },
|
||||||
{ Keycode.KBDILLUMTOGGLE, "keycode.kbdillumtoggle" },
|
{ Keycode.KBDILLUMTOGGLE, "KBDIllumToggle" },
|
||||||
{ Keycode.KBDILLUMDOWN, "keycode.kbdillumdown" },
|
{ Keycode.KBDILLUMDOWN, "KBDIllumDown" },
|
||||||
{ Keycode.KBDILLUMUP, "keycode.kbdillumup" },
|
{ Keycode.KBDILLUMUP, "KBDIllumUp" },
|
||||||
{ Keycode.EJECT, "keycode.eject" },
|
{ Keycode.EJECT, "Eject" },
|
||||||
{ Keycode.SLEEP, "keycode.sleep" },
|
{ Keycode.SLEEP, "Sleep" },
|
||||||
{ Keycode.MOUSE4, "keycode.mouse4" },
|
{ Keycode.MOUSE4, "Mouse 4" },
|
||||||
{ Keycode.MOUSE5, "keycode.mouse5" },
|
{ Keycode.MOUSE5, "Mouse 5" },
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string DisplayString(Keycode k)
|
public static string DisplayString(Keycode k)
|
||||||
{
|
{
|
||||||
if (!KeycodeFluentKeys.TryGetValue(k, out var fluentKey))
|
if (!KeyNames.TryGetValue(k, out var ret))
|
||||||
return k.ToString();
|
return k.ToString();
|
||||||
|
|
||||||
return FluentProvider.GetMessage(fluentKey);
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,11 +84,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var client = HttpClientFactory.Create();
|
var client = HttpClientFactory.Create();
|
||||||
|
|
||||||
var url = playerDatabase.Profile + Fingerprint;
|
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
|
||||||
var httpResponseMessage = await client.GetAsync(url);
|
|
||||||
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
var yaml = MiniYaml.FromStream(result, url).First();
|
var yaml = MiniYaml.FromStream(result).First();
|
||||||
if (yaml.Key == "Player")
|
if (yaml.Key == "Player")
|
||||||
{
|
{
|
||||||
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
|
||||||
|
|||||||
@@ -43,22 +43,25 @@ 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 class ModMetadata
|
||||||
{
|
{
|
||||||
// FieldLoader used here, must matching naming in YAML.
|
public string Title;
|
||||||
#pragma warning disable IDE1006 // Naming Styles
|
public string Version;
|
||||||
[FluentReference]
|
public string Website;
|
||||||
readonly string Title;
|
public string WebIcon32;
|
||||||
public readonly string Version;
|
public string WindowTitle;
|
||||||
public readonly string Website;
|
public bool Hidden;
|
||||||
public readonly string WebIcon32;
|
|
||||||
[FluentReference]
|
|
||||||
readonly string WindowTitle;
|
|
||||||
public readonly bool Hidden;
|
|
||||||
#pragma warning restore IDE1006 // Naming Styles
|
|
||||||
|
|
||||||
public string TitleTranslated => FluentProvider.GetMessage(Title);
|
|
||||||
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetMessage(WindowTitle) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
/// <summary>Describes what is to be loaded in order to run a mod.</summary>
|
||||||
@@ -69,34 +72,27 @@ namespace OpenRA
|
|||||||
public readonly ModMetadata Metadata;
|
public readonly ModMetadata Metadata;
|
||||||
public readonly string[]
|
public readonly string[]
|
||||||
Rules, ServerTraits,
|
Rules, ServerTraits,
|
||||||
Sequences, ModelSequences, Cursors, Chrome, ChromeLayout,
|
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||||
Weapons, Voices, Notifications, Music, FluentMessages, TileSets,
|
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||||
|
|
||||||
|
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||||
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
public readonly IReadOnlyDictionary<string, string> MapFolders;
|
||||||
public readonly MiniYaml FileSystem;
|
|
||||||
public readonly MiniYaml LoadScreen;
|
public readonly MiniYaml LoadScreen;
|
||||||
public readonly string DefaultOrderGenerator;
|
public readonly string DefaultOrderGenerator;
|
||||||
|
|
||||||
public readonly string[] Assemblies = Array.Empty<string>();
|
|
||||||
public readonly string[] SoundFormats = Array.Empty<string>();
|
public readonly string[] SoundFormats = Array.Empty<string>();
|
||||||
public readonly string[] SpriteFormats = Array.Empty<string>();
|
public readonly string[] SpriteFormats = Array.Empty<string>();
|
||||||
public readonly string[] PackageFormats = Array.Empty<string>();
|
public readonly string[] PackageFormats = Array.Empty<string>();
|
||||||
public readonly string[] VideoFormats = Array.Empty<string>();
|
public readonly string[] VideoFormats = Array.Empty<string>();
|
||||||
public readonly int FontSheetSize = 512;
|
|
||||||
public readonly int CursorSheetSize = 512;
|
|
||||||
|
|
||||||
// TODO: This should be controlled by a user-selected translation bundle!
|
|
||||||
public readonly string FluentCulture = "en";
|
|
||||||
public readonly bool AllowUnusedFluentMessagesInExternalPackages = true;
|
|
||||||
|
|
||||||
readonly string[] reservedModuleNames =
|
readonly string[] reservedModuleNames =
|
||||||
{
|
{
|
||||||
"Include", "Metadata", "FileSystem", "MapFolders", "Rules",
|
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||||
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||||
"Voices", "Notifications", "Music", "FluentMessages", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
|
||||||
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
"ServerTraits", "LoadScreen", "DefaultOrderGenerator", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
|
||||||
"RequiresMods", "PackageFormats", "AllowUnusedFluentMessagesInExternalPackages", "FontSheetSize", "CursorSheetSize"
|
"RequiresMods", "PackageFormats"
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly TypeDictionary modules = new();
|
readonly TypeDictionary modules = new();
|
||||||
@@ -109,8 +105,7 @@ namespace OpenRA
|
|||||||
Id = modId;
|
Id = modId;
|
||||||
Package = package;
|
Package = package;
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
|
||||||
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool);
|
|
||||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
for (var i = nodes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (nodes[i].Key != "Include")
|
if (nodes[i].Key != "Include")
|
||||||
@@ -123,7 +118,7 @@ namespace OpenRA
|
|||||||
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
|
||||||
|
|
||||||
nodes.RemoveAt(i);
|
nodes.RemoveAt(i);
|
||||||
nodes.InsertRange(i, MiniYaml.FromStream(contents, $"{package.Name}:{filename}", stringPool: stringPool));
|
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge inherited overrides
|
// Merge inherited overrides
|
||||||
@@ -134,20 +129,21 @@ namespace OpenRA
|
|||||||
// TODO: Use fieldloader
|
// TODO: Use fieldloader
|
||||||
MapFolders = YamlDictionary(yaml, "MapFolders");
|
MapFolders = YamlDictionary(yaml, "MapFolders");
|
||||||
|
|
||||||
if (!yaml.TryGetValue("FileSystem", out FileSystem))
|
if (yaml.TryGetValue("Packages", out var packages))
|
||||||
throw new InvalidDataException("`FileSystem` section is not defined.");
|
Packages = packages.ToDictionary(x => x.Value);
|
||||||
|
|
||||||
Rules = YamlList(yaml, "Rules");
|
Rules = YamlList(yaml, "Rules");
|
||||||
Sequences = YamlList(yaml, "Sequences");
|
Sequences = YamlList(yaml, "Sequences");
|
||||||
ModelSequences = YamlList(yaml, "ModelSequences");
|
ModelSequences = YamlList(yaml, "ModelSequences");
|
||||||
Cursors = YamlList(yaml, "Cursors");
|
Cursors = YamlList(yaml, "Cursors");
|
||||||
Chrome = YamlList(yaml, "Chrome");
|
Chrome = YamlList(yaml, "Chrome");
|
||||||
|
Assemblies = YamlList(yaml, "Assemblies");
|
||||||
ChromeLayout = YamlList(yaml, "ChromeLayout");
|
ChromeLayout = YamlList(yaml, "ChromeLayout");
|
||||||
Weapons = YamlList(yaml, "Weapons");
|
Weapons = YamlList(yaml, "Weapons");
|
||||||
Voices = YamlList(yaml, "Voices");
|
Voices = YamlList(yaml, "Voices");
|
||||||
Notifications = YamlList(yaml, "Notifications");
|
Notifications = YamlList(yaml, "Notifications");
|
||||||
Music = YamlList(yaml, "Music");
|
Music = YamlList(yaml, "Music");
|
||||||
FluentMessages = YamlList(yaml, "FluentMessages");
|
Translations = YamlList(yaml, "Translations");
|
||||||
TileSets = YamlList(yaml, "TileSets");
|
TileSets = YamlList(yaml, "TileSets");
|
||||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||||
Missions = YamlList(yaml, "Missions");
|
Missions = YamlList(yaml, "Missions");
|
||||||
@@ -169,9 +165,6 @@ namespace OpenRA
|
|||||||
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
if (yaml.TryGetValue("DefaultOrderGenerator", out entry))
|
||||||
DefaultOrderGenerator = entry.Value;
|
DefaultOrderGenerator = entry.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Assemblies", out entry))
|
|
||||||
Assemblies = FieldLoader.GetValue<string[]>("Assemblies", entry.Value);
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("PackageFormats", out entry))
|
if (yaml.TryGetValue("PackageFormats", out entry))
|
||||||
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
PackageFormats = FieldLoader.GetValue<string[]>("PackageFormats", entry.Value);
|
||||||
|
|
||||||
@@ -183,16 +176,6 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (yaml.TryGetValue("VideoFormats", out entry))
|
if (yaml.TryGetValue("VideoFormats", out entry))
|
||||||
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", entry.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("AllowUnusedFluentMessagesInExternalPackages", out entry))
|
|
||||||
AllowUnusedFluentMessagesInExternalPackages =
|
|
||||||
FieldLoader.GetValue<bool>("AllowUnusedFluentMessagesInExternalPackages", entry.Value);
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("FontSheetSize", out entry))
|
|
||||||
FontSheetSize = FieldLoader.GetValue<int>("FontSheetSize", entry.Value);
|
|
||||||
|
|
||||||
if (yaml.TryGetValue("CursorSheetSize", out entry))
|
|
||||||
CursorSheetSize = FieldLoader.GetValue<int>("CursorSheetSize", entry.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCustomData(ObjectCreator oc)
|
public void LoadCustomData(ObjectCreator oc)
|
||||||
@@ -228,18 +211,18 @@ namespace OpenRA
|
|||||||
|
|
||||||
static string[] YamlList(Dictionary<string, MiniYaml> yaml, string key)
|
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 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)
|
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 new Dictionary<string, string>();
|
||||||
|
|
||||||
return value.ToDictionary(my => my.Value);
|
return yaml[key].ToDictionary(my => my.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains<T>() where T : IGlobalModData
|
public bool Contains<T>() where T : IGlobalModData
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public virtual void Initialize(MiniYaml yaml)
|
public virtual void Initialize(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
Initialize(FieldLoader.GetValue<T>(nameof(value), yaml.Value));
|
Initialize((T)FieldLoader.GetValue(nameof(value), typeof(T), yaml.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Initialize(T value)
|
public virtual void Initialize(T value)
|
||||||
@@ -175,7 +175,8 @@ namespace OpenRA
|
|||||||
protected CompositeActorInit(TraitInfo info)
|
protected CompositeActorInit(TraitInfo info)
|
||||||
: base(info.InstanceName) { }
|
: base(info.InstanceName) { }
|
||||||
|
|
||||||
protected CompositeActorInit() { }
|
protected CompositeActorInit()
|
||||||
|
: base() { }
|
||||||
|
|
||||||
public virtual void Initialize(MiniYaml yaml)
|
public virtual void Initialize(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
|
||||||
{
|
{
|
||||||
var nodes = new List<MiniYamlNode>();
|
var ret = new MiniYaml(Type);
|
||||||
foreach (var o in initDict.Value)
|
foreach (var o in initDict.Value)
|
||||||
{
|
{
|
||||||
if (o is not ActorInit init || o is ISuppressInitExport)
|
if (o is not ActorInit init || o is ISuppressInitExport)
|
||||||
@@ -98,10 +98,10 @@ namespace OpenRA
|
|||||||
if (!string.IsNullOrEmpty(init.InstanceName))
|
if (!string.IsNullOrEmpty(init.InstanceName))
|
||||||
initName += ActorInfo.TraitInstanceSeparator + 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(); }
|
public IEnumerator<object> GetEnumerator() { return initDict.Value.GetEnumerator(); }
|
||||||
@@ -139,7 +139,7 @@ namespace OpenRA
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyCollection<T> GetAll<T>() where T : ActorInit
|
public IEnumerable<T> GetAll<T>() where T : ActorInit
|
||||||
{
|
{
|
||||||
return initDict.Value.WithInterface<T>();
|
return initDict.Value.WithInterface<T>();
|
||||||
}
|
}
|
||||||
@@ -152,9 +152,8 @@ namespace OpenRA
|
|||||||
// If a more specific init is not available, fall back to an unnamed init.
|
// 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 duplicate inits are defined, take the last to match standard yaml override expectations
|
||||||
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
|
||||||
return
|
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
||||||
inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
|
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||||
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
|
||||||
|
|
||||||
// Untagged traits will only use untagged inits
|
// Untagged traits will only use untagged inits
|
||||||
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
|
||||||
|
|||||||
@@ -1,95 +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 bool Contains(CPos cell)
|
|
||||||
{
|
|
||||||
return cell.X >= TopLeft.X && cell.X <= BottomRight.X && cell.Y >= TopLeft.Y && cell.Y <= BottomRight.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -63,9 +64,9 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
|
/// <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));
|
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
|
||||||
|
|
||||||
var minU = int.MaxValue;
|
var minU = int.MaxValue;
|
||||||
@@ -102,7 +103,6 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
|
public MapCoordsRegion MapCoords => new(mapTopLeft, mapBottomRight);
|
||||||
public CellCoordsRegion CellCoords => new(TopLeft, BottomRight);
|
|
||||||
|
|
||||||
public CellRegionEnumerator GetEnumerator()
|
public CellRegionEnumerator GetEnumerator()
|
||||||
{
|
{
|
||||||
@@ -136,12 +136,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
u++;
|
u += 1;
|
||||||
|
|
||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.mapBottomRight.U)
|
if (u > r.mapBottomRight.U)
|
||||||
{
|
{
|
||||||
v++;
|
v += 1;
|
||||||
u = r.mapTopLeft.U;
|
u = r.mapTopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -162,8 +162,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CPos Current { get; private set; }
|
public CPos Current { get; private set; }
|
||||||
readonly object IEnumerator.Current => Current;
|
object IEnumerator.Current => Current;
|
||||||
public readonly void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -91,13 +90,13 @@ namespace OpenRA
|
|||||||
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
|
throw new InvalidOperationException("Map does not have a field/property " + fieldName);
|
||||||
|
|
||||||
var t = field != null ? field.FieldType : property.PropertyType;
|
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;
|
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 (node == null)
|
||||||
{
|
{
|
||||||
if (required)
|
if (required)
|
||||||
@@ -131,14 +130,14 @@ namespace OpenRA
|
|||||||
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
|
var value = field != null ? field.GetValue(map) : property.GetValue(map, null);
|
||||||
if (type == Type.NodeList)
|
if (type == Type.NodeList)
|
||||||
{
|
{
|
||||||
var listValue = (IReadOnlyCollection<MiniYamlNode>)value;
|
var listValue = (List<MiniYamlNode>)value;
|
||||||
if (required || listValue.Count > 0)
|
if (required || listValue.Count > 0)
|
||||||
nodes.Add(new MiniYamlNode(key, null, listValue));
|
nodes.Add(new MiniYamlNode(key, null, listValue));
|
||||||
}
|
}
|
||||||
else if (type == Type.MiniYaml)
|
else if (type == Type.MiniYaml)
|
||||||
{
|
{
|
||||||
var yamlValue = (MiniYaml)value;
|
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));
|
nodes.Add(new MiniYamlNode(key, yamlValue));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -159,26 +158,26 @@ namespace OpenRA
|
|||||||
/// <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 =
|
static readonly MapField[] YamlFields =
|
||||||
{
|
{
|
||||||
new("MapFormat"),
|
new MapField("MapFormat"),
|
||||||
new("RequiresMod"),
|
new MapField("RequiresMod"),
|
||||||
new("Title"),
|
new MapField("Title"),
|
||||||
new("Author"),
|
new MapField("Author"),
|
||||||
new("Tileset"),
|
new MapField("Tileset"),
|
||||||
new("MapSize"),
|
new MapField("MapSize"),
|
||||||
new("Bounds"),
|
new MapField("Bounds"),
|
||||||
new("Visibility"),
|
new MapField("Visibility"),
|
||||||
new("Categories"),
|
new MapField("Categories"),
|
||||||
new("LockPreview", required: false, ignoreIfValue: "False"),
|
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
||||||
new("Players", nameof(PlayerDefinitions)),
|
new MapField("Players", "PlayerDefinitions"),
|
||||||
new("Actors", nameof(ActorDefinitions)),
|
new MapField("Actors", "ActorDefinitions"),
|
||||||
new("Rules", nameof(RuleDefinitions), required: false),
|
new MapField("Rules", "RuleDefinitions", required: false),
|
||||||
new("FluentMessages", nameof(FluentMessageDefinitions), required: false),
|
new MapField("Translations", "TranslationDefinitions", required: false),
|
||||||
new("Sequences", nameof(SequenceDefinitions), required: false),
|
new MapField("Sequences", "SequenceDefinitions", required: false),
|
||||||
new("ModelSequences", nameof(ModelSequenceDefinitions), required: false),
|
new MapField("ModelSequences", "ModelSequenceDefinitions", required: false),
|
||||||
new("Weapons", nameof(WeaponDefinitions), required: false),
|
new MapField("Weapons", "WeaponDefinitions", required: false),
|
||||||
new("Voices", nameof(VoiceDefinitions), required: false),
|
new MapField("Voices", "VoiceDefinitions", required: false),
|
||||||
new("Music", nameof(MusicDefinitions), required: false),
|
new MapField("Music", "MusicDefinitions", required: false),
|
||||||
new("Notifications", nameof(NotificationDefinitions), required: false),
|
new MapField("Notifications", "NotificationDefinitions", required: false),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format versions
|
// Format versions
|
||||||
@@ -198,18 +197,18 @@ namespace OpenRA
|
|||||||
public int2 MapSize { get; private set; }
|
public int2 MapSize { get; private set; }
|
||||||
|
|
||||||
// Player and actor yaml. Public for access by the map importers and lint checks.
|
// Player and actor yaml. Public for access by the map importers and lint checks.
|
||||||
public IReadOnlyCollection<MiniYamlNode> PlayerDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
public List<MiniYamlNode> PlayerDefinitions = new();
|
||||||
public IReadOnlyCollection<MiniYamlNode> ActorDefinitions = ImmutableArray<MiniYamlNode>.Empty;
|
public List<MiniYamlNode> ActorDefinitions = new();
|
||||||
|
|
||||||
// Custom map yaml. Public for access by the map importers and lint checks
|
// Custom map yaml. Public for access by the map importers and lint checks
|
||||||
public MiniYaml RuleDefinitions;
|
public readonly MiniYaml RuleDefinitions;
|
||||||
public MiniYaml FluentMessageDefinitions;
|
public readonly MiniYaml TranslationDefinitions;
|
||||||
public MiniYaml SequenceDefinitions;
|
public readonly MiniYaml SequenceDefinitions;
|
||||||
public MiniYaml ModelSequenceDefinitions;
|
public readonly MiniYaml ModelSequenceDefinitions;
|
||||||
public MiniYaml WeaponDefinitions;
|
public readonly MiniYaml WeaponDefinitions;
|
||||||
public MiniYaml VoiceDefinitions;
|
public readonly MiniYaml VoiceDefinitions;
|
||||||
public MiniYaml MusicDefinitions;
|
public readonly MiniYaml MusicDefinitions;
|
||||||
public MiniYaml NotificationDefinitions;
|
public readonly MiniYaml NotificationDefinitions;
|
||||||
|
|
||||||
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new();
|
||||||
|
|
||||||
@@ -275,10 +274,7 @@ namespace OpenRA
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var filename in contents)
|
foreach (var filename in contents)
|
||||||
if (filename.EndsWith(".yaml", StringComparison.Ordinal) ||
|
if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua") || (format >= 12 && filename == "map.png"))
|
||||||
filename.EndsWith(".bin", StringComparison.Ordinal) ||
|
|
||||||
filename.EndsWith(".lua", StringComparison.Ordinal) ||
|
|
||||||
(format >= 12 && filename == "map.png"))
|
|
||||||
streams.Add(package.GetStream(filename));
|
streams.Add(package.GetStream(filename));
|
||||||
|
|
||||||
// Take the SHA1
|
// Take the SHA1
|
||||||
@@ -361,15 +357,15 @@ namespace OpenRA
|
|||||||
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
|
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
|
||||||
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
|
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)
|
foreach (var field in YamlFields)
|
||||||
field.Deserialize(this, yaml);
|
field.Deserialize(this, yaml.Nodes);
|
||||||
|
|
||||||
if (MapFormat < SupportedMapFormat)
|
if (MapFormat < SupportedMapFormat)
|
||||||
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
|
||||||
|
|
||||||
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
|
||||||
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
|
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
|
||||||
|
|
||||||
Grid = modData.Manifest.Get<MapGrid>();
|
Grid = modData.Manifest.Get<MapGrid>();
|
||||||
|
|
||||||
@@ -611,7 +607,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
// Odd-height ramps get bumped up a level to the next even height layer
|
// Odd-height ramps get bumped up a level to the next even height layer
|
||||||
if ((height & 1) == 1 && Ramp[uv] != 0)
|
if ((height & 1) == 1 && Ramp[uv] != 0)
|
||||||
height++;
|
height += 1;
|
||||||
|
|
||||||
var candidates = new List<PPos>();
|
var candidates = new List<PPos>();
|
||||||
|
|
||||||
@@ -650,24 +646,21 @@ namespace OpenRA
|
|||||||
foreach (var file in Package.Contents)
|
foreach (var file in Package.Contents)
|
||||||
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
|
toPackage.Update(file, Package.GetStream(file).ReadAllBytes());
|
||||||
|
|
||||||
void UpdatePackage(string filename, byte[] data)
|
if (!LockPreview)
|
||||||
{
|
{
|
||||||
if (Package != toPackage)
|
var previewData = SavePreview();
|
||||||
toPackage.Update(filename, data);
|
if (Package != toPackage || !Enumerable.SequenceEqual(previewData, Package.GetStream("map.png").ReadAllBytes()))
|
||||||
else
|
toPackage.Update("map.png", previewData);
|
||||||
{
|
|
||||||
var stream = Package.GetStream(filename);
|
|
||||||
if (stream == null || !Enumerable.SequenceEqual(data, stream.ReadAllBytes()))
|
|
||||||
toPackage.Update(filename, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LockPreview)
|
|
||||||
UpdatePackage("map.png", SavePreview());
|
|
||||||
|
|
||||||
// Update the package with the new map data
|
// Update the package with the new map data
|
||||||
UpdatePackage("map.yaml", Encoding.UTF8.GetBytes(root.WriteToString()));
|
var textData = Encoding.UTF8.GetBytes(root.WriteToString());
|
||||||
UpdatePackage("map.bin", SaveBinaryData());
|
if (Package != toPackage || !Enumerable.SequenceEqual(textData, Package.GetStream("map.yaml").ReadAllBytes()))
|
||||||
|
toPackage.Update("map.yaml", textData);
|
||||||
|
|
||||||
|
var binaryData = SaveBinaryData();
|
||||||
|
if (Package != toPackage || !Enumerable.SequenceEqual(binaryData, Package.GetStream("map.bin").ReadAllBytes()))
|
||||||
|
toPackage.Update("map.bin", binaryData);
|
||||||
|
|
||||||
Package = toPackage;
|
Package = toPackage;
|
||||||
|
|
||||||
@@ -688,16 +681,16 @@ namespace OpenRA
|
|||||||
writer.Write((ushort)MapSize.Y);
|
writer.Write((ushort)MapSize.Y);
|
||||||
|
|
||||||
// Data offsets
|
// Data offsets
|
||||||
const int TilesOffset = 17;
|
var tilesOffset = 17;
|
||||||
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
var heightsOffset = Grid.MaximumTerrainHeight > 0 ? 3 * MapSize.X * MapSize.Y + 17 : 0;
|
||||||
var resourcesOffset = (Grid.MaximumTerrainHeight > 0 ? 4 : 3) * MapSize.X * MapSize.Y + 17;
|
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)heightsOffset);
|
||||||
writer.Write((uint)resourcesOffset);
|
writer.Write((uint)resourcesOffset);
|
||||||
|
|
||||||
// Tile data
|
// Tile data
|
||||||
if (TilesOffset != 0)
|
if (tilesOffset != 0)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < MapSize.X; i++)
|
for (var i = 0; i < MapSize.X; i++)
|
||||||
{
|
{
|
||||||
@@ -779,10 +772,19 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (Grid.MaximumTerrainHeight > 0)
|
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)
|
if (allBottom.Count > 0)
|
||||||
throw new InvalidDataException("The map has invalid boundaries");
|
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -799,7 +801,7 @@ namespace OpenRA
|
|||||||
bitmapWidth = 2 * bitmapWidth - 1;
|
bitmapWidth = 2 * bitmapWidth - 1;
|
||||||
|
|
||||||
var stride = bitmapWidth * 4;
|
var stride = bitmapWidth * 4;
|
||||||
const int PxStride = 4;
|
var pxStride = 4;
|
||||||
var minimapData = new byte[stride * height];
|
var minimapData = new byte[stride * height];
|
||||||
(Color Left, Color Right) terrainColor = default;
|
(Color Left, Color Right) terrainColor = default;
|
||||||
|
|
||||||
@@ -821,10 +823,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// Odd rows are shifted right by 1px
|
// Odd rows are shifted right by 1px
|
||||||
var dx = uv.V & 1;
|
var dx = uv.V & 1;
|
||||||
var xOffset = PxStride * (2 * x + dx);
|
var xOffset = pxStride * (2 * x + dx);
|
||||||
if (x + dx > 0)
|
if (x + dx > 0)
|
||||||
{
|
{
|
||||||
var z = y * stride + xOffset - PxStride;
|
var z = y * stride + xOffset - pxStride;
|
||||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||||
minimapData[z++] = c.R;
|
minimapData[z++] = c.R;
|
||||||
minimapData[z++] = c.G;
|
minimapData[z++] = c.G;
|
||||||
@@ -844,7 +846,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var z = y * stride + PxStride * x;
|
var z = y * stride + pxStride * x;
|
||||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||||
minimapData[z++] = c.R;
|
minimapData[z++] = c.R;
|
||||||
minimapData[z++] = c.G;
|
minimapData[z++] = c.G;
|
||||||
@@ -858,28 +860,6 @@ namespace OpenRA
|
|||||||
return png.Save();
|
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)
|
public bool Contains(CPos cell)
|
||||||
{
|
{
|
||||||
if (Grid.Type == MapGridType.RectangularIsometric)
|
if (Grid.Type == MapGridType.RectangularIsometric)
|
||||||
@@ -1200,7 +1180,7 @@ namespace OpenRA
|
|||||||
// Project this guessed cell and take the first available cell
|
// Project this guessed cell and take the first available cell
|
||||||
// If it is projected outside the layer, then make another guess.
|
// If it is projected outside the layer, then make another guess.
|
||||||
var allProjected = ProjectedCellsCovering(uv);
|
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));
|
: new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom));
|
||||||
|
|
||||||
// Clamp the projected cell to the map area
|
// Clamp the projected cell to the map area
|
||||||
@@ -1269,7 +1249,7 @@ namespace OpenRA
|
|||||||
PPos edge;
|
PPos edge;
|
||||||
if (allProjected.Length > 0)
|
if (allProjected.Length > 0)
|
||||||
{
|
{
|
||||||
var puv = allProjected[0];
|
var puv = allProjected.First();
|
||||||
var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right;
|
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 verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom;
|
||||||
|
|
||||||
@@ -1369,18 +1349,13 @@ namespace OpenRA
|
|||||||
throw new ArgumentOutOfRangeException(nameof(maxRange),
|
throw new ArgumentOutOfRangeException(nameof(maxRange),
|
||||||
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
|
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
|
||||||
|
|
||||||
return FindTilesInAnnulus();
|
for (var i = minRange; i <= maxRange; i++)
|
||||||
|
|
||||||
IEnumerable<CPos> FindTilesInAnnulus()
|
|
||||||
{
|
{
|
||||||
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))
|
||||||
var t = offset + center;
|
yield return t;
|
||||||
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
|
|
||||||
yield return t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1427,11 +1402,11 @@ namespace OpenRA
|
|||||||
return modData.DefaultFileSystem.Exists(filename);
|
return modData.DefaultFileSystem.Exists(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsExternalFile(string filename)
|
public bool IsExternalModFile(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
if (filename.Contains('|'))
|
if (filename.Contains('|'))
|
||||||
return modData.DefaultFileSystem.IsExternalFile(filename);
|
return modData.DefaultFileSystem.IsExternalModFile(filename);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
@@ -39,7 +38,7 @@ namespace OpenRA
|
|||||||
readonly object syncRoot = new();
|
readonly object syncRoot = new();
|
||||||
readonly Queue<MapPreview> generateMinimap = new();
|
readonly Queue<MapPreview> generateMinimap = new();
|
||||||
|
|
||||||
public HashSet<string> StringPool { get; } = new();
|
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
readonly List<MapDirectoryTracker> mapDirectoryTrackers = new();
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ namespace OpenRA
|
|||||||
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value);
|
||||||
|
|
||||||
IReadOnlyPackage package;
|
IReadOnlyPackage package;
|
||||||
var optional = name.StartsWith('~');
|
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
@@ -107,7 +106,7 @@ namespace OpenRA
|
|||||||
// HACK: If the path is inside the support directory then we may need to create it
|
// HACK: If the path is inside the support directory then we may need to create it
|
||||||
// Assume that the path is a directory if there is not an existing file with the same name
|
// Assume that the path is a directory if there is not an existing file with the same name
|
||||||
var resolved = Platform.ResolvePath(name);
|
var resolved = Platform.ResolvePath(name);
|
||||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && !File.Exists(resolved))
|
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
|
||||||
Directory.CreateDirectory(resolved);
|
Directory.CreateDirectory(resolved);
|
||||||
|
|
||||||
package = modData.ModFiles.OpenPackage(name);
|
package = modData.ModFiles.OpenPackage(name);
|
||||||
@@ -141,8 +140,7 @@ namespace OpenRA
|
|||||||
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap,
|
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
IEnumerable<List<MiniYamlNode>> modDataRules)
|
|
||||||
{
|
{
|
||||||
IReadOnlyPackage mapPackage = null;
|
IReadOnlyPackage mapPackage = null;
|
||||||
try
|
try
|
||||||
@@ -155,9 +153,6 @@ namespace OpenRA
|
|||||||
var uid = Map.ComputeUID(mapPackage);
|
var uid = Map.ComputeUID(mapPackage);
|
||||||
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
|
||||||
|
|
||||||
// Freeing the package to save memory if there is a lot of Maps
|
|
||||||
previews[uid].DisposePackage();
|
|
||||||
|
|
||||||
if (oldMap != uid)
|
if (oldMap != uid)
|
||||||
{
|
{
|
||||||
LastModifiedMap = uid;
|
LastModifiedMap = uid;
|
||||||
@@ -195,13 +190,13 @@ namespace OpenRA
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var name = kv.Key;
|
var name = kv.Key;
|
||||||
var optional = name.StartsWith('~');
|
var optional = name.StartsWith("~", StringComparison.Ordinal);
|
||||||
if (optional)
|
if (optional)
|
||||||
name = name[1..];
|
name = name[1..];
|
||||||
|
|
||||||
// Don't try to open the map directory in the support directory if it doesn't exist
|
// Don't try to open the map directory in the support directory if it doesn't exist
|
||||||
var resolved = Platform.ResolvePath(name);
|
var resolved = Platform.ResolvePath(name);
|
||||||
if (resolved.StartsWith(Platform.SupportDir, StringComparison.Ordinal) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
|
||||||
@@ -228,8 +223,7 @@ namespace OpenRA
|
|||||||
yield return mapPackage;
|
yield return mapPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids,
|
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
|
||||||
Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
|
|
||||||
{
|
{
|
||||||
var queryUids = uids.Distinct()
|
var queryUids = uids.Distinct()
|
||||||
.Where(uid => uid != null)
|
.Where(uid => uid != null)
|
||||||
@@ -239,67 +233,44 @@ namespace OpenRA
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var uid in queryUids)
|
foreach (var uid in queryUids)
|
||||||
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null, null);
|
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null);
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var client = HttpClientFactory.Create();
|
var client = HttpClientFactory.Create();
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
// Limit each query to 50 maps at a time to avoid request size limits
|
// Limit each query to 50 maps at a time to avoid request size limits
|
||||||
for (var i = 0; i < queryUids.Count; i += 50)
|
for (var i = 0; i < queryUids.Count; i += 50)
|
||||||
{
|
{
|
||||||
var batchUids = queryUids.Skip(i).Take(50).ToList();
|
var batchUids = queryUids.Skip(i).Take(50).ToList();
|
||||||
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
|
||||||
|
try
|
||||||
using (new PerfTimer("RemoteMapDetails"))
|
|
||||||
{
|
{
|
||||||
try
|
var httpResponseMessage = await client.GetAsync(url);
|
||||||
|
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
var yaml = MiniYaml.FromStream(result);
|
||||||
|
foreach (var kv in yaml)
|
||||||
|
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
|
||||||
|
|
||||||
|
foreach (var uid in batchUids)
|
||||||
{
|
{
|
||||||
await using (var resultStream = await client.GetStreamAsync(url))
|
var p = previews[uid];
|
||||||
{
|
if (p.Status != MapStatus.DownloadAvailable)
|
||||||
using (var resultReader = new StreamReader(resultStream))
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var line = await resultReader.ReadLineAsync();
|
|
||||||
if (line == null || !line.StartsWith('\t'))
|
|
||||||
{
|
|
||||||
var yaml = MiniYaml.FromString(buffer.ToString(), url, stringPool: stringPool);
|
|
||||||
buffer.Clear();
|
|
||||||
foreach (var kv in yaml)
|
|
||||||
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
|
|
||||||
|
|
||||||
if (line == null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Append(line);
|
|
||||||
buffer.Append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var uid in batchUids)
|
|
||||||
{
|
|
||||||
var p = previews[uid];
|
|
||||||
if (p.Status != MapStatus.DownloadAvailable)
|
|
||||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
Log.Write("debug", "Remote map query failed with error:");
|
{
|
||||||
Log.Write("debug", e);
|
Log.Write("debug", "Remote map query failed with error:");
|
||||||
Log.Write("debug", $"URL was: {url}");
|
Log.Write("debug", e);
|
||||||
|
Log.Write("debug", $"URL was: {url}");
|
||||||
|
|
||||||
foreach (var uid in batchUids)
|
foreach (var uid in batchUids)
|
||||||
{
|
{
|
||||||
var p = previews[uid];
|
var p = previews[uid];
|
||||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null, null);
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||||
mapQueryFailed?.Invoke(p);
|
mapQueryFailed?.Invoke(p);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,11 +282,11 @@ namespace OpenRA
|
|||||||
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
Log.Write("debug", "MapCache.LoadAsyncInternal started");
|
||||||
|
|
||||||
// Milliseconds to wait on one loop when nothing to do
|
// Milliseconds to wait on one loop when nothing to do
|
||||||
const int EmptyDelay = 50;
|
var emptyDelay = 50;
|
||||||
|
|
||||||
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
// Keep the thread alive for at least 5 seconds after the last minimap generation
|
||||||
const int MaxKeepAlive = 5000 / EmptyDelay;
|
var maxKeepAlive = 5000 / emptyDelay;
|
||||||
var keepAlive = MaxKeepAlive;
|
var keepAlive = maxKeepAlive;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -335,11 +306,11 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (todo.Count == 0)
|
if (todo.Count == 0)
|
||||||
{
|
{
|
||||||
Thread.Sleep(EmptyDelay);
|
Thread.Sleep(emptyDelay);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
keepAlive = MaxKeepAlive;
|
keepAlive = maxKeepAlive;
|
||||||
|
|
||||||
// Render the minimap into the shared sheet
|
// Render the minimap into the shared sheet
|
||||||
foreach (var p in todo)
|
foreach (var p in todo)
|
||||||
@@ -377,8 +348,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
while (this[uid].Status != MapStatus.Available)
|
while (this[uid].Status != MapStatus.Available)
|
||||||
{
|
{
|
||||||
if (mapUpdates.TryGetValue(uid, out var newUid))
|
if (mapUpdates.ContainsKey(uid))
|
||||||
uid = newUid;
|
uid = mapUpdates[uid];
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -435,16 +406,10 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
UpdateMaps();
|
UpdateMaps();
|
||||||
var map = string.IsNullOrEmpty(initialUid) ? null : previews[initialUid];
|
var map = string.IsNullOrEmpty(initialUid) ? null : previews[initialUid];
|
||||||
if (map == null ||
|
if (map == null || map.Status != MapStatus.Available || !map.Visibility.HasFlag(MapVisibility.Lobby) || (map.Class != MapClassification.System && map.Class != MapClassification.User))
|
||||||
map.Status != MapStatus.Available ||
|
|
||||||
!map.Visibility.HasFlag(MapVisibility.Lobby) ||
|
|
||||||
(map.Class != MapClassification.System && map.Class != MapClassification.User))
|
|
||||||
{
|
{
|
||||||
var selected = previews.Values.Where(IsSuitableInitialMap).RandomOrDefault(random) ??
|
var selected = previews.Values.Where(IsSuitableInitialMap).RandomOrDefault(random) ??
|
||||||
previews.Values.FirstOrDefault(m =>
|
previews.Values.FirstOrDefault(m => m.Status == MapStatus.Available && m.Visibility.HasFlag(MapVisibility.Lobby) && (m.Class == MapClassification.System || m.Class == MapClassification.User));
|
||||||
m.Status == MapStatus.Available &&
|
|
||||||
m.Visibility.HasFlag(MapVisibility.Lobby) &&
|
|
||||||
(m.Class == MapClassification.System || m.Class == MapClassification.User));
|
|
||||||
return selected == null ? string.Empty : selected.Uid;
|
return selected == null ? string.Empty : selected.Uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace OpenRA
|
|||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.BottomRight.U)
|
if (u > r.BottomRight.U)
|
||||||
{
|
{
|
||||||
v++;
|
v += 1;
|
||||||
u = r.TopLeft.U;
|
u = r.TopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -53,8 +53,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MPos Current { get; private set; }
|
public MPos Current { get; private set; }
|
||||||
readonly object IEnumerator.Current => Current;
|
object IEnumerator.Current => Current;
|
||||||
public readonly void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
|
public MapCoordsRegion(MPos mapTopLeft, MPos mapBottomRight)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace OpenRA
|
|||||||
dirty = false;
|
dirty = false;
|
||||||
foreach (var mapAction in mapActionQueue)
|
foreach (var mapAction in mapActionQueue)
|
||||||
{
|
{
|
||||||
var map = mapcache.FirstOrDefault(x => x.PackageName == mapAction.Key && x.Status == MapStatus.Available);
|
var map = mapcache.FirstOrDefault(x => x.Package?.Name == mapAction.Key && x.Status == MapStatus.Available);
|
||||||
if (map != null)
|
if (map != null)
|
||||||
{
|
{
|
||||||
if (mapAction.Value == MapAction.Delete)
|
if (mapAction.Value == MapAction.Delete)
|
||||||
|
|||||||
@@ -116,12 +116,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public readonly WVec[] SubCellOffsets =
|
public readonly WVec[] SubCellOffsets =
|
||||||
{
|
{
|
||||||
new(0, 0, 0), // full cell - index 0
|
new WVec(0, 0, 0), // full cell - index 0
|
||||||
new(-299, -256, 0), // top left - index 1
|
new WVec(-299, -256, 0), // top left - index 1
|
||||||
new(256, -256, 0), // top right - index 2
|
new WVec(256, -256, 0), // top right - index 2
|
||||||
new(0, 0, 0), // center - index 3
|
new WVec(0, 0, 0), // center - index 3
|
||||||
new(-299, 256, 0), // bottom left - index 4
|
new WVec(-299, 256, 0), // bottom left - index 4
|
||||||
new(256, 256, 0), // bottom right - index 5
|
new WVec(256, 256, 0), // bottom right - index 5
|
||||||
};
|
};
|
||||||
|
|
||||||
public CellRamp[] Ramps { get; }
|
public CellRamp[] Ramps { get; }
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ namespace OpenRA
|
|||||||
public readonly string rules;
|
public readonly string rules;
|
||||||
public readonly string players_block;
|
public readonly string players_block;
|
||||||
public readonly int mapformat;
|
public readonly int mapformat;
|
||||||
public readonly string game_mod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MapPreview : IDisposable, IReadOnlyFileSystem
|
public sealed class MapPreview : IDisposable, IReadOnlyFileSystem
|
||||||
@@ -90,9 +89,8 @@ namespace OpenRA
|
|||||||
public MiniYaml NotificationDefinitions;
|
public MiniYaml NotificationDefinitions;
|
||||||
public MiniYaml SequenceDefinitions;
|
public MiniYaml SequenceDefinitions;
|
||||||
public MiniYaml ModelSequenceDefinitions;
|
public MiniYaml ModelSequenceDefinitions;
|
||||||
public MiniYaml FluentMessageDefinitions;
|
|
||||||
|
|
||||||
public FluentBundle FluentBundle { get; private set; }
|
public Translation Translation { get; private set; }
|
||||||
public ActorInfo WorldActorInfo { get; private set; }
|
public ActorInfo WorldActorInfo { get; private set; }
|
||||||
public ActorInfo PlayerActorInfo { get; private set; }
|
public ActorInfo PlayerActorInfo { get; private set; }
|
||||||
|
|
||||||
@@ -122,32 +120,13 @@ namespace OpenRA
|
|||||||
NotificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
NotificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
||||||
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||||
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
||||||
FluentMessageDefinitions = LoadRuleSection(yaml, "FluentMessages");
|
|
||||||
|
Translation = yaml.TryGetValue("Translations", out var node) && node != null
|
||||||
|
? new Translation(Game.Settings.Player.Language, FieldLoader.GetValue<string[]>("value", node.Value), fileSystem)
|
||||||
|
: null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (FluentMessageDefinitions != null)
|
|
||||||
{
|
|
||||||
var files = Array.Empty<string>();
|
|
||||||
if (FluentMessageDefinitions.Value != null)
|
|
||||||
files = FieldLoader.GetValue<string[]>("value", FluentMessageDefinitions.Value);
|
|
||||||
|
|
||||||
string text = null;
|
|
||||||
if (FluentMessageDefinitions.Nodes.Length > 0)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
foreach (var node in FluentMessageDefinitions.Nodes)
|
|
||||||
if (node.Key == "base64")
|
|
||||||
builder.Append(Encoding.UTF8.GetString(Convert.FromBase64String(node.Value.Value)));
|
|
||||||
|
|
||||||
text = builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
FluentBundle = new FluentBundle(modData.Manifest.FluentCulture, files, fileSystem, text);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
FluentBundle = null;
|
|
||||||
|
|
||||||
// PERF: Implement a minimal custom loader for custom world and player actors to minimize loading time
|
// PERF: Implement a minimal custom loader for custom world and player actors to minimize loading time
|
||||||
// This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^)
|
// This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^)
|
||||||
if (RuleDefinitions != null)
|
if (RuleDefinitions != null)
|
||||||
@@ -160,22 +139,15 @@ namespace OpenRA
|
|||||||
files = files.Append(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
|
||||||
var sources =
|
var sources =
|
||||||
modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList())
|
modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList())
|
||||||
.Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool).Where(IsLoadableRuleDefinition).ToList()));
|
.Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList()));
|
||||||
if (RuleDefinitions.Nodes.Length > 0)
|
if (RuleDefinitions.Nodes.Count > 0)
|
||||||
sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList());
|
sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList());
|
||||||
|
|
||||||
var yamlNodes = MiniYaml.Merge(sources);
|
var yamlNodes = MiniYaml.Merge(sources);
|
||||||
WorldActorInfo = new ActorInfo(
|
WorldActorInfo = new ActorInfo(modData.ObjectCreator, "world", yamlNodes.First(n => string.Equals(n.Key, "world", StringComparison.InvariantCultureIgnoreCase)).Value);
|
||||||
modData.ObjectCreator,
|
PlayerActorInfo = new ActorInfo(modData.ObjectCreator, "player", yamlNodes.First(n => string.Equals(n.Key, "player", StringComparison.InvariantCultureIgnoreCase)).Value);
|
||||||
"world",
|
|
||||||
yamlNodes.First(n => string.Equals(n.Key, "world", StringComparison.InvariantCultureIgnoreCase)).Value);
|
|
||||||
PlayerActorInfo = new ActorInfo(
|
|
||||||
modData.ObjectCreator,
|
|
||||||
"player",
|
|
||||||
yamlNodes.First(n => string.Equals(n.Key, "player", StringComparison.InvariantCultureIgnoreCase)).Value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,19 +172,7 @@ namespace OpenRA
|
|||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
|
|
||||||
public readonly string Uid;
|
public readonly string Uid;
|
||||||
public string PackageName { get; private set; }
|
public IReadOnlyPackage Package { get; private set; }
|
||||||
IReadOnlyPackage package;
|
|
||||||
public IReadOnlyPackage Package
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
package ??= parentPackage.OpenPackage(PackageName, modData.ModFiles);
|
|
||||||
return package;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set => package = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
IReadOnlyPackage parentPackage;
|
IReadOnlyPackage parentPackage;
|
||||||
|
|
||||||
volatile InnerData innerData;
|
volatile InnerData innerData;
|
||||||
@@ -244,31 +204,16 @@ namespace OpenRA
|
|||||||
public int DownloadPercentage { get; private set; }
|
public int DownloadPercentage { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Functionality mirrors <see cref="FluentProvider.GetMessage"/>, except instead of using
|
/// Functionality mirrors <see cref="TranslationProvider.GetString"/>, except instead of using
|
||||||
/// loaded <see cref="Map"/>'s fluent bundle as backup, we use this <see cref="MapPreview"/>'s.
|
/// loaded <see cref="Map"/>'s translations as backup, we use this <see cref="MapPreview"/>'s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string GetMessage(string key, object[] args = null)
|
public string GetLocalisedString(string key, IDictionary<string, object> args = null)
|
||||||
{
|
{
|
||||||
if (TryGetMessage(key, out var message, args))
|
// PERF: instead of loading mod level Translation per each MapPreview, reuse the already loaded one in TranslationProvider.
|
||||||
|
if (TranslationProvider.TryGetModString(key, out var message, args))
|
||||||
return message;
|
return message;
|
||||||
|
|
||||||
return key;
|
return innerData.Translation?.GetString(key, args) ?? key;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Functionality mirrors <see cref="FluentProvider.TryGetMessage"/>, except instead of using
|
|
||||||
/// loaded <see cref="Map"/>'s fluent bundle as backup, we use this <see cref="MapPreview"/>'s.
|
|
||||||
/// </summary>
|
|
||||||
public bool TryGetMessage(string key, out string message, object[] args = null)
|
|
||||||
{
|
|
||||||
// PERF: instead of loading mod level strings per each MapPreview, reuse the already loaded one in FluentProvider.
|
|
||||||
if (FluentProvider.TryGetModMessage(key, out message, args))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (innerData.FluentBundle == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return innerData.FluentBundle.TryGetMessage(key, out message, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Sprite minimap;
|
Sprite minimap;
|
||||||
@@ -339,7 +284,7 @@ namespace OpenRA
|
|||||||
cache = modData.MapCache;
|
cache = modData.MapCache;
|
||||||
|
|
||||||
Uid = map.Uid;
|
Uid = map.Uid;
|
||||||
PackageName = map.Package.Name;
|
Package = map.Package;
|
||||||
|
|
||||||
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
var mapPlayers = new MapPlayers(map.PlayerDefinitions);
|
||||||
var spawns = new List<CPos>();
|
var spawns = new List<CPos>();
|
||||||
@@ -370,7 +315,7 @@ namespace OpenRA
|
|||||||
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
innerData.SetCustomRules(modData, this, new Dictionary<string, MiniYaml>()
|
||||||
{
|
{
|
||||||
{ "Rules", map.RuleDefinitions },
|
{ "Rules", map.RuleDefinitions },
|
||||||
{ "FluentMessages", map.FluentMessageDefinitions },
|
{ "Translations", map.TranslationDefinitions },
|
||||||
{ "Weapons", map.WeaponDefinitions },
|
{ "Weapons", map.WeaponDefinitions },
|
||||||
{ "Voices", map.VoiceDefinitions },
|
{ "Voices", map.VoiceDefinitions },
|
||||||
{ "Music", map.MusicDefinitions },
|
{ "Music", map.MusicDefinitions },
|
||||||
@@ -380,8 +325,7 @@ namespace OpenRA
|
|||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification,
|
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
|
||||||
string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
|
|
||||||
{
|
{
|
||||||
Dictionary<string, MiniYaml> yaml;
|
Dictionary<string, MiniYaml> yaml;
|
||||||
using (var yamlStream = p.GetStream("map.yaml"))
|
using (var yamlStream = p.GetStream("map.yaml"))
|
||||||
@@ -389,10 +333,10 @@ namespace OpenRA
|
|||||||
if (yamlStream == null)
|
if (yamlStream == null)
|
||||||
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
throw new FileNotFoundException("Required file map.yaml not present in this map");
|
||||||
|
|
||||||
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageName = p.Name;
|
Package = p;
|
||||||
parentPackage = parent;
|
parentPackage = parent;
|
||||||
|
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
@@ -483,7 +427,7 @@ namespace OpenRA
|
|||||||
innerData = newData;
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, string[] mapCompatibility, Action<MapPreview> parseMetadata = null)
|
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
||||||
{
|
{
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
newData.Status = status;
|
newData.Status = status;
|
||||||
@@ -530,19 +474,11 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
||||||
newData.Players = new MapPlayers(MiniYaml.FromString(playersString,
|
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
||||||
$"{yaml.NodeWithKey(nameof(r.players_block)).Location.Name}:{nameof(r.players_block)}"));
|
|
||||||
|
|
||||||
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
||||||
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString,
|
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
||||||
$"{yaml.NodeWithKey(nameof(r.rules)).Location.Name}:{nameof(r.rules)}")).ToDictionary();
|
|
||||||
newData.SetCustomRules(modData, this, rulesYaml, null);
|
newData.SetCustomRules(modData, this, rulesYaml, null);
|
||||||
|
|
||||||
// Map is for a different mod: update its information so it can be displayed
|
|
||||||
// in the cross-mod server browser UI, but mark it as unavailable so it can't
|
|
||||||
// be selected in a server for the current mod.
|
|
||||||
if (!mapCompatibility.Contains(r.game_mod))
|
|
||||||
newData.Status = MapStatus.Unavailable;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -641,15 +577,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DisposePackage();
|
if (Package != null)
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposePackage()
|
|
||||||
{
|
|
||||||
if (package != null)
|
|
||||||
{
|
{
|
||||||
package.Dispose();
|
Package.Dispose();
|
||||||
package = null;
|
Package = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,11 +627,11 @@ namespace OpenRA
|
|||||||
return modData.DefaultFileSystem.Exists(filename);
|
return modData.DefaultFileSystem.Exists(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IReadOnlyFileSystem.IsExternalFile(string filename)
|
bool IReadOnlyFileSystem.IsExternalModFile(string filename)
|
||||||
{
|
{
|
||||||
// Explicit package paths never refer to a map
|
// Explicit package paths never refer to a map
|
||||||
if (filename.Contains('|'))
|
if (filename.Contains('|'))
|
||||||
return modData.DefaultFileSystem.IsExternalFile(filename);
|
return modData.DefaultFileSystem.IsExternalModFile(filename);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
@@ -54,15 +53,5 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
return Bounds.Contains(uv.U, uv.V);
|
return Bounds.Contains(uv.U, uv.V);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IndexOf(T value, int startIndex)
|
|
||||||
{
|
|
||||||
return Array.IndexOf(Entries, value, startIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAll(T value)
|
|
||||||
{
|
|
||||||
Array.Fill(Entries, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
u++;
|
u += 1;
|
||||||
|
|
||||||
// Check for column overflow
|
// Check for column overflow
|
||||||
if (u > r.BottomRight.U)
|
if (u > r.BottomRight.U)
|
||||||
{
|
{
|
||||||
v++;
|
v += 1;
|
||||||
u = r.TopLeft.U;
|
u = r.TopLeft.U;
|
||||||
|
|
||||||
// Check for row overflow
|
// Check for row overflow
|
||||||
@@ -118,8 +118,8 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PPos Current { get; private set; }
|
public PPos Current { get; private set; }
|
||||||
readonly object IEnumerator.Current => Current;
|
object IEnumerator.Current => Current;
|
||||||
public readonly void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,36 +20,18 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public static class MiniYamlExts
|
public static class MiniYamlExts
|
||||||
{
|
{
|
||||||
public static void WriteToFile(this IEnumerable<MiniYamlNode> y, string filename)
|
public static void WriteToFile(this List<MiniYamlNode> y, string filename)
|
||||||
{
|
{
|
||||||
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string WriteToString(this IEnumerable<MiniYamlNode> y)
|
public static string WriteToString(this List<MiniYamlNode> y)
|
||||||
{
|
{
|
||||||
// Remove all trailing newlines and restore the final EOF newline
|
// Remove all trailing newlines and restore the final EOF newline
|
||||||
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNode> y)
|
public static IEnumerable<string> ToLines(this List<MiniYamlNode> y)
|
||||||
{
|
|
||||||
foreach (var kv in y)
|
|
||||||
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
|
||||||
yield return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteToFile(this IEnumerable<MiniYamlNodeBuilder> y, string filename)
|
|
||||||
{
|
|
||||||
File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string WriteToString(this IEnumerable<MiniYamlNodeBuilder> y)
|
|
||||||
{
|
|
||||||
// Remove all trailing newlines and restore the final EOF newline
|
|
||||||
return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> ToLines(this IEnumerable<MiniYamlNodeBuilder> y)
|
|
||||||
{
|
{
|
||||||
foreach (var kv in y)
|
foreach (var kv in y)
|
||||||
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment))
|
||||||
@@ -61,29 +43,22 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
public readonly struct SourceLocation
|
public readonly struct SourceLocation
|
||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Filename;
|
||||||
public readonly int Line;
|
public readonly int Line;
|
||||||
|
|
||||||
public SourceLocation(string name, int line)
|
public SourceLocation(string filename, int line)
|
||||||
{
|
{
|
||||||
Name = name;
|
Filename = filename;
|
||||||
Line = line;
|
Line = line;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() { return $"{Name}:{Line}"; }
|
public override string ToString() { return $"{Filename}:{Line}"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly SourceLocation Location;
|
public SourceLocation Location;
|
||||||
public readonly string Key;
|
public string Key;
|
||||||
public readonly MiniYaml Value;
|
public MiniYaml Value;
|
||||||
public readonly string Comment;
|
public string Comment;
|
||||||
|
|
||||||
public MiniYamlNode WithValue(MiniYaml value)
|
|
||||||
{
|
|
||||||
if (Value == value)
|
|
||||||
return this;
|
|
||||||
return new MiniYamlNode(Key, value, Comment, Location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNode(string k, MiniYaml v, string c = null)
|
public MiniYamlNode(string k, MiniYaml v, string c = null)
|
||||||
{
|
{
|
||||||
@@ -99,15 +74,26 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, string c = null)
|
public MiniYamlNode(string k, string v, string c = null)
|
||||||
: this(k, new MiniYaml(v, Enumerable.Empty<MiniYamlNode>()), c) { }
|
: this(k, v, c, null) { }
|
||||||
|
|
||||||
public MiniYamlNode(string k, string v, IEnumerable<MiniYamlNode> n)
|
public MiniYamlNode(string k, string v, List<MiniYamlNode> n)
|
||||||
: this(k, new MiniYaml(v, n), null) { }
|
: this(k, new MiniYaml(v, n), null) { }
|
||||||
|
|
||||||
|
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n)
|
||||||
|
: this(k, new MiniYaml(v, n), c) { }
|
||||||
|
|
||||||
|
public MiniYamlNode(string k, string v, string c, List<MiniYamlNode> n, SourceLocation loc)
|
||||||
|
: this(k, new MiniYaml(v, n), c, loc) { }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{{YamlNode: {Key} @ {Location}}}";
|
return $"{{YamlNode: {Key} @ {Location}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MiniYamlNode Clone()
|
||||||
|
{
|
||||||
|
return new MiniYamlNode(Key, Value.Clone(), Comment, Location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MiniYaml
|
public sealed class MiniYaml
|
||||||
@@ -115,59 +101,15 @@ namespace OpenRA
|
|||||||
const int SpacesPerLevel = 4;
|
const int SpacesPerLevel = 4;
|
||||||
static readonly Func<string, string> StringIdentity = s => s;
|
static readonly Func<string, string> StringIdentity = s => s;
|
||||||
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
static readonly Func<MiniYaml, MiniYaml> MiniYamlIdentity = my => my;
|
||||||
static readonly Dictionary<string, MiniYamlNode> ConflictScratch = new();
|
public string Value;
|
||||||
|
public List<MiniYamlNode> Nodes;
|
||||||
|
|
||||||
public readonly string Value;
|
public MiniYaml Clone()
|
||||||
public readonly ImmutableArray<MiniYamlNode> Nodes;
|
|
||||||
|
|
||||||
public MiniYaml WithValue(string value)
|
|
||||||
{
|
{
|
||||||
if (Value == value)
|
var clonedNodes = new List<MiniYamlNode>(Nodes.Count);
|
||||||
return this;
|
|
||||||
return new MiniYaml(value, Nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYaml WithNodes(IEnumerable<MiniYamlNode> nodes)
|
|
||||||
{
|
|
||||||
if (nodes is ImmutableArray<MiniYamlNode> n && Nodes == n)
|
|
||||||
return this;
|
|
||||||
return new MiniYaml(Value, nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYaml WithNodesAppended(IEnumerable<MiniYamlNode> nodes)
|
|
||||||
{
|
|
||||||
var newNodes = Nodes.AddRange(nodes);
|
|
||||||
if (Nodes == newNodes)
|
|
||||||
return this;
|
|
||||||
return new MiniYaml(Value, newNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNode NodeWithKey(string key)
|
|
||||||
{
|
|
||||||
var result = NodeWithKeyOrDefault(key);
|
|
||||||
if (result == null)
|
|
||||||
throw new InvalidDataException($"No node with key '{key}'");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNode NodeWithKeyOrDefault(string key)
|
|
||||||
{
|
|
||||||
// PERF: Avoid LINQ.
|
|
||||||
var first = true;
|
|
||||||
MiniYamlNode result = null;
|
|
||||||
foreach (var node in Nodes)
|
foreach (var node in Nodes)
|
||||||
{
|
clonedNodes.Add(node.Clone());
|
||||||
if (node.Key != key)
|
return new MiniYaml(Value, clonedNodes);
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!first)
|
|
||||||
throw new InvalidDataException($"Duplicate key '{node.Key}' in {node.Location}");
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
result = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, MiniYaml> ToDictionary()
|
public Dictionary<string, MiniYaml> ToDictionary()
|
||||||
@@ -183,7 +125,7 @@ namespace OpenRA
|
|||||||
public Dictionary<TKey, TElement> ToDictionary<TKey, TElement>(
|
public Dictionary<TKey, TElement> ToDictionary<TKey, TElement>(
|
||||||
Func<string, TKey> keySelector, Func<MiniYaml, TElement> elementSelector)
|
Func<string, TKey> keySelector, Func<MiniYaml, TElement> elementSelector)
|
||||||
{
|
{
|
||||||
var ret = new Dictionary<TKey, TElement>(Nodes.Length);
|
var ret = new Dictionary<TKey, TElement>(Nodes.Count);
|
||||||
foreach (var y in Nodes)
|
foreach (var y in Nodes)
|
||||||
{
|
{
|
||||||
var key = keySelector(y.Key);
|
var key = keySelector(y.Key);
|
||||||
@@ -196,27 +138,28 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MiniYaml(string value)
|
public MiniYaml(string value)
|
||||||
: this(value, Enumerable.Empty<MiniYamlNode>()) { }
|
: this(value, null) { }
|
||||||
|
|
||||||
public MiniYaml(string value, IEnumerable<MiniYamlNode> nodes)
|
public MiniYaml(string value, List<MiniYamlNode> nodes)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Nodes = ImmutableArray.CreateRange(nodes);
|
Nodes = nodes ?? new List<MiniYamlNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string name, bool discardCommentsAndWhitespace, HashSet<string> stringPool)
|
public static List<MiniYamlNode> NodesOrEmpty(MiniYaml y, string s)
|
||||||
{
|
{
|
||||||
// YAML config often contains repeated strings for key, values, comments.
|
var nd = y.ToDictionary();
|
||||||
// Pool these strings so we only need one copy of each unique string.
|
return nd.TryGetValue(s, out var v) ? v.Nodes : new List<MiniYamlNode>();
|
||||||
// This saves on long-term memory usage as parsed values can often live a long time.
|
}
|
||||||
// A caller can also provide a pool as input, allowing de-duplication across multiple parses.
|
|
||||||
stringPool ??= new HashSet<string>();
|
|
||||||
|
|
||||||
var result = new List<List<MiniYamlNode>>
|
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
|
||||||
|
{
|
||||||
|
stringPool ??= new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var levels = new List<List<MiniYamlNode>>
|
||||||
{
|
{
|
||||||
new()
|
new List<MiniYamlNode>()
|
||||||
};
|
};
|
||||||
var parsedLines = new List<(int Level, string Key, string Value, string Comment, MiniYamlNode.SourceLocation Location)>();
|
|
||||||
|
|
||||||
var lineNo = 0;
|
var lineNo = 0;
|
||||||
foreach (var ll in lines)
|
foreach (var ll in lines)
|
||||||
@@ -232,7 +175,7 @@ namespace OpenRA
|
|||||||
ReadOnlySpan<char> key = default;
|
ReadOnlySpan<char> key = default;
|
||||||
ReadOnlySpan<char> value = default;
|
ReadOnlySpan<char> value = default;
|
||||||
ReadOnlySpan<char> comment = default;
|
ReadOnlySpan<char> comment = default;
|
||||||
var location = new MiniYamlNode.SourceLocation(name, lineNo);
|
var location = new MiniYamlNode.SourceLocation(filename, lineNo);
|
||||||
|
|
||||||
if (line.Length > 0)
|
if (line.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -263,6 +206,15 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (levels.Count <= level)
|
||||||
|
throw new YamlException($"Bad indent in miniyaml at {location}");
|
||||||
|
|
||||||
|
while (levels.Count > level + 1)
|
||||||
|
{
|
||||||
|
levels[^1].TrimExcess();
|
||||||
|
levels.RemoveAt(levels.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Extract key, value, comment from line as `<key>: <value>#<comment>`
|
// Extract key, value, comment from line as `<key>: <value>#<comment>`
|
||||||
// The # character is allowed in the value if escaped (\#).
|
// The # character is allowed in the value if escaped (\#).
|
||||||
// Leading and trailing whitespace is always trimmed from keys.
|
// Leading and trailing whitespace is always trimmed from keys.
|
||||||
@@ -284,7 +236,7 @@ namespace OpenRA
|
|||||||
if (commentStart < 0 && line[i] == '#' && (i == 0 || line[i - 1] != '\\'))
|
if (commentStart < 0 && line[i] == '#' && (i == 0 || line[i - 1] != '\\'))
|
||||||
{
|
{
|
||||||
commentStart = i + 1;
|
commentStart = i + 1;
|
||||||
if (i <= keyStart + keyLength)
|
if (commentStart <= keyLength)
|
||||||
keyLength = i - keyStart;
|
keyLength = i - keyStart;
|
||||||
else
|
else
|
||||||
valueLength = i - valueStart;
|
valueLength = i - valueStart;
|
||||||
@@ -322,81 +274,46 @@ namespace OpenRA
|
|||||||
|
|
||||||
if (!key.IsEmpty || !discardCommentsAndWhitespace)
|
if (!key.IsEmpty || !discardCommentsAndWhitespace)
|
||||||
{
|
{
|
||||||
if (parsedLines.Count > 0 && parsedLines[^1].Level < level - 1)
|
|
||||||
throw new YamlException($"Bad indent in miniyaml at {location}");
|
|
||||||
|
|
||||||
while (parsedLines.Count > 0 && parsedLines[^1].Level > level)
|
|
||||||
BuildCompletedSubNode(level);
|
|
||||||
|
|
||||||
var keyString = key.IsEmpty ? null : key.ToString();
|
var keyString = key.IsEmpty ? null : key.ToString();
|
||||||
var valueString = value.IsEmpty ? null : value.ToString();
|
var valueString = value.IsEmpty ? null : value.ToString();
|
||||||
|
|
||||||
// Note: We need to support empty comments here to ensure that empty comments
|
// Note: We need to support empty comments here to ensure that empty comments
|
||||||
// (i.e. a lone # at the end of a line) can be correctly re-serialized
|
// (i.e. a lone # at the end of a line) can be correctly re-serialized
|
||||||
var commentString = comment == ReadOnlySpan<char>.Empty ? null : comment.ToString();
|
var commentString = comment == default ? null : comment.ToString();
|
||||||
|
|
||||||
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString);
|
keyString = keyString == null ? null : stringPool.GetOrAdd(keyString, keyString);
|
||||||
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString);
|
valueString = valueString == null ? null : stringPool.GetOrAdd(valueString, valueString);
|
||||||
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString);
|
commentString = commentString == null ? null : stringPool.GetOrAdd(commentString, commentString);
|
||||||
|
|
||||||
parsedLines.Add((level, keyString, valueString, commentString, location));
|
var nodes = new List<MiniYamlNode>();
|
||||||
|
levels[level].Add(new MiniYamlNode(keyString, valueString, commentString, nodes, location));
|
||||||
|
|
||||||
|
levels.Add(nodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedLines.Count > 0)
|
foreach (var nodes in levels)
|
||||||
BuildCompletedSubNode(0);
|
nodes.TrimExcess();
|
||||||
|
|
||||||
return result[0];
|
return levels[0];
|
||||||
|
|
||||||
void BuildCompletedSubNode(int level)
|
|
||||||
{
|
|
||||||
var lastLevel = parsedLines[^1].Level;
|
|
||||||
while (lastLevel >= result.Count)
|
|
||||||
result.Add(new List<MiniYamlNode>());
|
|
||||||
|
|
||||||
while (parsedLines.Count > 0 && parsedLines[^1].Level >= level)
|
|
||||||
{
|
|
||||||
var parent = parsedLines[^1];
|
|
||||||
var startOfRange = parsedLines.Count - 1;
|
|
||||||
while (startOfRange > 0 && parsedLines[startOfRange - 1].Level == parent.Level)
|
|
||||||
startOfRange--;
|
|
||||||
|
|
||||||
for (var i = startOfRange; i < parsedLines.Count - 1; i++)
|
|
||||||
{
|
|
||||||
var sibling = parsedLines[i];
|
|
||||||
result[parent.Level].Add(
|
|
||||||
new MiniYamlNode(sibling.Key, new MiniYaml(sibling.Value), sibling.Comment, sibling.Location));
|
|
||||||
}
|
|
||||||
|
|
||||||
var childNodes = parent.Level + 1 < result.Count ? result[parent.Level + 1] : null;
|
|
||||||
result[parent.Level].Add(new MiniYamlNode(
|
|
||||||
parent.Key,
|
|
||||||
new MiniYaml(parent.Value, childNodes ?? Enumerable.Empty<MiniYamlNode>()),
|
|
||||||
parent.Comment,
|
|
||||||
parent.Location));
|
|
||||||
childNodes?.Clear();
|
|
||||||
|
|
||||||
parsedLines.RemoveRange(startOfRange, parsedLines.Count - startOfRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool);
|
return FromLines(s.ReadAllLinesAsMemory(), fileName, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet<string> stringPool = null)
|
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
|
||||||
{
|
{
|
||||||
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), name, discardCommentsAndWhitespace, stringPool);
|
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), fileName, discardCommentsAndWhitespace, stringPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MiniYamlNode> Merge(IEnumerable<IReadOnlyCollection<MiniYamlNode>> sources)
|
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
|
||||||
{
|
{
|
||||||
var sourcesList = sources.ToList();
|
var sourcesList = sources.ToList();
|
||||||
if (sourcesList.Count == 0)
|
if (sourcesList.Count == 0)
|
||||||
@@ -419,41 +336,28 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve any top-level removals (e.g. removing whole actor blocks)
|
// Resolve any top-level removals (e.g. removing whole actor blocks)
|
||||||
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)));
|
var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)).ToList());
|
||||||
var result = ResolveInherits(nodes, tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty);
|
return ResolveInherits(nodes, tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation>.Empty);
|
||||||
return result as List<MiniYamlNode> ?? result.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes, HashSet<string> existingNodeKeys,
|
static void MergeIntoResolved(MiniYamlNode overrideNode, List<MiniYamlNode> existingNodes, HashSet<string> existingNodeKeys,
|
||||||
Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||||
{
|
{
|
||||||
var existingNodeIndex = -1;
|
if (existingNodeKeys.Add(overrideNode.Key))
|
||||||
MiniYamlNode existingNode = null;
|
|
||||||
if (!existingNodeKeys.Add(overrideNode.Key))
|
|
||||||
{
|
{
|
||||||
existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key);
|
existingNodes.Add(overrideNode.Clone());
|
||||||
existingNode = existingNodes[existingNodeIndex];
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = MergePartial(existingNode?.Value, overrideNode.Value);
|
var existingNode = existingNodes.Find(n => n.Key == overrideNode.Key);
|
||||||
var nodes = ResolveInherits(value, tree, inherited);
|
existingNode.Value = MergePartial(existingNode.Value, overrideNode.Value);
|
||||||
if (!value.Nodes.SequenceEqual(nodes))
|
existingNode.Value.Nodes = ResolveInherits(existingNode.Value, tree, inherited);
|
||||||
value = value.WithNodes(nodes);
|
|
||||||
|
|
||||||
if (existingNode != null)
|
|
||||||
existingNodes[existingNodeIndex] = existingNode.WithValue(value);
|
|
||||||
else
|
|
||||||
existingNodes.Add(overrideNode.WithValue(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static IReadOnlyCollection<MiniYamlNode> ResolveInherits(
|
static List<MiniYamlNode> ResolveInherits(MiniYaml node, Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
||||||
MiniYaml node, Dictionary<string, MiniYaml> tree, ImmutableDictionary<string, MiniYamlNode.SourceLocation> inherited)
|
|
||||||
{
|
{
|
||||||
if (node.Nodes.Length == 0)
|
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
|
||||||
return node.Nodes;
|
var resolvedKeys = new HashSet<string>(node.Nodes.Count);
|
||||||
|
|
||||||
var resolved = new List<MiniYamlNode>(node.Nodes.Length);
|
|
||||||
var resolvedKeys = new HashSet<string>(node.Nodes.Length);
|
|
||||||
|
|
||||||
foreach (var n in node.Nodes)
|
foreach (var n in node.Nodes)
|
||||||
{
|
{
|
||||||
@@ -469,14 +373,13 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
throw new YamlException(
|
throw new YamlException($"{n.Location}: Parent type `{n.Value.Value}` was already inherited by this yaml tree at {inherited[n.Value.Value]} (note: may be from a derived tree)");
|
||||||
$"{n.Location}: Parent type `{n.Value.Value}` was already inherited by this yaml tree at {inherited[n.Value.Value]} (note: may be from a derived tree)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var r in ResolveInherits(parent, tree, inherited))
|
foreach (var r in ResolveInherits(parent, tree, inherited))
|
||||||
MergeIntoResolved(r, resolved, resolvedKeys, tree, inherited);
|
MergeIntoResolved(r, resolved, resolvedKeys, tree, inherited);
|
||||||
}
|
}
|
||||||
else if (n.Key.StartsWith('-'))
|
else if (n.Key.StartsWith("-", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var removed = n.Key[1..];
|
var removed = n.Key[1..];
|
||||||
if (resolved.RemoveAll(r => r.Key == removed) == 0)
|
if (resolved.RemoveAll(r => r.Key == removed) == 0)
|
||||||
@@ -487,6 +390,7 @@ namespace OpenRA
|
|||||||
MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited);
|
MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolved.TrimExcess();
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,11 +398,8 @@ namespace OpenRA
|
|||||||
/// Merges any duplicate keys that are defined within the same set of nodes.
|
/// Merges any duplicate keys that are defined within the same set of nodes.
|
||||||
/// Does not resolve inheritance or node removals.
|
/// Does not resolve inheritance or node removals.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static IReadOnlyCollection<MiniYamlNode> MergeSelfPartial(IReadOnlyCollection<MiniYamlNode> existingNodes)
|
static List<MiniYamlNode> MergeSelfPartial(List<MiniYamlNode> existingNodes)
|
||||||
{
|
{
|
||||||
if (existingNodes.Count == 0)
|
|
||||||
return existingNodes;
|
|
||||||
|
|
||||||
var keys = new HashSet<string>(existingNodes.Count);
|
var keys = new HashSet<string>(existingNodes.Count);
|
||||||
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
var ret = new List<MiniYamlNode>(existingNodes.Count);
|
||||||
foreach (var n in existingNodes)
|
foreach (var n in existingNodes)
|
||||||
@@ -508,26 +409,19 @@ namespace OpenRA
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Node with the same key has already been added: merge new node over the existing one
|
// Node with the same key has already been added: merge new node over the existing one
|
||||||
var originalIndex = IndexOfKey(ret, n.Key);
|
var original = ret.First(r => r.Key == n.Key);
|
||||||
var original = ret[originalIndex];
|
original.Value = MergePartial(original.Value, n.Value);
|
||||||
ret[originalIndex] = original.WithValue(MergePartial(original.Value, n.Value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret.TrimExcess();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes)
|
||||||
{
|
{
|
||||||
lock (ConflictScratch)
|
existingNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||||
{
|
overrideNodes?.Nodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => $"{x.Key} (at {x.Location})");
|
||||||
// PERF: Reuse ConflictScratch for all conflict checks to avoid allocations.
|
|
||||||
existingNodes?.Nodes.IntoDictionaryWithConflictLog(
|
|
||||||
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
|
||||||
overrideNodes?.Nodes.IntoDictionaryWithConflictLog(
|
|
||||||
n => n.Key, n => n, "MiniYaml.Merge", ConflictScratch, k => k, n => $"{n.Key} (at {n.Location})");
|
|
||||||
ConflictScratch.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingNodes == null)
|
if (existingNodes == null)
|
||||||
return overrideNodes;
|
return overrideNodes;
|
||||||
@@ -538,7 +432,7 @@ namespace OpenRA
|
|||||||
return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes));
|
return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
static IReadOnlyCollection<MiniYamlNode> MergePartial(IReadOnlyCollection<MiniYamlNode> existingNodes, IReadOnlyCollection<MiniYamlNode> overrideNodes)
|
static List<MiniYamlNode> MergePartial(List<MiniYamlNode> existingNodes, List<MiniYamlNode> overrideNodes)
|
||||||
{
|
{
|
||||||
if (existingNodes.Count == 0)
|
if (existingNodes.Count == 0)
|
||||||
return overrideNodes;
|
return overrideNodes;
|
||||||
@@ -558,7 +452,7 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
// Append Removal nodes to the result.
|
// Append Removal nodes to the result.
|
||||||
// Therefore: we know the remainder of the method deals with a plain node.
|
// Therefore: we know the remainder of the method deals with a plain node.
|
||||||
if (node.Key.StartsWith('-'))
|
if (node.Key.StartsWith("-", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
ret.Add(node);
|
ret.Add(node);
|
||||||
return;
|
return;
|
||||||
@@ -574,8 +468,9 @@ namespace OpenRA
|
|||||||
// A Removal node is closer than the previous node.
|
// A Removal node is closer than the previous node.
|
||||||
// We should not merge the new node, as the data being merged will jump before the Removal.
|
// We should not merge the new node, as the data being merged will jump before the Removal.
|
||||||
// Instead, append it so the previous node is applied, then removed, then the new node is applied.
|
// Instead, append it so the previous node is applied, then removed, then the new node is applied.
|
||||||
var previousNodeIndex = LastIndexOfKey(ret, node.Key);
|
var removalKey = $"-{node.Key}";
|
||||||
var previousRemovalNodeIndex = LastIndexOfKey(ret, $"-{node.Key}");
|
var previousNodeIndex = ret.FindLastIndex(n => n.Key == node.Key);
|
||||||
|
var previousRemovalNodeIndex = ret.FindLastIndex(n => n.Key == removalKey);
|
||||||
if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex)
|
if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex)
|
||||||
{
|
{
|
||||||
ret.Add(node);
|
ret.Add(node);
|
||||||
@@ -584,30 +479,13 @@ namespace OpenRA
|
|||||||
|
|
||||||
// A previous node is present with no intervening Removal.
|
// A previous node is present with no intervening Removal.
|
||||||
// We should merge the new one into it, in place.
|
// We should merge the new one into it, in place.
|
||||||
ret[previousNodeIndex] = node.WithValue(MergePartial(ret[previousNodeIndex].Value, node.Value));
|
ret[previousNodeIndex] = new MiniYamlNode(node.Key, MergePartial(ret[previousNodeIndex].Value, node.Value), node.Comment, node.Location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret.TrimExcess();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int IndexOfKey(List<MiniYamlNode> nodes, string key)
|
|
||||||
{
|
|
||||||
// PERF: Avoid LINQ.
|
|
||||||
for (var i = 0; i < nodes.Count; i++)
|
|
||||||
if (nodes[i].Key == key)
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int LastIndexOfKey(List<MiniYamlNode> nodes, string key)
|
|
||||||
{
|
|
||||||
// PERF: Avoid LINQ.
|
|
||||||
for (var i = nodes.Count - 1; i >= 0; i--)
|
|
||||||
if (nodes[i].Key == key)
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> ToLines(string key, string comment = null)
|
public IEnumerable<string> ToLines(string key, string comment = null)
|
||||||
{
|
{
|
||||||
var hasKey = !string.IsNullOrEmpty(key);
|
var hasKey = !string.IsNullOrEmpty(key);
|
||||||
@@ -630,100 +508,14 @@ namespace OpenRA
|
|||||||
files = files.Append(mapFiles);
|
files = files.Append(mapFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
var yaml = files.Select(s => FromStream(fileSystem.Open(s), s));
|
||||||
IEnumerable<IReadOnlyCollection<MiniYamlNode>> yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool));
|
if (mapRules != null && mapRules.Nodes.Count > 0)
|
||||||
if (mapRules != null && mapRules.Nodes.Length > 0)
|
|
||||||
yaml = yaml.Append(mapRules.Nodes);
|
yaml = yaml.Append(mapRules.Nodes);
|
||||||
|
|
||||||
return Merge(yaml);
|
return Merge(yaml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MiniYamlNodeBuilder
|
|
||||||
{
|
|
||||||
public MiniYamlNode.SourceLocation Location;
|
|
||||||
public string Key;
|
|
||||||
public MiniYamlBuilder Value;
|
|
||||||
public string Comment;
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder(MiniYamlNode node)
|
|
||||||
{
|
|
||||||
Location = node.Location;
|
|
||||||
Key = node.Key;
|
|
||||||
Value = new MiniYamlBuilder(node.Value);
|
|
||||||
Comment = node.Comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c = null)
|
|
||||||
{
|
|
||||||
Key = k;
|
|
||||||
Value = v;
|
|
||||||
Comment = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c, MiniYamlNode.SourceLocation loc)
|
|
||||||
: this(k, v, c)
|
|
||||||
{
|
|
||||||
Location = loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder(string k, string v, string c = null)
|
|
||||||
: this(k, new MiniYamlBuilder(v, null), c) { }
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder(string k, string v, List<MiniYamlNode> n)
|
|
||||||
: this(k, new MiniYamlBuilder(v, n), null) { }
|
|
||||||
|
|
||||||
public MiniYamlNode Build()
|
|
||||||
{
|
|
||||||
return new MiniYamlNode(Key, Value.Build(), Comment, Location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class MiniYamlBuilder
|
|
||||||
{
|
|
||||||
public string Value;
|
|
||||||
public List<MiniYamlNodeBuilder> Nodes;
|
|
||||||
|
|
||||||
public MiniYamlBuilder(MiniYaml yaml)
|
|
||||||
{
|
|
||||||
Value = yaml.Value;
|
|
||||||
Nodes = yaml.Nodes.Select(n => new MiniYamlNodeBuilder(n)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlBuilder(string value)
|
|
||||||
: this(value, null) { }
|
|
||||||
|
|
||||||
public MiniYamlBuilder(string value, List<MiniYamlNode> nodes)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
Nodes = nodes == null ? new List<MiniYamlNodeBuilder>() : nodes.ConvertAll(x => new MiniYamlNodeBuilder(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYaml Build()
|
|
||||||
{
|
|
||||||
return new MiniYaml(Value, Nodes.Select(n => n.Build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> ToLines(string key, string comment = null)
|
|
||||||
{
|
|
||||||
var hasKey = !string.IsNullOrEmpty(key);
|
|
||||||
var hasValue = !string.IsNullOrEmpty(Value);
|
|
||||||
var hasComment = comment != null;
|
|
||||||
yield return (hasKey ? key + ":" : "")
|
|
||||||
+ (hasValue ? " " + Value.Replace("#", "\\#") : "")
|
|
||||||
+ (hasComment ? (hasKey || hasValue ? " " : "") + "#" + comment : "");
|
|
||||||
|
|
||||||
if (Nodes != null)
|
|
||||||
foreach (var line in Nodes.ToLines())
|
|
||||||
yield return "\t" + line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MiniYamlNodeBuilder NodeWithKeyOrDefault(string key)
|
|
||||||
{
|
|
||||||
return Nodes.SingleOrDefault(n => n.Key == key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class YamlException : Exception
|
public class YamlException : Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ namespace OpenRA
|
|||||||
public readonly ISpriteLoader[] SpriteLoaders;
|
public readonly ISpriteLoader[] SpriteLoaders;
|
||||||
public readonly ITerrainLoader TerrainLoader;
|
public readonly ITerrainLoader TerrainLoader;
|
||||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||||
|
public readonly IModelSequenceLoader ModelSequenceLoader;
|
||||||
public readonly IVideoLoader[] VideoLoaders;
|
public readonly IVideoLoader[] VideoLoaders;
|
||||||
public readonly HotkeyManager Hotkeys;
|
public readonly HotkeyManager Hotkeys;
|
||||||
public readonly IFileSystemLoader FileSystemLoader;
|
|
||||||
|
|
||||||
public ILoadScreen LoadScreen { get; }
|
public ILoadScreen LoadScreen { get; }
|
||||||
public CursorProvider CursorProvider { get; private set; }
|
public CursorProvider CursorProvider { get; private set; }
|
||||||
public FS ModFiles;
|
public FS ModFiles;
|
||||||
@@ -56,17 +55,11 @@ namespace OpenRA
|
|||||||
Manifest = new Manifest(mod.Id, mod.Package);
|
Manifest = new Manifest(mod.Id, mod.Package);
|
||||||
ObjectCreator = new ObjectCreator(Manifest, mods);
|
ObjectCreator = new ObjectCreator(Manifest, mods);
|
||||||
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
|
PackageLoaders = ObjectCreator.GetLoaders<IPackageLoader>(Manifest.PackageFormats, "package");
|
||||||
|
|
||||||
ModFiles = new FS(mod.Id, mods, PackageLoaders);
|
ModFiles = new FS(mod.Id, mods, PackageLoaders);
|
||||||
|
ModFiles.LoadFromManifest(Manifest);
|
||||||
FileSystemLoader = ObjectCreator.GetLoader<IFileSystemLoader>(Manifest.FileSystem.Value, "filesystem");
|
|
||||||
FieldLoader.Load(FileSystemLoader, Manifest.FileSystem);
|
|
||||||
FileSystemLoader.Mount(ModFiles, ObjectCreator);
|
|
||||||
ModFiles.TrimExcess();
|
|
||||||
|
|
||||||
Manifest.LoadCustomData(ObjectCreator);
|
Manifest.LoadCustomData(ObjectCreator);
|
||||||
|
|
||||||
FluentProvider.Initialize(this, DefaultFileSystem);
|
|
||||||
|
|
||||||
if (useLoadScreen)
|
if (useLoadScreen)
|
||||||
{
|
{
|
||||||
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);
|
||||||
@@ -97,6 +90,15 @@ namespace OpenRA
|
|||||||
|
|
||||||
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
|
||||||
|
|
||||||
|
var modelFormat = Manifest.Get<ModelSequenceFormat>();
|
||||||
|
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
|
||||||
|
var modelCtor = modelLoader?.GetConstructor(new[] { typeof(ModData) });
|
||||||
|
if (modelLoader == null || !modelLoader.GetInterfaces().Contains(typeof(IModelSequenceLoader)) || modelCtor == null)
|
||||||
|
throw new InvalidOperationException($"Unable to find a model loader for type '{modelFormat.Type}'.");
|
||||||
|
|
||||||
|
ModelSequenceLoader = (IModelSequenceLoader)modelCtor.Invoke(new[] { this });
|
||||||
|
ModelSequenceLoader.OnMissingModelError = s => Log.Write("debug", s);
|
||||||
|
|
||||||
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
Hotkeys = new HotkeyManager(ModFiles, Game.Settings.Keys, Manifest);
|
||||||
|
|
||||||
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
|
||||||
@@ -132,7 +134,7 @@ namespace OpenRA
|
|||||||
// horribly when you use ModData in unexpected ways.
|
// horribly when you use ModData in unexpected ways.
|
||||||
ChromeMetrics.Initialize(this);
|
ChromeMetrics.Initialize(this);
|
||||||
ChromeProvider.Initialize(this);
|
ChromeProvider.Initialize(this);
|
||||||
FluentProvider.Initialize(this, fileSystem);
|
TranslationProvider.Initialize(this, fileSystem);
|
||||||
|
|
||||||
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
Game.Sound.Initialize(SoundLoaders, fileSystem);
|
||||||
|
|
||||||
@@ -166,8 +168,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public List<MiniYamlNode>[] GetRulesYaml()
|
public List<MiniYamlNode>[] GetRulesYaml()
|
||||||
{
|
{
|
||||||
var stringPool = new HashSet<string>(); // Reuse common strings in YAML
|
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s)).ToArray();
|
||||||
return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -198,9 +199,4 @@ namespace OpenRA
|
|||||||
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
/// <summary>Called when the engine expects to connect to a server/replay or load the shellmap.</summary>
|
||||||
void StartGame(Arguments args);
|
void StartGame(Arguments args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IFileSystemLoader
|
|
||||||
{
|
|
||||||
void Mount(FS fileSystem, ObjectCreator objectCreator);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,15 +260,15 @@ namespace OpenRA.Network
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
ms.Write(packet.Length);
|
ms.WriteArray(BitConverter.GetBytes(packet.Length));
|
||||||
ms.Write(packet);
|
ms.WriteArray(packet);
|
||||||
|
|
||||||
foreach (var s in queuedSyncPackets)
|
foreach (var s in queuedSyncPackets)
|
||||||
{
|
{
|
||||||
var q = OrderIO.SerializeSync(s);
|
var q = OrderIO.SerializeSync(s);
|
||||||
|
|
||||||
ms.Write(q.Length);
|
ms.WriteArray(BitConverter.GetBytes(q.Length));
|
||||||
ms.Write(q);
|
ms.WriteArray(q);
|
||||||
|
|
||||||
sentSync.Enqueue(s);
|
sentSync.Enqueue(s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,10 +122,10 @@ namespace OpenRA.Network
|
|||||||
LastSyncFrame = rs.ReadInt32();
|
LastSyncFrame = rs.ReadInt32();
|
||||||
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
|
||||||
|
|
||||||
var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings");
|
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||||
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
|
||||||
|
|
||||||
var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots");
|
var slots = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||||
Slots = new Dictionary<string, Session.Slot>();
|
Slots = new Dictionary<string, Session.Slot>();
|
||||||
foreach (var s in slots)
|
foreach (var s in slots)
|
||||||
{
|
{
|
||||||
@@ -133,7 +133,7 @@ namespace OpenRA.Network
|
|||||||
Slots.Add(slot.PlayerReference, slot);
|
Slots.Add(slot.PlayerReference, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients");
|
var slotClients = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||||
SlotClients = new Dictionary<string, SlotClient>();
|
SlotClients = new Dictionary<string, SlotClient>();
|
||||||
foreach (var s in slotClients)
|
foreach (var s in slotClients)
|
||||||
{
|
{
|
||||||
@@ -144,9 +144,9 @@ namespace OpenRA.Network
|
|||||||
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
|
||||||
throw new InvalidDataException("Invalid orasav file");
|
throw new InvalidDataException("Invalid orasav file");
|
||||||
|
|
||||||
var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:traitData");
|
var traitData = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
|
||||||
foreach (var td in traitData)
|
foreach (var td in traitData)
|
||||||
TraitData.Add(Exts.ParseInt32Invariant(td.Key), td.Value);
|
TraitData.Add(int.Parse(td.Key), td.Value);
|
||||||
|
|
||||||
rs.Seek(0, SeekOrigin.Begin);
|
rs.Seek(0, SeekOrigin.Begin);
|
||||||
ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset);
|
ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset);
|
||||||
@@ -226,10 +226,10 @@ namespace OpenRA.Network
|
|||||||
clientSlot = firstBotSlotIndex;
|
clientSlot = firstBotSlotIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
ordersStream.Write(data.Length + 8);
|
ordersStream.WriteArray(BitConverter.GetBytes(data.Length + 8));
|
||||||
ordersStream.Write(frame);
|
ordersStream.WriteArray(BitConverter.GetBytes(frame));
|
||||||
ordersStream.Write(clientSlot);
|
ordersStream.WriteArray(BitConverter.GetBytes(clientSlot));
|
||||||
ordersStream.Write(data);
|
ordersStream.WriteArray(data);
|
||||||
LastOrdersFrame = frame;
|
LastOrdersFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ namespace OpenRA.Network
|
|||||||
// Send the trait data first to guarantee that it is available when needed
|
// Send the trait data first to guarantee that it is available when needed
|
||||||
foreach (var kv in TraitData)
|
foreach (var kv in TraitData)
|
||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode>() { new(kv.Key.ToStringInvariant(), kv.Value) }.WriteToString();
|
var data = new List<MiniYamlNode>() { new MiniYamlNode(kv.Key.ToString(), kv.Value) }.WriteToString();
|
||||||
packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize());
|
packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,35 +288,35 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
ordersStream.Seek(0, SeekOrigin.Begin);
|
ordersStream.Seek(0, SeekOrigin.Begin);
|
||||||
ordersStream.CopyTo(file);
|
ordersStream.CopyTo(file);
|
||||||
file.Write(MetadataMarker);
|
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
|
||||||
file.Write(LastOrdersFrame);
|
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
|
||||||
file.Write(LastSyncFrame);
|
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
|
||||||
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
|
||||||
|
|
||||||
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());
|
||||||
|
|
||||||
var slotNodes = Slots
|
var slotNodes = Slots
|
||||||
.Select(s => s.Value.Serialize())
|
.Select(s => s.Value.Serialize())
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, slotNodes.WriteToString());
|
file.WriteString(Encoding.UTF8, slotNodes.WriteToString());
|
||||||
|
|
||||||
var slotClientNodes = SlotClients
|
var slotClientNodes = SlotClients
|
||||||
.Select(s => s.Value.Serialize(s.Key))
|
.Select(s => s.Value.Serialize(s.Key))
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
|
file.WriteString(Encoding.UTF8, slotClientNodes.WriteToString());
|
||||||
|
|
||||||
var traitDataOffset = file.Length;
|
var traitDataOffset = file.Length;
|
||||||
file.Write(TraitDataMarker);
|
file.Write(BitConverter.GetBytes(TraitDataMarker), 0, 4);
|
||||||
|
|
||||||
var traitDataNodes = TraitData
|
var traitDataNodes = TraitData
|
||||||
.Select(kv => new MiniYamlNode(kv.Key.ToStringInvariant(), kv.Value))
|
.Select(kv => new MiniYamlNode(kv.Key.ToString(), kv.Value))
|
||||||
.ToList();
|
.ToList();
|
||||||
file.WriteLengthPrefixedString(Encoding.UTF8, traitDataNodes.WriteToString());
|
file.WriteString(Encoding.UTF8, traitDataNodes.WriteToString());
|
||||||
|
|
||||||
file.Write((int)ordersStream.Length);
|
file.Write(BitConverter.GetBytes(ordersStream.Length), 0, 4);
|
||||||
file.Write((int)traitDataOffset);
|
file.Write(BitConverter.GetBytes(traitDataOffset), 0, 4);
|
||||||
file.Write(EOFMarker);
|
file.Write(BitConverter.GetBytes(EOFMarker), 0, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace OpenRA.Network
|
|||||||
static object LoadClients(MiniYaml yaml)
|
static object LoadClients(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var clients = new List<GameClient>();
|
var clients = new List<GameClient>();
|
||||||
var clientsNode = yaml.NodeWithKeyOrDefault("Clients");
|
var clientsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Clients");
|
||||||
if (clientsNode != null)
|
if (clientsNode != null)
|
||||||
{
|
{
|
||||||
var regex = new Regex(@"Client@\d+");
|
var regex = new Regex(@"Client@\d+");
|
||||||
@@ -159,7 +159,7 @@ namespace OpenRA.Network
|
|||||||
// Games advertised using the old API used a single Mods field
|
// Games advertised using the old API used a single Mods field
|
||||||
if (Mod == null || Version == null)
|
if (Mod == null || Version == null)
|
||||||
{
|
{
|
||||||
var modsNode = yaml.NodeWithKeyOrDefault("Mods");
|
var modsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Mods");
|
||||||
if (modsNode != null)
|
if (modsNode != null)
|
||||||
{
|
{
|
||||||
var modVersion = modsNode.Value.Value.Split('@');
|
var modVersion = modsNode.Value.Value.Split('@');
|
||||||
@@ -169,8 +169,9 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Games advertised using the old API calculated the play time locally
|
// Games advertised using the old API calculated the play time locally
|
||||||
if (State == 2 && PlayTime < 0 && DateTime.TryParse(Started, out var startTime))
|
if (State == 2 && PlayTime < 0)
|
||||||
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
if (DateTime.TryParse(Started, out var startTime))
|
||||||
|
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
|
||||||
|
|
||||||
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
var externalKey = ExternalMod.MakeKey(Mod, Version);
|
||||||
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
|
||||||
@@ -182,13 +183,13 @@ namespace OpenRA.Network
|
|||||||
if (external != null && external.Version == Version)
|
if (external != null && external.Version == Version)
|
||||||
{
|
{
|
||||||
// Use external mod registration to populate the section header
|
// Use external mod registration to populate the section header
|
||||||
ModTitle = external.Id;
|
ModTitle = external.Title;
|
||||||
}
|
}
|
||||||
else if (Game.Mods.TryGetValue(Mod, out var mod))
|
else if (Game.Mods.TryGetValue(Mod, out var mod))
|
||||||
{
|
{
|
||||||
// Use internal mod data to populate the section header, but
|
// Use internal mod data to populate the section header, but
|
||||||
// on-connect switching must use the external mod plumbing.
|
// on-connect switching must use the external mod plumbing.
|
||||||
ModTitle = mod.Metadata.TitleTranslated;
|
ModTitle = mod.Metadata.Title;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -199,7 +200,7 @@ namespace OpenRA.Network
|
|||||||
.FirstOrDefault(m => m.Id == Mod);
|
.FirstOrDefault(m => m.Id == Mod);
|
||||||
|
|
||||||
if (guessMod != null)
|
if (guessMod != null)
|
||||||
ModTitle = guessMod.Id;
|
ModTitle = guessMod.Title;
|
||||||
else
|
else
|
||||||
ModTitle = $"Unknown mod: {Mod}";
|
ModTitle = $"Unknown mod: {Mod}";
|
||||||
}
|
}
|
||||||
@@ -216,13 +217,13 @@ namespace OpenRA.Network
|
|||||||
Name = server.Settings.Name;
|
Name = server.Settings.Name;
|
||||||
|
|
||||||
// IP address will be replaced with a real value by the master server / receiving LAN client
|
// IP address will be replaced with a real value by the master server / receiving LAN client
|
||||||
Address = "0.0.0.0:" + server.Settings.ListenPort.ToStringInvariant();
|
Address = "0.0.0.0:" + server.Settings.ListenPort.ToString();
|
||||||
State = (int)server.State;
|
State = (int)server.State;
|
||||||
MaxPlayers = server.LobbyInfo.Slots.Count(s => !s.Value.Closed) - server.LobbyInfo.Clients.Count(c1 => c1.Bot != null);
|
MaxPlayers = server.LobbyInfo.Slots.Count(s => !s.Value.Closed) - server.LobbyInfo.Clients.Count(c1 => c1.Bot != null);
|
||||||
Map = server.Map.Uid;
|
Map = server.Map.Uid;
|
||||||
Mod = manifest.Id;
|
Mod = manifest.Id;
|
||||||
Version = manifest.Metadata.Version;
|
Version = manifest.Metadata.Version;
|
||||||
ModTitle = manifest.Metadata.TitleTranslated;
|
ModTitle = manifest.Metadata.Title;
|
||||||
ModWebsite = manifest.Metadata.Website;
|
ModWebsite = manifest.Metadata.Website;
|
||||||
ModIcon32 = manifest.Metadata.WebIcon32;
|
ModIcon32 = manifest.Metadata.WebIcon32;
|
||||||
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
Protected = !string.IsNullOrEmpty(server.Settings.Password);
|
||||||
@@ -233,7 +234,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public string ToPOSTData(bool lanGame)
|
public string ToPOSTData(bool lanGame)
|
||||||
{
|
{
|
||||||
var root = new List<MiniYamlNode>() { new("Protocol", ProtocolVersion.ToStringInvariant()) };
|
var root = new List<MiniYamlNode>() { new MiniYamlNode("Protocol", ProtocolVersion.ToString()) };
|
||||||
foreach (var field in SerializeFields)
|
foreach (var field in SerializeFields)
|
||||||
root.Add(FieldSaver.SaveField(this, field));
|
root.Add(FieldSaver.SaveField(this, field));
|
||||||
|
|
||||||
@@ -242,16 +243,18 @@ namespace OpenRA.Network
|
|||||||
// Add fields that are normally generated by the master server
|
// Add fields that are normally generated by the master server
|
||||||
// LAN games overload the Id with a GUID string (rather than an ID) to allow deduplication
|
// LAN games overload the Id with a GUID string (rather than an ID) to allow deduplication
|
||||||
root.Add(new MiniYamlNode("Id", Platform.SessionGUID.ToString()));
|
root.Add(new MiniYamlNode("Id", Platform.SessionGUID.ToString()));
|
||||||
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToStringInvariant()));
|
root.Add(new MiniYamlNode("Players", Clients.Count(c => !c.IsBot && !c.IsSpectator).ToString()));
|
||||||
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToStringInvariant()));
|
root.Add(new MiniYamlNode("Spectators", Clients.Count(c => c.IsSpectator).ToString()));
|
||||||
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToStringInvariant()));
|
root.Add(new MiniYamlNode("Bots", Clients.Count(c => c.IsBot).ToString()));
|
||||||
|
|
||||||
// Included for backwards compatibility with older clients that don't support separated Mod/Version.
|
// Included for backwards compatibility with older clients that don't support separated Mod/Version.
|
||||||
root.Add(new MiniYamlNode("Mods", Mod + "@" + Version));
|
root.Add(new MiniYamlNode("Mods", Mod + "@" + Version));
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientsNode = new MiniYaml("", Clients.Select((c, i) =>
|
var clientsNode = new MiniYaml("");
|
||||||
new MiniYamlNode("Client@" + i, FieldSaver.Save(c))));
|
var i = 0;
|
||||||
|
foreach (var c in Clients)
|
||||||
|
clientsNode.Nodes.Add(new MiniYamlNode("Client@" + i++.ToString(), FieldSaver.Save(c)));
|
||||||
|
|
||||||
root.Add(new MiniYamlNode("Clients", clientsNode));
|
root.Add(new MiniYamlNode("Clients", clientsNode));
|
||||||
return new MiniYaml("", root)
|
return new MiniYaml("", root)
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ namespace OpenRA.Network
|
|||||||
public string Version;
|
public string Version;
|
||||||
public string AuthToken;
|
public string AuthToken;
|
||||||
|
|
||||||
public static HandshakeRequest Deserialize(string data, string name)
|
public static HandshakeRequest Deserialize(string data)
|
||||||
{
|
{
|
||||||
var handshake = new HandshakeRequest();
|
var handshake = new HandshakeRequest();
|
||||||
FieldLoader.Load(handshake, MiniYaml.FromString(data, name).First().Value);
|
FieldLoader.Load(handshake, MiniYaml.FromString(data).First().Value);
|
||||||
return handshake;
|
return handshake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Serialize()
|
public string Serialize()
|
||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode> { new("Handshake", FieldSaver.Save(this)) };
|
var data = new List<MiniYamlNode> { new MiniYamlNode("Handshake", FieldSaver.Save(this)) };
|
||||||
return data.WriteToString();
|
return data.WriteToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,14 +51,14 @@ namespace OpenRA.Network
|
|||||||
[FieldLoader.Ignore]
|
[FieldLoader.Ignore]
|
||||||
public Session.Client Client;
|
public Session.Client Client;
|
||||||
|
|
||||||
public static HandshakeResponse Deserialize(string data, string name)
|
public static HandshakeResponse Deserialize(string data)
|
||||||
{
|
{
|
||||||
var handshake = new HandshakeResponse
|
var handshake = new HandshakeResponse
|
||||||
{
|
{
|
||||||
Client = new Session.Client()
|
Client = new Session.Client()
|
||||||
};
|
};
|
||||||
|
|
||||||
var ys = MiniYaml.FromString(data, name);
|
var ys = MiniYaml.FromString(data);
|
||||||
foreach (var y in ys)
|
foreach (var y in ys)
|
||||||
{
|
{
|
||||||
switch (y.Key)
|
switch (y.Key)
|
||||||
@@ -79,9 +79,9 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var data = new List<MiniYamlNode>
|
var data = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new("Handshake", null,
|
new MiniYamlNode("Handshake", null,
|
||||||
new[] { "Mod", "Version", "Password", "Fingerprint", "AuthSignature", "OrdersProtocol" }.Select(p => FieldSaver.SaveField(this, p)).ToList()),
|
new[] { "Mod", "Version", "Password", "Fingerprint", "AuthSignature", "OrdersProtocol" }.Select(p => FieldSaver.SaveField(this, p)).ToList()),
|
||||||
new("Client", FieldSaver.Save(Client))
|
new MiniYamlNode("Client", FieldSaver.Save(Client))
|
||||||
};
|
};
|
||||||
|
|
||||||
return data.WriteToString();
|
return data.WriteToString();
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Linguini.Shared.Types.Bundle;
|
using Linguini.Shared.Types.Bundle;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
@@ -48,75 +50,74 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FluentMessage
|
public class LocalizedMessage
|
||||||
{
|
{
|
||||||
public const int ProtocolVersion = 1;
|
public const int ProtocolVersion = 1;
|
||||||
|
|
||||||
public readonly string Key = string.Empty;
|
public readonly string Key = string.Empty;
|
||||||
|
|
||||||
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
[FieldLoader.LoadUsing(nameof(LoadArguments))]
|
||||||
public readonly object[] Arguments;
|
public readonly FluentArgument[] Arguments = Array.Empty<FluentArgument>();
|
||||||
|
|
||||||
|
public string TranslatedText { get; }
|
||||||
|
|
||||||
static object LoadArguments(MiniYaml yaml)
|
static object LoadArguments(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var arguments = new List<object>();
|
var arguments = new List<FluentArgument>();
|
||||||
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
|
var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments");
|
||||||
if (argumentsNode != null)
|
if (argumentsNode != null)
|
||||||
{
|
{
|
||||||
foreach (var argumentNode in argumentsNode.Value.Nodes)
|
var regex = new Regex(@"Argument@\d+");
|
||||||
{
|
foreach (var argument in argumentsNode.Value.Nodes)
|
||||||
var argument = FieldLoader.Load<FluentArgument>(argumentNode.Value);
|
if (regex.IsMatch(argument.Key))
|
||||||
arguments.Add(argument.Key);
|
arguments.Add(FieldLoader.Load<FluentArgument>(argument.Value));
|
||||||
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
|
||||||
{
|
|
||||||
if (!double.TryParse(argument.Value, out var number))
|
|
||||||
Log.Write("debug", $"Failed to parse {argument.Value}");
|
|
||||||
|
|
||||||
arguments.Add(number);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
arguments.Add(argument.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return arguments.ToArray();
|
return arguments.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FluentMessage(MiniYaml yaml)
|
public LocalizedMessage(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
// Let the FieldLoader do the dirty work of loading the public fields.
|
// Let the FieldLoader do the dirty work of loading the public fields.
|
||||||
FieldLoader.Load(this, yaml);
|
FieldLoader.Load(this, yaml);
|
||||||
|
|
||||||
|
var argumentDictionary = new Dictionary<string, object>();
|
||||||
|
foreach (var argument in Arguments)
|
||||||
|
{
|
||||||
|
if (argument.Type == FluentArgument.FluentArgumentType.Number)
|
||||||
|
{
|
||||||
|
if (!double.TryParse(argument.Value, out var number))
|
||||||
|
Log.Write("debug", $"Failed to parse {argument.Value}");
|
||||||
|
|
||||||
|
argumentDictionary.Add(argument.Key, number);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
argumentDictionary.Add(argument.Key, argument.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslatedText = TranslationProvider.GetString(Key, argumentDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Serialize(string key, object[] args)
|
public static string Serialize(string key, Dictionary<string, object> arguments = null)
|
||||||
{
|
{
|
||||||
var root = new List<MiniYamlNode>
|
var root = new List<MiniYamlNode>
|
||||||
{
|
{
|
||||||
new("Protocol", ProtocolVersion.ToStringInvariant()),
|
new MiniYamlNode("Protocol", ProtocolVersion.ToString()),
|
||||||
new("Key", key),
|
new MiniYamlNode("Key", key)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (args != null)
|
if (arguments != null)
|
||||||
{
|
{
|
||||||
var nodes = new List<MiniYamlNode>();
|
var argumentsNode = new MiniYaml("");
|
||||||
for (var i = 0; i < args.Length; i += 2)
|
var i = 0;
|
||||||
{
|
foreach (var argument in arguments.Select(a => new FluentArgument(a.Key, a.Value)))
|
||||||
var argKey = args[i] as string;
|
argumentsNode.Nodes.Add(new MiniYamlNode("Argument@" + i++, FieldSaver.Save(argument)));
|
||||||
if (string.IsNullOrEmpty(argKey))
|
|
||||||
throw new ArgumentException($"Expected the argument at index {i} to be a non-empty string", nameof(args));
|
|
||||||
|
|
||||||
var argValue = args[i + 1];
|
root.Add(new MiniYamlNode("Arguments", argumentsNode));
|
||||||
if (argValue == null)
|
|
||||||
throw new ArgumentNullException(nameof(args), $"Expected the argument at index {i + 1} to be a non-null value");
|
|
||||||
|
|
||||||
nodes.Add(new MiniYamlNode($"Argument@{i / 2}", FieldSaver.Save(new FluentArgument(argKey, argValue))));
|
|
||||||
}
|
|
||||||
|
|
||||||
root.Add(new MiniYamlNode("Arguments", new MiniYaml("", nodes)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MiniYaml("", root)
|
return new MiniYaml("", root)
|
||||||
.ToLines("FluentMessage")
|
.ToLines("LocalizedMessage")
|
||||||
.JoinWith("\n");
|
.JoinWith("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,8 +71,8 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Write("nat", "Port forwarding failed.");
|
Console.WriteLine("Port forwarding failed: {0}", e.Message);
|
||||||
Log.Write("nat", e);
|
Log.Write("nat", e.StackTrace);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Write("nat", "Port removal failed.");
|
Console.WriteLine("Port removal failed: {0}", e.Message);
|
||||||
Log.Write("nat", e);
|
Log.Write("nat", e.StackTrace);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ namespace OpenRA
|
|||||||
readonly Target target;
|
readonly Target target;
|
||||||
readonly Target visualFeedbackTarget;
|
readonly Target visualFeedbackTarget;
|
||||||
|
|
||||||
Order(string orderString, Actor subject, in Target target, string targetString, bool queued,
|
Order(string orderString, Actor subject, in Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
||||||
Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
|
||||||
{
|
{
|
||||||
OrderString = orderString ?? "";
|
OrderString = orderString ?? "";
|
||||||
Subject = subject;
|
Subject = subject;
|
||||||
@@ -157,18 +156,7 @@ namespace OpenRA
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pos = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
var pos = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
||||||
|
target = Target.FromPos(pos);
|
||||||
var numberOfTerrainPositions = r.ReadInt16();
|
|
||||||
if (numberOfTerrainPositions == -1)
|
|
||||||
target = Target.FromPos(pos);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var terrainPositions = new WPos[numberOfTerrainPositions];
|
|
||||||
for (var i = 0; i < numberOfTerrainPositions; i++)
|
|
||||||
terrainPositions[i] = new WPos(r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
|
|
||||||
|
|
||||||
target = Target.FromSerializedTerrainPosition(pos, terrainPositions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -271,15 +259,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
public static Order FromGroupedOrder(Order grouped, Actor subject)
|
||||||
{
|
{
|
||||||
return new Order(
|
return new Order(grouped.OrderString, subject, grouped.Target, grouped.TargetString, grouped.Queued, grouped.ExtraActors, grouped.ExtraLocation, grouped.ExtraData);
|
||||||
grouped.OrderString,
|
|
||||||
subject,
|
|
||||||
grouped.Target,
|
|
||||||
grouped.TargetString,
|
|
||||||
grouped.Queued,
|
|
||||||
grouped.ExtraActors,
|
|
||||||
grouped.ExtraLocation,
|
|
||||||
grouped.ExtraData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Order Command(string text)
|
public static Order Command(string text)
|
||||||
@@ -408,21 +388,6 @@ namespace OpenRA
|
|||||||
w.Write(targetState.Pos.X);
|
w.Write(targetState.Pos.X);
|
||||||
w.Write(targetState.Pos.Y);
|
w.Write(targetState.Pos.Y);
|
||||||
w.Write(targetState.Pos.Z);
|
w.Write(targetState.Pos.Z);
|
||||||
|
|
||||||
// Don't send extra data over the network that will be restored by the Target ctor
|
|
||||||
var terrainPositions = targetState.TerrainPositions.Length;
|
|
||||||
if (terrainPositions == 1 && targetState.TerrainPositions[0] == targetState.Pos)
|
|
||||||
w.Write((short)-1);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
w.Write((short)terrainPositions);
|
|
||||||
foreach (var position in targetState.TerrainPositions)
|
|
||||||
{
|
|
||||||
w.Write(position.X);
|
|
||||||
w.Write(position.Y);
|
|
||||||
w.Write(position.Z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -465,7 +430,7 @@ namespace OpenRA
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"OrderString: \"{OrderString}\" \n\t Type: \"{Type}\". \n\t Subject: \"{Subject}\". \n\t Target: \"{Target}\"." +
|
return $"OrderString: \"{OrderString}\" \n\t Type: \"{Type}\". \n\t Subject: \"{Subject}\". \n\t Target: \"{Target}\"." +
|
||||||
$"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.ResolvedPlayerName}\n";
|
$"\n\t TargetString: \"{TargetString}\".\n\t IsImmediate: {IsImmediate}.\n\t Player(PlayerName): {Player?.PlayerName}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace OpenRA.Network
|
|||||||
// the Order objects directly on the local client.
|
// the Order objects directly on the local client.
|
||||||
data = new MemoryStream();
|
data = new MemoryStream();
|
||||||
foreach (var o in orders)
|
foreach (var o in orders)
|
||||||
data.Write(o.Serialize());
|
data.WriteArray(o.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderPacket(MemoryStream data)
|
public OrderPacket(MemoryStream data)
|
||||||
@@ -55,7 +55,7 @@ namespace OpenRA.Network
|
|||||||
public byte[] Serialize(int frame)
|
public byte[] Serialize(int frame)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream((int)data.Length + 4);
|
var ms = new MemoryStream((int)data.Length + 4);
|
||||||
ms.Write(frame);
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||||
|
|
||||||
data.Position = 0;
|
data.Position = 0;
|
||||||
data.CopyTo(ms);
|
data.CopyTo(ms);
|
||||||
@@ -83,19 +83,19 @@ namespace OpenRA.Network
|
|||||||
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
public static byte[] SerializeSync((int Frame, int SyncHash, ulong DefeatState) data)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
var ms = new MemoryStream(4 + Order.SyncHashOrderLength);
|
||||||
ms.Write(data.Frame);
|
ms.WriteArray(BitConverter.GetBytes(data.Frame));
|
||||||
ms.WriteByte((byte)OrderType.SyncHash);
|
ms.WriteByte((byte)OrderType.SyncHash);
|
||||||
ms.Write(data.SyncHash);
|
ms.WriteArray(BitConverter.GetBytes(data.SyncHash));
|
||||||
ms.Write(data.DefeatState);
|
ms.WriteArray(BitConverter.GetBytes(data.DefeatState));
|
||||||
return ms.GetBuffer();
|
return ms.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
public static byte[] SerializePingResponse(long timestamp, byte queueLength)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(14);
|
var ms = new MemoryStream(14);
|
||||||
ms.Write(0);
|
ms.WriteArray(BitConverter.GetBytes(0));
|
||||||
ms.WriteByte((byte)OrderType.Ping);
|
ms.WriteByte((byte)OrderType.Ping);
|
||||||
ms.Write(timestamp);
|
ms.WriteArray(BitConverter.GetBytes(timestamp));
|
||||||
ms.WriteByte(queueLength);
|
ms.WriteByte(queueLength);
|
||||||
return ms.GetBuffer();
|
return ms.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
const OrderPacket ClientDisconnected = null;
|
const OrderPacket ClientDisconnected = null;
|
||||||
|
|
||||||
[FluentReference("frame")]
|
|
||||||
const string DesyncCompareLogs = "notification-desync-compare-logs";
|
|
||||||
|
|
||||||
readonly SyncReport syncReport;
|
readonly SyncReport syncReport;
|
||||||
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
readonly Dictionary<int, Queue<(int Frame, OrderPacket Orders)>> pendingOrders = new();
|
||||||
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
readonly Dictionary<int, (int SyncHash, ulong DefeatState)> syncForFrame = new();
|
||||||
@@ -39,9 +36,6 @@ namespace OpenRA.Network
|
|||||||
public string ServerError = null;
|
public string ServerError = null;
|
||||||
public bool AuthenticationFailed = false;
|
public bool AuthenticationFailed = false;
|
||||||
|
|
||||||
// The default null means "no map restriction" while an empty set means "all maps restricted"
|
|
||||||
public HashSet<string> ServerMapPool = null;
|
|
||||||
|
|
||||||
public int NetFrameNumber { get; private set; }
|
public int NetFrameNumber { get; private set; }
|
||||||
public int LocalFrameNumber;
|
public int LocalFrameNumber;
|
||||||
|
|
||||||
@@ -76,7 +70,7 @@ namespace OpenRA.Network
|
|||||||
public int Client;
|
public int Client;
|
||||||
public Order Order;
|
public Order Order;
|
||||||
|
|
||||||
public override readonly string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"ClientId: {Client} {Order}";
|
return $"ClientId: {Client} {Order}";
|
||||||
}
|
}
|
||||||
@@ -91,7 +85,7 @@ namespace OpenRA.Network
|
|||||||
World.OutOfSync();
|
World.OutOfSync();
|
||||||
IsOutOfSync = true;
|
IsOutOfSync = true;
|
||||||
|
|
||||||
TextNotificationsManager.AddSystemLine(DesyncCompareLogs, "frame", frame);
|
TextNotificationsManager.AddSystemLine($"Out of sync in frame {frame}.\nCompare syncreport.log with other players.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartGame()
|
public void StartGame()
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace OpenRA.Network
|
|||||||
if (o.OrderString == "StartGame")
|
if (o.OrderString == "StartGame")
|
||||||
IsValid = true;
|
IsValid = true;
|
||||||
else if (o.OrderString == "SyncInfo" && !IsValid)
|
else if (o.OrderString == "SyncInfo" && !IsValid)
|
||||||
LobbyInfo = Session.Deserialize(o.TargetString, o.OrderString);
|
LobbyInfo = Session.Deserialize(o.TargetString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Write(initialContent);
|
file.WriteArray(initialContent);
|
||||||
writer = new BinaryWriter(file);
|
writer = new BinaryWriter(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@ namespace OpenRA.Network
|
|||||||
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
public void ReceiveFrame(int clientID, int frame, byte[] data)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream(4 + data.Length);
|
var ms = new MemoryStream(4 + data.Length);
|
||||||
ms.Write(frame);
|
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||||
ms.Write(data);
|
ms.WriteArray(data);
|
||||||
Receive(clientID, ms.GetBuffer());
|
Receive(clientID, ms.GetBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ namespace OpenRA.Network
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Session Deserialize(string data, string name)
|
public static Session Deserialize(string data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var session = new Session();
|
var session = new Session();
|
||||||
|
|
||||||
var nodes = MiniYaml.FromString(data, name);
|
var nodes = MiniYaml.FromString(data);
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var strings = node.Key.Split('@');
|
||||||
@@ -227,7 +227,7 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var gs = FieldLoader.Load<Global>(data);
|
var gs = FieldLoader.Load<Global>(data);
|
||||||
|
|
||||||
var optionsNode = data.NodeWithKeyOrDefault("Options");
|
var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options");
|
||||||
if (optionsNode != null)
|
if (optionsNode != null)
|
||||||
foreach (var n in optionsNode.Value.Nodes)
|
foreach (var n in optionsNode.Value.Nodes)
|
||||||
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);
|
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);
|
||||||
@@ -238,9 +238,8 @@ namespace OpenRA.Network
|
|||||||
public MiniYamlNode Serialize()
|
public MiniYamlNode Serialize()
|
||||||
{
|
{
|
||||||
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this));
|
||||||
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value)));
|
var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList();
|
||||||
data = data.WithValue(data.Value.WithNodesAppended(
|
data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options)));
|
||||||
new[] { new MiniYamlNode("Options", new MiniYaml(null, options)) }));
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +264,7 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
var sessionData = new List<MiniYamlNode>()
|
var sessionData = new List<MiniYamlNode>()
|
||||||
{
|
{
|
||||||
new("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var client in Clients)
|
foreach (var client in Clients)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ namespace OpenRA.Network
|
|||||||
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
|
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
|
||||||
if (Game.IsHost)
|
if (Game.IsHost)
|
||||||
Log.Write("sync", "Player is host.");
|
Log.Write("sync", "Player is host.");
|
||||||
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.TitleTranslated} at Version {mod.Version})");
|
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.Title} at Version {mod.Version})");
|
||||||
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
|
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
|
||||||
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
|
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
|
||||||
Log.Write("sync", "Synced Traits:");
|
Log.Write("sync", "Synced Traits:");
|
||||||
@@ -201,12 +201,8 @@ namespace OpenRA.Network
|
|||||||
public TypeInfo(Type type)
|
public TypeInfo(Type type)
|
||||||
{
|
{
|
||||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||||
var fields = type.GetFields(Flags)
|
var fields = type.GetFields(Flags).Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>());
|
||||||
.Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>())
|
var properties = type.GetProperties(Flags).Where(pi => pi.HasAttribute<SyncAttribute>());
|
||||||
.ToList();
|
|
||||||
var properties = type.GetProperties(Flags)
|
|
||||||
.Where(pi => pi.HasAttribute<SyncAttribute>())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
|
if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
|
||||||
@@ -304,7 +300,7 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
public object this[int index]
|
public object this[int index]
|
||||||
{
|
{
|
||||||
readonly get
|
get
|
||||||
{
|
{
|
||||||
if (item2OrSentinel == Sentinel)
|
if (item2OrSentinel == Sentinel)
|
||||||
return ((object[])item1OrArray)[index];
|
return ((object[])item1OrArray)[index];
|
||||||
|
|||||||
@@ -20,24 +20,6 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public const int ChatMessageMaxLength = 2500;
|
public const int ChatMessageMaxLength = 2500;
|
||||||
|
|
||||||
[FluentReference("player")]
|
|
||||||
const string Joined = "notification-joined";
|
|
||||||
|
|
||||||
[FluentReference("player")]
|
|
||||||
const string Left = "notification-lobby-disconnected";
|
|
||||||
|
|
||||||
[FluentReference]
|
|
||||||
const string GameStarted = "notification-game-has-started";
|
|
||||||
|
|
||||||
[FluentReference]
|
|
||||||
const string GameSaved = "notification-game-saved";
|
|
||||||
|
|
||||||
[FluentReference("player")]
|
|
||||||
const string GamePaused = "notification-game-paused";
|
|
||||||
|
|
||||||
[FluentReference("player")]
|
|
||||||
const string GameUnpaused = "notification-game-unpaused";
|
|
||||||
|
|
||||||
public static int? KickVoteTarget { get; internal set; }
|
public static int? KickVoteTarget { get; internal set; }
|
||||||
|
|
||||||
static Player FindPlayerByClient(this World world, Session.Client c)
|
static Player FindPlayerByClient(this World world, Session.Client c)
|
||||||
@@ -56,352 +38,344 @@ namespace OpenRA.Network
|
|||||||
TextNotificationsManager.AddSystemLine(order.TargetString);
|
TextNotificationsManager.AddSystemLine(order.TargetString);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Client side resolved server message
|
// Client side translated server message
|
||||||
case "FluentMessage":
|
case "LocalizedMessage":
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(order.TargetString))
|
|
||||||
break;
|
|
||||||
|
|
||||||
var yaml = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
foreach (var node in yaml)
|
|
||||||
{
|
{
|
||||||
var message = new FluentMessage(node.Value);
|
if (string.IsNullOrEmpty(order.TargetString))
|
||||||
if (message.Key == Joined)
|
break;
|
||||||
TextNotificationsManager.AddPlayerJoinedLine(message.Key, message.Arguments);
|
|
||||||
else if (message.Key == Left)
|
|
||||||
TextNotificationsManager.AddPlayerLeftLine(message.Key, message.Arguments);
|
|
||||||
else
|
|
||||||
TextNotificationsManager.AddSystemLine(message.Key, message.Arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
var yaml = MiniYaml.FromString(order.TargetString);
|
||||||
}
|
foreach (var node in yaml)
|
||||||
|
{
|
||||||
|
var localizedMessage = new LocalizedMessage(node.Value);
|
||||||
|
TextNotificationsManager.AddSystemLine(localizedMessage.TranslatedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "DisableChatEntry":
|
case "DisableChatEntry":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Server may send MaxValue to indicate that it is disabled until further notice
|
||||||
|
if (order.ExtraData == uint.MaxValue)
|
||||||
|
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
||||||
|
else
|
||||||
|
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
// Server may send MaxValue to indicate that it is disabled until further notice
|
|
||||||
if (order.ExtraData == uint.MaxValue)
|
|
||||||
TextNotificationsManager.ChatDisabledUntil = uint.MaxValue;
|
|
||||||
else
|
|
||||||
TextNotificationsManager.ChatDisabledUntil = Game.RunTime + order.ExtraData;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "StartKickVote":
|
case "StartKickVote":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
KickVoteTarget = (int)order.ExtraData;
|
KickVoteTarget = (int)order.ExtraData;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "EndKickVote":
|
case "EndKickVote":
|
||||||
{
|
{
|
||||||
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
if (OrderNotFromServerOrWorldIsReplay(clientId, world))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (KickVoteTarget == (int)order.ExtraData)
|
||||||
|
KickVoteTarget = null;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
if (KickVoteTarget == (int)order.ExtraData)
|
|
||||||
KickVoteTarget = null;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Chat":
|
case "Chat":
|
||||||
{
|
|
||||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
|
||||||
if (client == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Cut chat messages to the hard limit to avoid exploits
|
|
||||||
var message = order.TargetString;
|
|
||||||
if (message.Length > ChatMessageMaxLength)
|
|
||||||
message = order.TargetString[..ChatMessageMaxLength];
|
|
||||||
|
|
||||||
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
|
||||||
if (order.ExtraData == 0)
|
|
||||||
{
|
{
|
||||||
var p = world?.FindPlayerByClient(client);
|
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||||
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
if (client == null)
|
||||||
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
break;
|
||||||
|
|
||||||
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
// Cut chat messages to the hard limit to avoid exploits
|
||||||
suffix += " (Ally)";
|
var message = order.TargetString;
|
||||||
|
if (message.Length > ChatMessageMaxLength)
|
||||||
|
message = order.TargetString[..ChatMessageMaxLength];
|
||||||
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
// ExtraData 0 means this is a normal chat order, everything else is team chat
|
||||||
break;
|
if (order.ExtraData == 0)
|
||||||
}
|
{
|
||||||
|
var p = world?.FindPlayerByClient(client);
|
||||||
|
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
|
||||||
|
suffix = client.IsObserver ? " (Spectator)" : suffix;
|
||||||
|
|
||||||
// We are still in the lobby
|
if (orderManager.LocalClient != null && client != orderManager.LocalClient && client.Team > 0 && client.Team == orderManager.LocalClient.Team)
|
||||||
if (world == null)
|
suffix += " (Ally)";
|
||||||
{
|
|
||||||
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
TextNotificationsManager.AddChatLine(clientId, client.Name + suffix, message, client.Color);
|
||||||
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
break;
|
||||||
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
}
|
||||||
|
|
||||||
|
// We are still in the lobby
|
||||||
|
if (world == null)
|
||||||
|
{
|
||||||
|
var prefix = order.ExtraData == uint.MaxValue ? "[Spectators] " : "[Team] ";
|
||||||
|
if (orderManager.LocalClient != null && client.Team == orderManager.LocalClient.Team)
|
||||||
|
TextNotificationsManager.AddChatLine(clientId, prefix + client.Name, message, client.Color);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = world.FindPlayerByClient(client);
|
||||||
|
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
||||||
|
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||||
|
|
||||||
|
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
||||||
|
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
||||||
|
{
|
||||||
|
// Validate before adding the line
|
||||||
|
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
||||||
|
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
||||||
|
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
||||||
|
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
||||||
|
|
||||||
|
if (valid && (isSameTeam || world.IsReplay))
|
||||||
|
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = world.FindPlayerByClient(client);
|
|
||||||
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
|
||||||
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
|
||||||
|
|
||||||
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
|
||||||
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
|
||||||
{
|
|
||||||
// Validate before adding the line
|
|
||||||
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, "[Spectators] " + client.Name, message, client.Color);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
|
||||||
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
|
||||||
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
|
||||||
|
|
||||||
if (valid && (isSameTeam || world.IsReplay))
|
|
||||||
TextNotificationsManager.AddChatLine(clientId, "[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, message, client.Color);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "StartGame":
|
case "StartGame":
|
||||||
{
|
|
||||||
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
|
||||||
{
|
{
|
||||||
Game.Disconnect();
|
if (Game.ModData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available)
|
||||||
Game.LoadShellMap();
|
{
|
||||||
|
Game.Disconnect();
|
||||||
|
Game.LoadShellMap();
|
||||||
|
|
||||||
// TODO: After adding a startup error dialog, notify the replay load failure.
|
// TODO: After adding a startup error dialog, notify the replay load failure.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(order.TargetString))
|
||||||
|
{
|
||||||
|
var data = MiniYaml.FromString(order.TargetString);
|
||||||
|
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
||||||
|
if (saveLastOrdersFrame != null)
|
||||||
|
orderManager.GameSaveLastFrame =
|
||||||
|
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
||||||
|
|
||||||
|
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
||||||
|
if (saveSyncFrame != null)
|
||||||
|
orderManager.GameSaveLastSyncFrame =
|
||||||
|
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
TextNotificationsManager.AddSystemLine("The game has started.");
|
||||||
|
|
||||||
|
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(order.TargetString))
|
|
||||||
{
|
|
||||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame");
|
|
||||||
if (saveLastOrdersFrame != null)
|
|
||||||
orderManager.GameSaveLastFrame =
|
|
||||||
FieldLoader.GetValue<int>("saveLastOrdersFrame", saveLastOrdersFrame.Value.Value);
|
|
||||||
|
|
||||||
var saveSyncFrame = data.FirstOrDefault(n => n.Key == "SaveSyncFrame");
|
|
||||||
if (saveSyncFrame != null)
|
|
||||||
orderManager.GameSaveLastSyncFrame =
|
|
||||||
FieldLoader.GetValue<int>("SaveSyncFrame", saveSyncFrame.Value.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
TextNotificationsManager.AddSystemLine(GameStarted);
|
|
||||||
|
|
||||||
Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, WorldType.Regular);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "SaveTraitData":
|
case "SaveTraitData":
|
||||||
{
|
{
|
||||||
var data = MiniYaml.FromString(order.TargetString, order.OrderString)[0];
|
var data = MiniYaml.FromString(order.TargetString)[0];
|
||||||
var traitIndex = Exts.ParseInt32Invariant(data.Key);
|
var traitIndex = int.Parse(data.Key);
|
||||||
|
|
||||||
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
world?.AddGameSaveTraitData(traitIndex, data.Value);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "GameSaved":
|
case "GameSaved":
|
||||||
if (!orderManager.World.IsReplay)
|
if (!orderManager.World.IsReplay)
|
||||||
TextNotificationsManager.AddSystemLine(GameSaved);
|
TextNotificationsManager.AddSystemLine("Game saved");
|
||||||
|
|
||||||
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
foreach (var nsr in orderManager.World.WorldActor.TraitsImplementing<INotifyGameSaved>())
|
||||||
nsr.GameSaved(orderManager.World);
|
nsr.GameSaved(orderManager.World);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "PauseGame":
|
case "PauseGame":
|
||||||
{
|
|
||||||
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
|
||||||
if (client != null)
|
|
||||||
{
|
{
|
||||||
var pause = order.TargetString == "Pause";
|
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
|
||||||
|
if (client != null)
|
||||||
|
{
|
||||||
|
var pause = order.TargetString == "Pause";
|
||||||
|
|
||||||
// Prevent injected unpause orders from restarting a finished game
|
// Prevent injected unpause orders from restarting a finished game
|
||||||
if (orderManager.World.IsGameOver && !pause)
|
if (orderManager.World.IsGameOver && !pause)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
if (orderManager.World.Paused != pause && world != null && world.LobbyInfo.NonBotClients.Count() > 1)
|
||||||
TextNotificationsManager.AddSystemLine(pause ? GamePaused : GameUnpaused, "player", client.Name);
|
{
|
||||||
|
var pausetext = $"The game is {(pause ? "paused" : "un-paused")} by {client.Name}";
|
||||||
|
TextNotificationsManager.AddSystemLine(pausetext);
|
||||||
|
}
|
||||||
|
|
||||||
orderManager.World.Paused = pause;
|
orderManager.World.Paused = pause;
|
||||||
orderManager.World.PredictedPaused = pause;
|
orderManager.World.PredictedPaused = pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "HandshakeRequest":
|
case "HandshakeRequest":
|
||||||
{
|
|
||||||
// Switch to the server's mod if we need and are able to
|
|
||||||
var mod = Game.ModData.Manifest;
|
|
||||||
var request = HandshakeRequest.Deserialize(order.TargetString, order.OrderString);
|
|
||||||
|
|
||||||
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
|
||||||
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
|
||||||
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
|
||||||
{
|
{
|
||||||
// The ConnectionFailedLogic will prompt the user to switch mods
|
// Switch to the server's mod if we need and are able to
|
||||||
CurrentServerSettings.ServerExternalMod = external;
|
var mod = Game.ModData.Manifest;
|
||||||
orderManager.Connection.Dispose();
|
var request = HandshakeRequest.Deserialize(order.TargetString);
|
||||||
|
|
||||||
|
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
|
||||||
|
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
|
||||||
|
Game.ExternalMods.TryGetValue(externalKey, out var external))
|
||||||
|
{
|
||||||
|
// The ConnectionFailedLogic will prompt the user to switch mods
|
||||||
|
CurrentServerSettings.ServerExternalMod = external;
|
||||||
|
orderManager.Connection.Dispose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
||||||
|
Game.Settings.Save();
|
||||||
|
|
||||||
|
// Otherwise send the handshake with our current settings and let the server reject us
|
||||||
|
var info = new Session.Client()
|
||||||
|
{
|
||||||
|
Name = Game.Settings.Player.Name,
|
||||||
|
PreferredColor = Game.Settings.Player.Color,
|
||||||
|
Color = Game.Settings.Player.Color,
|
||||||
|
Faction = "Random",
|
||||||
|
SpawnPoint = 0,
|
||||||
|
Team = 0,
|
||||||
|
State = Session.ClientState.Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
var localProfile = Game.LocalPlayerProfile;
|
||||||
|
var response = new HandshakeResponse()
|
||||||
|
{
|
||||||
|
Client = info,
|
||||||
|
Mod = mod.Id,
|
||||||
|
Version = mod.Metadata.Version,
|
||||||
|
Password = CurrentServerSettings.Password,
|
||||||
|
Fingerprint = localProfile.Fingerprint,
|
||||||
|
OrdersProtocol = ProtocolVersion.Orders
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.AuthToken != null && response.Fingerprint != null)
|
||||||
|
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
||||||
|
|
||||||
|
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
||||||
|
{
|
||||||
|
Type = OrderType.Handshake,
|
||||||
|
IsImmediate = true,
|
||||||
|
TargetString = response.Serialize()
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.Settings.Player.Name = Settings.SanitizedPlayerName(Game.Settings.Player.Name);
|
|
||||||
Game.Settings.Save();
|
|
||||||
|
|
||||||
// Otherwise send the handshake with our current settings and let the server reject us
|
|
||||||
var info = new Session.Client()
|
|
||||||
{
|
|
||||||
Name = Game.Settings.Player.Name,
|
|
||||||
PreferredColor = Game.Settings.Player.Color,
|
|
||||||
Color = Game.Settings.Player.Color,
|
|
||||||
Faction = "Random",
|
|
||||||
SpawnPoint = 0,
|
|
||||||
Team = 0,
|
|
||||||
State = Session.ClientState.Invalid
|
|
||||||
};
|
|
||||||
|
|
||||||
var localProfile = Game.LocalPlayerProfile;
|
|
||||||
var response = new HandshakeResponse()
|
|
||||||
{
|
|
||||||
Client = info,
|
|
||||||
Mod = mod.Id,
|
|
||||||
Version = mod.Metadata.Version,
|
|
||||||
Password = CurrentServerSettings.Password,
|
|
||||||
Fingerprint = localProfile.Fingerprint,
|
|
||||||
OrdersProtocol = ProtocolVersion.Orders
|
|
||||||
};
|
|
||||||
|
|
||||||
if (request.AuthToken != null && response.Fingerprint != null)
|
|
||||||
response.AuthSignature = localProfile.Sign(request.AuthToken);
|
|
||||||
|
|
||||||
orderManager.IssueOrder(new Order("HandshakeResponse", null, false)
|
|
||||||
{
|
|
||||||
Type = OrderType.Handshake,
|
|
||||||
IsImmediate = true,
|
|
||||||
TargetString = response.Serialize()
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "ServerError":
|
case "ServerError":
|
||||||
{
|
{
|
||||||
orderManager.ServerError = order.TargetString;
|
orderManager.ServerError = order.TargetString;
|
||||||
orderManager.AuthenticationFailed = false;
|
orderManager.AuthenticationFailed = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "AuthenticationError":
|
case "AuthenticationError":
|
||||||
{
|
{
|
||||||
// The ConnectionFailedLogic will prompt the user for the password
|
// The ConnectionFailedLogic will prompt the user for the password
|
||||||
orderManager.ServerError = order.TargetString;
|
orderManager.ServerError = order.TargetString;
|
||||||
orderManager.AuthenticationFailed = true;
|
orderManager.AuthenticationFailed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncInfo":
|
case "SyncInfo":
|
||||||
{
|
{
|
||||||
orderManager.LobbyInfo = Session.Deserialize(order.TargetString, order.OrderString);
|
orderManager.LobbyInfo = Session.Deserialize(order.TargetString);
|
||||||
Game.SyncLobbyInfo();
|
Game.SyncLobbyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncLobbyClients":
|
case "SyncLobbyClients":
|
||||||
{
|
|
||||||
var clients = new List<Session.Client>();
|
|
||||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var clients = new List<Session.Client>();
|
||||||
if (strings[0] == "Client")
|
var nodes = MiniYaml.FromString(order.TargetString);
|
||||||
clients.Add(Session.Client.Deserialize(node.Value));
|
foreach (var node in nodes)
|
||||||
}
|
{
|
||||||
|
var strings = node.Key.Split('@');
|
||||||
|
if (strings[0] == "Client")
|
||||||
|
clients.Add(Session.Client.Deserialize(node.Value));
|
||||||
|
}
|
||||||
|
|
||||||
orderManager.LobbyInfo.Clients = clients;
|
orderManager.LobbyInfo.Clients = clients;
|
||||||
Game.SyncLobbyInfo();
|
Game.SyncLobbyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncLobbySlots":
|
case "SyncLobbySlots":
|
||||||
{
|
|
||||||
var slots = new Dictionary<string, Session.Slot>();
|
|
||||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var slots = new Dictionary<string, Session.Slot>();
|
||||||
if (strings[0] == "Slot")
|
var nodes = MiniYaml.FromString(order.TargetString);
|
||||||
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var slot = Session.Slot.Deserialize(node.Value);
|
var strings = node.Key.Split('@');
|
||||||
slots.Add(slot.PlayerReference, slot);
|
if (strings[0] == "Slot")
|
||||||
|
{
|
||||||
|
var slot = Session.Slot.Deserialize(node.Value);
|
||||||
|
slots.Add(slot.PlayerReference, slot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
orderManager.LobbyInfo.Slots = slots;
|
orderManager.LobbyInfo.Slots = slots;
|
||||||
Game.SyncLobbyInfo();
|
Game.SyncLobbyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncLobbyGlobalSettings":
|
case "SyncLobbyGlobalSettings":
|
||||||
{
|
|
||||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var nodes = MiniYaml.FromString(order.TargetString);
|
||||||
if (strings[0] == "GlobalSettings")
|
foreach (var node in nodes)
|
||||||
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
{
|
||||||
}
|
var strings = node.Key.Split('@');
|
||||||
|
if (strings[0] == "GlobalSettings")
|
||||||
|
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
|
||||||
|
}
|
||||||
|
|
||||||
Game.SyncLobbyInfo();
|
Game.SyncLobbyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SyncConnectionQuality":
|
case "SyncConnectionQuality":
|
||||||
{
|
|
||||||
var nodes = MiniYaml.FromString(order.TargetString, order.OrderString);
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
{
|
||||||
var strings = node.Key.Split('@');
|
var nodes = MiniYaml.FromString(order.TargetString);
|
||||||
if (strings[0] == "ConnectionQuality")
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == Exts.ParseInt32Invariant(strings[1]));
|
var strings = node.Key.Split('@');
|
||||||
if (client != null)
|
if (strings[0] == "ConnectionQuality")
|
||||||
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
{
|
||||||
|
var client = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.Index == int.Parse(strings[1]));
|
||||||
|
if (client != null)
|
||||||
|
client.ConnectionQuality = FieldLoader.GetValue<Session.ConnectionQuality>("ConnectionQuality", node.Value.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "SyncMapPool":
|
|
||||||
{
|
|
||||||
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if (world == null)
|
if (world == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (order.GroupedActors == null)
|
||||||
|
ResolveOrder(order, world, orderManager, clientId);
|
||||||
|
else
|
||||||
|
foreach (var subject in order.GroupedActors)
|
||||||
|
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
if (order.GroupedActors == null)
|
|
||||||
ResolveOrder(order, world, orderManager, clientId);
|
|
||||||
else
|
|
||||||
foreach (var subject in order.GroupedActors)
|
|
||||||
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user