Compare commits
75 Commits
devtest-20
...
devtest-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afde6850d1 | ||
|
|
297d01d463 | ||
|
|
f9d6338a98 | ||
|
|
18d1d7d62a | ||
|
|
63dd4b382e | ||
|
|
f61fda19f5 | ||
|
|
c18d509ddd | ||
|
|
1782794bac | ||
|
|
5e74e58b22 | ||
|
|
fef7a018f2 | ||
|
|
1d4891b017 | ||
|
|
e9a803e3c1 | ||
|
|
4abe7d5895 | ||
|
|
cd68db5c11 | ||
|
|
ae9f437bb8 | ||
|
|
a047893049 | ||
|
|
5b2733decf | ||
|
|
ff60540fac | ||
|
|
a68a91bb39 | ||
|
|
0bf8d2241f | ||
|
|
06850e6271 | ||
|
|
62fa3b7c9c | ||
|
|
ce09b402d0 | ||
|
|
6e7ad9df25 | ||
|
|
fb20479379 | ||
|
|
9d181e88d2 | ||
|
|
e90fc1ef39 | ||
|
|
ce013f17d6 | ||
|
|
78253ce284 | ||
|
|
2671e40c1d | ||
|
|
04cda69ef9 | ||
|
|
b4c483ce1a | ||
|
|
9a9f58d744 | ||
|
|
d38fe542a2 | ||
|
|
80503fbf36 | ||
|
|
99a23b4056 | ||
|
|
13a7de4b6b | ||
|
|
73bba97aaa | ||
|
|
d6e9cdab5b | ||
|
|
1a177bc2de | ||
|
|
e0b3e631fe | ||
|
|
2518a353af | ||
|
|
989800efff | ||
|
|
c02846e2cb | ||
|
|
09db4a0e25 | ||
|
|
a85da9d86c | ||
|
|
920d00bbae | ||
|
|
62475279ee | ||
|
|
d8e979d283 | ||
|
|
299b8880dd | ||
|
|
61027e4067 | ||
|
|
a7249c10dc | ||
|
|
611d12ac78 | ||
|
|
ef9f26a60d | ||
|
|
aeaffc0a8e | ||
|
|
e3084e230e | ||
|
|
4c01c772f8 | ||
|
|
ed94f7680a | ||
|
|
408d66cdaf | ||
|
|
c4a0f2f169 | ||
|
|
26b28d26da | ||
|
|
8ded6dafd4 | ||
|
|
57a94ad667 | ||
|
|
53933a4d8f | ||
|
|
6606d7dd93 | ||
|
|
7a256dcafa | ||
|
|
7899c52b6d | ||
|
|
919c670502 | ||
|
|
aac3174efc | ||
|
|
a8d3d5c79a | ||
|
|
7c852d90fb | ||
|
|
269ce9c406 | ||
|
|
53d98ec255 | ||
|
|
7a7cd21578 | ||
|
|
2b363e4e1c |
55
.github/workflows/ci.yaml
vendored
Normal file
55
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [ bleed ]
|
||||
|
||||
jobs:
|
||||
linux-mono:
|
||||
name: Linux (mono)
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check Code
|
||||
run: |
|
||||
mono --version
|
||||
make check
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
sudo apt-get install lua5.1
|
||||
make check-scripts
|
||||
make test
|
||||
|
||||
windows:
|
||||
name: Windows (Net 5.0)
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Check Code
|
||||
shell: powershell
|
||||
run: |
|
||||
# Work around runtime failures on the GH Actions runner
|
||||
dotnet nuget locals all --clear
|
||||
.\make.ps1 check
|
||||
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
|
||||
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
|
||||
|
||||
- name: Check Mods
|
||||
run: |
|
||||
chocolatey install lua --version 5.1.5.52
|
||||
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
|
||||
.\make.ps1 check-scripts
|
||||
.\make.ps1 test
|
||||
107
.github/workflows/documentation.yml
vendored
Normal file
107
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
wiki:
|
||||
name: Update Wiki
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone Wiki
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/openra.wiki
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: wiki
|
||||
|
||||
- name: Update Wiki (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
|
||||
|
||||
- name: Update Wiki (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
|
||||
|
||||
- name: Push Wiki
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd wiki
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
docs:
|
||||
name: Update docs.openra.net
|
||||
if: github.repository == 'openra/openra'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
make all
|
||||
|
||||
- name: Clone docs.openra.net
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: openra/docs
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
path: docs
|
||||
|
||||
- name: Update docs.openra.net (Playtest)
|
||||
if: startsWith(github.event.inputs.tag, 'playtest-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
|
||||
|
||||
- name: Update docs.openra.net (Release)
|
||||
if: startsWith(github.event.inputs.tag, 'release-')
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
|
||||
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
|
||||
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
|
||||
|
||||
- name: Push docs.openra.net
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
cd docs
|
||||
git config --local user.email "actions@github.com"
|
||||
git config --local user.name "GitHub Actions"
|
||||
git add --all
|
||||
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
|
||||
git push origin master
|
||||
|
||||
88
.github/workflows/itch.yml
vendored
Normal file
88
.github/workflows/itch.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Deploy itch.io Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git Tag'
|
||||
required: true
|
||||
default: 'release-xxxxxxxx'
|
||||
|
||||
jobs:
|
||||
itch:
|
||||
name: Deploy to itch.io
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'openra/openra'
|
||||
steps:
|
||||
- name: Download Packages
|
||||
env:
|
||||
GIT_TAG: ${{ github.event.inputs.tag }}
|
||||
run: |
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64.exe"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64-winportable.zip" -O "OpenRA-${GIT_TAG}-x64-win-itch.zip"
|
||||
wget -q "https://github.com${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}.dmg"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Dune-2000-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Red-Alert-x86_64.AppImage"
|
||||
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
|
||||
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${GIT_TAG}/packaging/.itch.toml"
|
||||
zip -u "OpenRA-${GIT_TAG}-x64-win-itch.zip" .itch.toml
|
||||
|
||||
- name: Publish Windows Installer
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: win
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
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-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
|
||||
|
||||
- name: Publish macOS Package
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: macos
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg"
|
||||
|
||||
- name: Publish RA AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-ra
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
|
||||
|
||||
- name: Publish TD AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-cnc
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
|
||||
|
||||
- name: Publish D2k AppImage
|
||||
uses: josephbmanley/butler-publish-itchio-action@master
|
||||
env:
|
||||
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
CHANNEL: linux-d2k
|
||||
ITCH_GAME: openra
|
||||
ITCH_USER: openra-developers
|
||||
VERSION: ${{ github.event.inputs.tag }}
|
||||
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage
|
||||
99
.github/workflows/packaging.yml
vendored
Normal file
99
.github/workflows/packaging.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Release Packaging
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'release-*'
|
||||
- 'playtest-*'
|
||||
- 'devtest-*'
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux AppImages
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package AppImages
|
||||
run: |
|
||||
mkdir -p build/linux
|
||||
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/linux/*
|
||||
|
||||
macos:
|
||||
name: macOS Disk Images
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Package Disk Images
|
||||
env:
|
||||
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
|
||||
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
|
||||
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
|
||||
run: |
|
||||
mkdir -p build/macos
|
||||
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/macos/*
|
||||
|
||||
windows:
|
||||
name: Windows Installers
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install .NET 5
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '5.0.x'
|
||||
|
||||
- name: Prepare Environment
|
||||
run: |
|
||||
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
|
||||
sudo apt install nsis wine64
|
||||
|
||||
- name: Package Installers
|
||||
run: |
|
||||
mkdir -p build/windows
|
||||
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
|
||||
|
||||
- name: Upload Packages
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file: build/windows/*
|
||||
92
.travis.yml
92
.travis.yml
@@ -1,92 +0,0 @@
|
||||
# Travis-CI Build for OpenRA
|
||||
# see travis-ci.org for details
|
||||
|
||||
language: csharp
|
||||
mono: 6.4.0
|
||||
os: linux
|
||||
dist: xenial
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
- os: osx
|
||||
if: tag IS present
|
||||
osx_image: xcode10
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- lua5.1
|
||||
- dpkg
|
||||
- zsync
|
||||
- imagemagick
|
||||
|
||||
# Environment variables
|
||||
env:
|
||||
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
|
||||
|
||||
# Fetch dependencies
|
||||
# Run the build script
|
||||
# Check source code with StyleCop
|
||||
# call OpenRA to check for YAML errors
|
||||
# Run the NUnit tests
|
||||
script:
|
||||
- make all
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
make check || travis_terminate 1;
|
||||
make check-scripts || travis_terminate 1;
|
||||
make test || travis_terminate 1;
|
||||
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll || travis_terminate 1;
|
||||
fi
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
only:
|
||||
- /^release-.*$/
|
||||
- /^playtest-.*$/
|
||||
- /^devtest-.*$/
|
||||
- /^prep-.*$/
|
||||
- bleed
|
||||
|
||||
# Notify developers when build passed/failed.
|
||||
notifications:
|
||||
irc:
|
||||
if: repo = OpenRA/OpenRA
|
||||
template:
|
||||
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
|
||||
channels:
|
||||
- "irc.freenode.net#openra"
|
||||
use_notice: true
|
||||
skip_join: true
|
||||
|
||||
before_deploy:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
|
||||
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
|
||||
sudo dpkg -i nsis-common_3.04-1_all.deb;
|
||||
sudo dpkg -i nsis_3.04-1_amd64.deb;
|
||||
echo ${TRAVIS_REPO_SLUG};
|
||||
if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
|
||||
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && ./update-docs.sh ${TRAVIS_TAG} && cd ..;
|
||||
fi;
|
||||
fi
|
||||
- export PATH=${PATH}:${HOME}/usr/bin
|
||||
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
|
||||
- cd packaging
|
||||
- mkdir build
|
||||
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
|
||||
- if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
|
||||
./upload-itch.sh ${TRAVIS_TAG} ${PWD}/build/;
|
||||
fi
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: ${GH_DEPLOY_API_KEY}
|
||||
file_glob: true
|
||||
file: build/*
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@@ -11,9 +11,8 @@
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=cnc"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
@@ -26,9 +25,8 @@
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ra"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
@@ -41,9 +39,8 @@
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=d2k"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
{
|
||||
@@ -56,9 +53,8 @@
|
||||
"type": "mono"
|
||||
},
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/OpenRA.Game.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": ["Game.Mod=ts"],
|
||||
"program": "${workspaceRoot}/bin/OpenRA.exe",
|
||||
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
|
||||
"preLaunchTask": "build",
|
||||
},
|
||||
]
|
||||
|
||||
10
INSTALL.md
10
INSTALL.md
@@ -8,18 +8,16 @@ Windows
|
||||
|
||||
Compiling OpenRA requires the following dependencies:
|
||||
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
|
||||
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
* [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (or via Visual Studio)
|
||||
|
||||
|
||||
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild 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.
|
||||
|
||||
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
Mono, version 6.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
|
||||
|
||||
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
|
||||
|
||||
@@ -99,7 +97,7 @@ macOS
|
||||
=====
|
||||
|
||||
Before compiling OpenRA you must install the following dependencies:
|
||||
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
|
||||
* [Mono >= 6.4](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
||||
|
||||
|
||||
23
Makefile
23
Makefile
@@ -25,18 +25,6 @@
|
||||
# make help
|
||||
#
|
||||
|
||||
############################## TOOLCHAIN ###############################
|
||||
#
|
||||
# List of .NET assemblies that we can guarantee exist
|
||||
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.exe OpenRA.Utility.exe OpenRA.Server.exe OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
|
||||
|
||||
# These are explicitly shipped alongside our core files by the packaging script
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
|
||||
|
||||
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
|
||||
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
|
||||
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
|
||||
|
||||
######################### UTILITIES/SETTINGS ###########################
|
||||
#
|
||||
# Install locations for local installs and downstream packaging
|
||||
@@ -78,13 +66,13 @@ endif
|
||||
endif
|
||||
endif
|
||||
|
||||
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
|
||||
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.dll
|
||||
|
||||
##################### DEVELOPMENT BUILDS AND TESTS #####################
|
||||
#
|
||||
all:
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
|
||||
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
|
||||
ifeq ($(TARGETPLATFORM), unix-generic)
|
||||
@./configure-system-libraries.sh
|
||||
endif
|
||||
@@ -92,16 +80,13 @@ endif
|
||||
|
||||
clean:
|
||||
@-$(RM_RF) ./bin ./*/bin ./*/obj
|
||||
@$(MSBUILD) -t:Clean
|
||||
@$(MSBUILD) -t:Clean -p:Mono=true
|
||||
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug
|
||||
@echo
|
||||
@echo "Checking runtime assemblies..."
|
||||
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
|
||||
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
|
||||
@echo
|
||||
@echo "Checking for explicit interface violations..."
|
||||
@$(OPENRA_UTILITY) all --check-explicit-interfaces
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace OpenRA.Activities
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
while (first != null && first.State == ActivityState.Done)
|
||||
first = first.NextActivity;
|
||||
first = first.nextActivity;
|
||||
|
||||
return first;
|
||||
}
|
||||
@@ -120,7 +120,8 @@ namespace OpenRA.Activities
|
||||
lastRun = Tick(self);
|
||||
|
||||
// Avoid a single tick delay if the childactivity was just queued.
|
||||
if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
|
||||
var ca = ChildActivity;
|
||||
if (ca != null && ca.State == ActivityState.Queued)
|
||||
{
|
||||
if (ChildHasPriority)
|
||||
lastRun = TickChild(self) && finishing;
|
||||
@@ -206,18 +207,18 @@ namespace OpenRA.Activities
|
||||
|
||||
public void Queue(Activity activity)
|
||||
{
|
||||
if (NextActivity != null)
|
||||
NextActivity.Queue(activity);
|
||||
else
|
||||
NextActivity = activity;
|
||||
var it = this;
|
||||
while (it.nextActivity != null)
|
||||
it = it.nextActivity;
|
||||
it.nextActivity = activity;
|
||||
}
|
||||
|
||||
public void QueueChild(Activity activity)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
ChildActivity.Queue(activity);
|
||||
if (childActivity != null)
|
||||
childActivity.Queue(activity);
|
||||
else
|
||||
ChildActivity = activity;
|
||||
childActivity = activity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -269,15 +270,21 @@ namespace OpenRA.Activities
|
||||
|
||||
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
|
||||
{
|
||||
if (includeChildren && ChildActivity != null)
|
||||
foreach (var a in ChildActivity.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
// Skips Done child and next activities
|
||||
if (includeChildren)
|
||||
{
|
||||
var ca = ChildActivity;
|
||||
if (ca != null)
|
||||
foreach (var a in ca.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
|
||||
if (this is T)
|
||||
yield return (T)(object)this;
|
||||
|
||||
if (NextActivity != null)
|
||||
foreach (var a in NextActivity.ActivitiesImplementing<T>())
|
||||
var na = NextActivity;
|
||||
if (na != null)
|
||||
foreach (var a in na.ActivitiesImplementing<T>())
|
||||
yield return a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,17 +66,11 @@ namespace OpenRA
|
||||
|
||||
static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
|
||||
new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
|
||||
static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
|
||||
new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
|
||||
|
||||
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
|
||||
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
|
||||
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
|
||||
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
|
||||
|
||||
static readonly object TranslationsLock = new object();
|
||||
static Dictionary<string, string> translations;
|
||||
|
||||
public static void Load(object self, MiniYaml my)
|
||||
{
|
||||
var loadInfo = TypeLoadInfo[self.GetType()];
|
||||
@@ -213,11 +207,7 @@ namespace OpenRA
|
||||
return InvalidValueAction(value, fieldType, fieldName);
|
||||
}
|
||||
else if (fieldType == typeof(string))
|
||||
{
|
||||
if (field != null && MemberHasTranslateAttribute[field] && value != null)
|
||||
return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
|
||||
return value;
|
||||
}
|
||||
else if (fieldType == typeof(Color))
|
||||
{
|
||||
if (value != null && Color.TryParse(value, out var color))
|
||||
@@ -694,34 +684,8 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Translate(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return key;
|
||||
|
||||
lock (TranslationsLock)
|
||||
{
|
||||
if (translations == null)
|
||||
return key;
|
||||
|
||||
if (!translations.TryGetValue(key, out var value))
|
||||
return key;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTranslations(IDictionary<string, string> translations)
|
||||
{
|
||||
lock (TranslationsLock)
|
||||
FieldLoader.translations = new Dictionary<string, string>(translations);
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class TranslateAttribute : Attribute { }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksum;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
@@ -25,12 +26,15 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public Color[] Palette { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public Color[] Palette { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
public SpriteFrameType Type { get; private set; }
|
||||
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
|
||||
|
||||
public int PixelStride { get { return Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4; } }
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
if (!Verify(s))
|
||||
@@ -38,9 +42,8 @@ namespace OpenRA.FileFormats
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
var isPaletted = false;
|
||||
var is24Bit = false;
|
||||
var data = new List<byte>();
|
||||
Type = SpriteFrameType.Rgba32;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -65,14 +68,12 @@ namespace OpenRA.FileFormats
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
isPaletted = IsPaletted(bitDepth, colorType);
|
||||
is24Bit = colorType == PngColorType.Color;
|
||||
if (IsPaletted(bitDepth, colorType))
|
||||
Type = SpriteFrameType.Indexed8;
|
||||
else if (colorType == PngColorType.Color)
|
||||
Type = SpriteFrameType.Rgb24;
|
||||
|
||||
var dataLength = Width * Height;
|
||||
if (!isPaletted)
|
||||
dataLength *= 4;
|
||||
|
||||
Data = new byte[dataLength];
|
||||
Data = new byte[Width * Height * PixelStride];
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
@@ -133,39 +134,28 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var ds = new InflaterInputStream(ns))
|
||||
{
|
||||
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
|
||||
var srcStride = Width * pxStride;
|
||||
var destStride = Width * (isPaletted ? 1 : 4);
|
||||
var pxStride = PixelStride;
|
||||
var rowStride = Width * pxStride;
|
||||
|
||||
var prevLine = new byte[srcStride];
|
||||
var prevLine = new byte[rowStride];
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(srcStride);
|
||||
var line = ds.ReadBytes(rowStride);
|
||||
|
||||
for (var i = 0; i < srcStride; i++)
|
||||
for (var i = 0; i < rowStride; i++)
|
||||
line[i] = i < pxStride
|
||||
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
|
||||
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
|
||||
|
||||
if (is24Bit)
|
||||
{
|
||||
// Fold alpha channel into RGB data
|
||||
for (var i = 0; i < line.Length / 3; i++)
|
||||
{
|
||||
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
|
||||
Data[y * destStride + 4 * i + 3] = 255;
|
||||
}
|
||||
}
|
||||
else
|
||||
Array.Copy(line, 0, Data, y * destStride, line.Length);
|
||||
Array.Copy(line, 0, Data, y * rowStride, rowStride);
|
||||
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPaletted && Palette == null)
|
||||
if (Type == SpriteFrameType.Indexed8 && Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
return;
|
||||
@@ -175,7 +165,7 @@ namespace OpenRA.FileFormats
|
||||
}
|
||||
}
|
||||
|
||||
public Png(byte[] data, int width, int height, Color[] palette = null,
|
||||
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
|
||||
Dictionary<string, string> embeddedData = null)
|
||||
{
|
||||
var expectLength = width * height;
|
||||
@@ -185,11 +175,46 @@ namespace OpenRA.FileFormats
|
||||
if (data.Length != expectLength)
|
||||
throw new InvalidDataException("Input data does not match expected length");
|
||||
|
||||
Type = type;
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
Palette = palette;
|
||||
Data = data;
|
||||
switch (type)
|
||||
{
|
||||
case SpriteFrameType.Indexed8:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
// Data is already in a compatible format
|
||||
Data = data;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
Palette = palette;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
{
|
||||
// Convert to big endian
|
||||
Data = new byte[data.Length];
|
||||
var stride = PixelStride;
|
||||
for (var i = 0; i < width * height; i++)
|
||||
{
|
||||
Data[stride * i] = data[stride * i + 2];
|
||||
Data[stride * i + 1] = data[stride * i + 1];
|
||||
Data[stride * i + 2] = data[stride * i + 0];
|
||||
|
||||
if (type == SpriteFrameType.Bgra32)
|
||||
Data[stride * i + 3] = data[stride * i + 3];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException("Unhandled SpriteFrameType {0}".F(type));
|
||||
}
|
||||
|
||||
if (embeddedData != null)
|
||||
EmbeddedData = embeddedData;
|
||||
@@ -274,9 +299,8 @@ namespace OpenRA.FileFormats
|
||||
header.Write(IPAddress.HostToNetworkOrder(Height));
|
||||
header.WriteByte(8); // Bit depth
|
||||
|
||||
var colorType = Palette != null
|
||||
? PngColorType.Indexed | PngColorType.Color
|
||||
: PngColorType.Color | PngColorType.Alpha;
|
||||
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color :
|
||||
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha;
|
||||
header.WriteByte((byte)colorType);
|
||||
|
||||
header.WriteByte(0); // Compression
|
||||
@@ -286,7 +310,7 @@ namespace OpenRA.FileFormats
|
||||
WritePngChunk(output, "IHDR", header);
|
||||
}
|
||||
|
||||
bool alphaPalette = false;
|
||||
var alphaPalette = false;
|
||||
if (Palette != null)
|
||||
{
|
||||
using (var palette = new MemoryStream())
|
||||
@@ -318,12 +342,12 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
using (var compressed = new DeflaterOutputStream(data))
|
||||
{
|
||||
var stride = Width * (Palette != null ? 1 : 4);
|
||||
var rowStride = Width * PixelStride;
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
// Write uncompressed scanlines for simplicity
|
||||
compressed.WriteByte(0);
|
||||
compressed.Write(Data, y * stride, stride);
|
||||
compressed.Write(Data, y * rowStride, rowStride);
|
||||
}
|
||||
|
||||
compressed.Flush();
|
||||
|
||||
@@ -297,6 +297,7 @@ namespace OpenRA
|
||||
EngineVersion = "Unknown";
|
||||
|
||||
Console.WriteLine("Engine version is {0}", EngineVersion);
|
||||
Console.WriteLine("Runtime: {0}", Platform.RuntimeVersion);
|
||||
|
||||
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
||||
// then we use this to override the mod search path, and replace it with the mod id
|
||||
@@ -329,9 +330,16 @@ namespace OpenRA
|
||||
try
|
||||
{
|
||||
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
|
||||
var assembly = Assembly.LoadFile(rendererPath);
|
||||
|
||||
#if !MONO
|
||||
var loader = new AssemblyLoader(rendererPath);
|
||||
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
|
||||
|
||||
#else
|
||||
var assembly = 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.");
|
||||
|
||||
@@ -476,6 +484,8 @@ namespace OpenRA
|
||||
|
||||
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
|
||||
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
|
||||
if (!ChromeMetrics.TryGet("SystemMessageLabel", out systemMessageLabel))
|
||||
systemMessageLabel = "Battlefield Control";
|
||||
|
||||
ModData.LoadScreen.StartGame(args);
|
||||
}
|
||||
@@ -545,6 +555,8 @@ namespace OpenRA
|
||||
static volatile ActionQueue delayedActions = new ActionQueue();
|
||||
static Color systemMessageColor = Color.White;
|
||||
static Color chatMessageColor = Color.White;
|
||||
static string systemMessageLabel;
|
||||
|
||||
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
|
||||
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
|
||||
|
||||
@@ -876,7 +888,7 @@ namespace OpenRA
|
||||
|
||||
public static void AddSystemLine(string text)
|
||||
{
|
||||
AddSystemLine("Battlefield Control", text);
|
||||
AddSystemLine(systemMessageLabel, text);
|
||||
}
|
||||
|
||||
public static void AddSystemLine(string name, string text)
|
||||
@@ -934,9 +946,11 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
// Always connect to local games using the same loopback connection
|
||||
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
|
||||
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Loopback, 0),
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
|
||||
@@ -119,6 +119,8 @@ namespace OpenRA
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Faction.Name,
|
||||
FactionId = runtimePlayer.Faction.InternalName,
|
||||
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
|
||||
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
@@ -157,6 +159,10 @@ namespace OpenRA
|
||||
public string FactionId;
|
||||
public Color Color;
|
||||
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public string DisplayFactionName;
|
||||
public string DisplayFactionId;
|
||||
|
||||
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace OpenRA
|
||||
{
|
||||
public class GameSpeed
|
||||
{
|
||||
[Translate]
|
||||
public readonly string Name = "Default";
|
||||
public readonly int Timestep = 40;
|
||||
public readonly int OrderLatency = 3;
|
||||
|
||||
@@ -69,9 +69,16 @@ namespace OpenRA.Graphics
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// SheetBuilder expects data in BGRA
|
||||
var data = FrameToBGRA(kv.Key, f, palette);
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
|
||||
// Resolve indexed data to real colours
|
||||
var data = f.Data;
|
||||
var type = f.Type;
|
||||
if (type == SpriteFrameType.Indexed8)
|
||||
{
|
||||
data = ConvertIndexedToBgra(kv.Key, f, palette);
|
||||
type = SpriteFrameType.Bgra32;
|
||||
}
|
||||
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
@@ -217,33 +224,27 @@ namespace OpenRA.Graphics
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
// Data is already in BGRA format
|
||||
if (frame.Type == SpriteFrameType.BGRA)
|
||||
return frame.Data;
|
||||
if (frame.Type != SpriteFrameType.Indexed8)
|
||||
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
|
||||
|
||||
// Cursors may be either native BGRA or Indexed.
|
||||
// Indexed sprites are converted to BGRA using the referenced palette.
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (frame.Type == SpriteFrameType.Indexed && palette == null)
|
||||
if (palette == null)
|
||||
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
for (var j = 0; j < height; j++)
|
||||
unsafe
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
// Cast the data to an int array so we can copy the src data directly
|
||||
fixed (byte* bd = &data[0])
|
||||
{
|
||||
var rgba = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = (byte)(rgba >> 16);
|
||||
data[k + 1] = (byte)(rgba >> 8);
|
||||
data[k + 2] = (byte)(rgba >> 0);
|
||||
data[k + 3] = (byte)(rgba >> 24);
|
||||
var rgba = (uint*)bd;
|
||||
for (var j = 0; j < height; j++)
|
||||
for (var i = 0; i < width; i++)
|
||||
rgba[j * width + i] = palette[frame.Data[j * width + i]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public interface IModelCache : IDisposable
|
||||
{
|
||||
IModel GetModel(string model);
|
||||
IModel GetModelSequence(string model, string sequence);
|
||||
bool HasModelSequence(string model, string sequence);
|
||||
IVertexBuffer<Vertex> VertexBuffer { get; }
|
||||
@@ -66,6 +67,11 @@ namespace OpenRA.Graphics
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public IModel GetModel(string model)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -79,21 +79,17 @@ namespace OpenRA.Graphics
|
||||
|
||||
public Png AsPng()
|
||||
{
|
||||
var data = GetData();
|
||||
if (Type == SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
|
||||
|
||||
// Convert BGRA to RGBA
|
||||
for (var i = 0; i < Size.Width * Size.Height; i++)
|
||||
{
|
||||
var temp = data[i * 4];
|
||||
data[i * 4] = data[i * 4 + 2];
|
||||
data[i * 4 + 2] = temp;
|
||||
}
|
||||
|
||||
return new Png(data, Size.Width, Size.Height);
|
||||
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
|
||||
}
|
||||
|
||||
public Png AsPng(TextureChannel channel, IPalette pal)
|
||||
{
|
||||
if (Type != SheetType.Indexed)
|
||||
throw new InvalidOperationException("AsPng(TextureChannel, IPalette) can only be called on Indexed sheets.");
|
||||
|
||||
var d = GetData();
|
||||
var plane = new byte[Size.Width * Size.Height];
|
||||
var dataStride = 4 * Size.Width;
|
||||
@@ -107,7 +103,7 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < Palette.Size; i++)
|
||||
palColors[i] = pal.GetColor(i);
|
||||
|
||||
return new Png(plane, Size.Width, Size.Height, palColors);
|
||||
return new Png(plane, SpriteFrameType.Bgra32, Size.Width, Size.Height, palColors);
|
||||
}
|
||||
|
||||
public void CreateBuffer()
|
||||
|
||||
@@ -52,8 +52,15 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case SpriteFrameType.Indexed: return SheetType.Indexed;
|
||||
case SpriteFrameType.BGRA: return SheetType.BGRA;
|
||||
case SpriteFrameType.Indexed8:
|
||||
return SheetType.Indexed;
|
||||
|
||||
// Util.FastCopyIntoChannel will automatically convert these to BGRA
|
||||
case SpriteFrameType.Bgra32:
|
||||
case SpriteFrameType.Bgr24:
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
return SheetType.BGRA;
|
||||
default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t));
|
||||
}
|
||||
}
|
||||
@@ -74,16 +81,16 @@ namespace OpenRA.Graphics
|
||||
this.margin = margin;
|
||||
}
|
||||
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
|
||||
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); }
|
||||
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
|
||||
{
|
||||
// Don't bother allocating empty sprites
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
|
||||
|
||||
var rect = Allocate(size, zRamp, spriteOffset);
|
||||
Util.FastCopyIntoChannel(rect, src);
|
||||
Util.FastCopyIntoChannel(rect, src, type);
|
||||
current.CommitBufferedData();
|
||||
return rect;
|
||||
}
|
||||
@@ -96,15 +103,6 @@ namespace OpenRA.Graphics
|
||||
return rect;
|
||||
}
|
||||
|
||||
public Sprite Add(Size size, byte paletteIndex)
|
||||
{
|
||||
var data = new byte[size.Width * size.Height];
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
data[i] = paletteIndex;
|
||||
|
||||
return Add(data, size);
|
||||
}
|
||||
|
||||
TextureChannel? NextChannel(TextureChannel t)
|
||||
{
|
||||
var nextChannel = (int)t + (int)Type;
|
||||
|
||||
@@ -18,7 +18,29 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public enum SpriteFrameType { Indexed, BGRA }
|
||||
/// <summary>
|
||||
/// Describes the format of the pixel data in a ISpriteFrame.
|
||||
/// Note that the channel order is defined for little-endian bytes, so BGRA corresponds
|
||||
/// to a 32bit ARGB value, such as that returned by Color.ToArgb()!
|
||||
/// </summary>
|
||||
public enum SpriteFrameType
|
||||
{
|
||||
// 8 bit index into an external palette
|
||||
Indexed8,
|
||||
|
||||
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
|
||||
// (remember that little-endian systems place the little bits in the first byte!)
|
||||
Bgra32,
|
||||
|
||||
// Like BGRA, but without an alpha channel
|
||||
Bgr24,
|
||||
|
||||
// 32 bit color in big-endian format, like png
|
||||
Rgba32,
|
||||
|
||||
// Like RGBA, but without an alpha channel
|
||||
Rgb24
|
||||
}
|
||||
|
||||
public interface ISpriteLoader
|
||||
{
|
||||
@@ -47,7 +69,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public class SpriteCache
|
||||
{
|
||||
public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
|
||||
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
|
||||
readonly ISpriteLoader[] loaders;
|
||||
readonly IReadOnlyFileSystem fileSystem;
|
||||
|
||||
@@ -57,7 +79,7 @@ namespace OpenRA.Graphics
|
||||
|
||||
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
|
||||
{
|
||||
SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
|
||||
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
|
||||
|
||||
this.fileSystem = fileSystem;
|
||||
this.loaders = loaders;
|
||||
@@ -103,7 +125,7 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
if (unloaded[i] != null)
|
||||
{
|
||||
sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
|
||||
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
|
||||
unloaded[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +107,13 @@ namespace OpenRA.Graphics
|
||||
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
|
||||
|
||||
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
|
||||
Util.FastCopyIntoChannel(s, f.Data);
|
||||
Util.FastCopyIntoChannel(s, f.Data, f.Type);
|
||||
|
||||
if (tileset.EnableDepth)
|
||||
{
|
||||
var ss = sheetBuilder.Allocate(f.Size, zRamp, offset);
|
||||
Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data);
|
||||
var depthFrame = allFrames[j + frameCount];
|
||||
Util.FastCopyIntoChannel(ss, depthFrame.Data, depthFrame.Type);
|
||||
|
||||
// s and ss are guaranteed to use the same sheet
|
||||
// because of the custom terrain sheet allocation
|
||||
@@ -136,7 +137,10 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// 1x1px transparent tile
|
||||
missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1));
|
||||
if (sheetBuilder.Type == SheetType.BGRA)
|
||||
missingTile = sheetBuilder.Add(new byte[4], SpriteFrameType.Bgra32, new Size(1, 1));
|
||||
else
|
||||
missingTile = sheetBuilder.Add(new byte[1], SpriteFrameType.Indexed8, new Size(1, 1));
|
||||
|
||||
Sheet.ReleaseBuffer();
|
||||
}
|
||||
@@ -168,10 +172,7 @@ namespace OpenRA.Graphics
|
||||
for (var x = 0; x < template.Size.X; x++)
|
||||
{
|
||||
var tile = new TerrainTile(template.Id, (byte)(i++));
|
||||
var tileInfo = tileset.GetTileInfo(tile);
|
||||
|
||||
// Empty tile
|
||||
if (tileInfo == null)
|
||||
if (!tileset.TryGetTileInfo(tile, out var tileInfo))
|
||||
continue;
|
||||
|
||||
var sprite = TileSprite(tile);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace OpenRA.Graphics
|
||||
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
|
||||
}
|
||||
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
|
||||
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
|
||||
{
|
||||
var destData = dest.Sheet.GetData();
|
||||
var width = dest.Bounds.Width;
|
||||
@@ -85,12 +85,34 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var r = src[k++];
|
||||
var g = src[k++];
|
||||
var b = src[k++];
|
||||
var a = src[k++];
|
||||
var cc = Color.FromArgb(a, r, g, b);
|
||||
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 {0}".F(srcType));
|
||||
}
|
||||
|
||||
var cc = Color.FromArgb(a, r, g, b);
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
}
|
||||
@@ -139,16 +161,29 @@ namespace OpenRA.Graphics
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
Color cc;
|
||||
if (src.Palette == null)
|
||||
switch (src.Type)
|
||||
{
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Data[k++];
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
case SpriteFrameType.Indexed8:
|
||||
{
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
break;
|
||||
}
|
||||
|
||||
case SpriteFrameType.Rgba32:
|
||||
case SpriteFrameType.Rgb24:
|
||||
{
|
||||
var r = src.Data[k++];
|
||||
var g = src.Data[k++];
|
||||
var b = src.Data[k++];
|
||||
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
|
||||
cc = Color.FromArgb(a, r, g, b);
|
||||
break;
|
||||
}
|
||||
|
||||
// Pngs don't support BGR[A], so no need to include them here
|
||||
default:
|
||||
throw new InvalidOperationException("Unknown SpriteFrameType {0}".F(src.Type));
|
||||
}
|
||||
else
|
||||
cc = src.Palette[src.Data[k++]];
|
||||
|
||||
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace OpenRA
|
||||
public readonly string[]
|
||||
Rules, ServerTraits,
|
||||
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||
Weapons, Voices, Notifications, Music, TileSets,
|
||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||
|
||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||
@@ -128,7 +128,6 @@ namespace OpenRA
|
||||
Voices = YamlList(yaml, "Voices");
|
||||
Notifications = YamlList(yaml, "Notifications");
|
||||
Music = YamlList(yaml, "Music");
|
||||
Translations = YamlList(yaml, "Translations");
|
||||
TileSets = YamlList(yaml, "TileSets");
|
||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||
Missions = YamlList(yaml, "Missions");
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
@@ -173,7 +174,6 @@ namespace OpenRA
|
||||
new MapField("Voices", "VoiceDefinitions", required: false),
|
||||
new MapField("Music", "MusicDefinitions", required: false),
|
||||
new MapField("Notifications", "NotificationDefinitions", required: false),
|
||||
new MapField("Translations", "TranslationDefinitions", required: false)
|
||||
};
|
||||
|
||||
// Format versions
|
||||
@@ -204,7 +204,8 @@ namespace OpenRA
|
||||
public readonly MiniYaml VoiceDefinitions;
|
||||
public readonly MiniYaml MusicDefinitions;
|
||||
public readonly MiniYaml NotificationDefinitions;
|
||||
public readonly MiniYaml TranslationDefinitions;
|
||||
|
||||
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new Dictionary<CPos, TerrainTile>();
|
||||
|
||||
// Generated data
|
||||
public readonly MapGrid Grid;
|
||||
@@ -287,7 +288,6 @@ namespace OpenRA
|
||||
this.modData = modData;
|
||||
var size = new Size(width, height);
|
||||
Grid = modData.Manifest.Get<MapGrid>();
|
||||
var tileRef = new TerrainTile(tileset.Templates.First().Key, 0);
|
||||
|
||||
Title = "Name your map here";
|
||||
Author = "Your name here";
|
||||
@@ -310,7 +310,7 @@ namespace OpenRA
|
||||
Tiles.CellEntryChanged += UpdateRamp;
|
||||
}
|
||||
|
||||
Tiles.Clear(tileRef);
|
||||
Tiles.Clear(tileset.DefaultTerrainTile);
|
||||
|
||||
PostInit();
|
||||
}
|
||||
@@ -430,12 +430,18 @@ namespace OpenRA
|
||||
foreach (var uv in AllCells.MapCoords)
|
||||
CustomTerrain[uv] = byte.MaxValue;
|
||||
|
||||
// Cache initial ramp state
|
||||
// Replace invalid tiles and cache ramp state
|
||||
var tileset = Rules.TileSet;
|
||||
foreach (var uv in AllCells)
|
||||
foreach (var uv in AllCells.MapCoords)
|
||||
{
|
||||
var tile = tileset.GetTileInfo(Tiles[uv]);
|
||||
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
|
||||
if (!tileset.TryGetTileInfo(Tiles[uv], out var info))
|
||||
{
|
||||
ReplacedInvalidTerrainTiles[uv.ToCPos(this)] = Tiles[uv];
|
||||
Tiles[uv] = tileset.DefaultTerrainTile;
|
||||
info = tileset.GetTileInfo(tileset.DefaultTerrainTile);
|
||||
}
|
||||
|
||||
Ramp[uv] = info.RampType;
|
||||
}
|
||||
|
||||
AllEdgeCells = UpdateEdgeCells();
|
||||
@@ -443,8 +449,7 @@ namespace OpenRA
|
||||
|
||||
void UpdateRamp(CPos cell)
|
||||
{
|
||||
var tile = Rules.TileSet.GetTileInfo(Tiles[cell]);
|
||||
Ramp[cell] = tile != null ? tile.RampType : (byte)0;
|
||||
Ramp[cell] = Rules.TileSet.GetTileInfo(Tiles[cell]).RampType;
|
||||
}
|
||||
|
||||
void InitializeCellProjection()
|
||||
@@ -670,32 +675,26 @@ namespace OpenRA
|
||||
Color left, right;
|
||||
var tileset = Rules.TileSet;
|
||||
var type = tileset.GetTileInfo(Tiles[uv]);
|
||||
if (type != null)
|
||||
if (type.MinColor != type.MaxColor)
|
||||
{
|
||||
if (type.MinColor != type.MaxColor)
|
||||
{
|
||||
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
}
|
||||
else
|
||||
left = right = type.MinColor;
|
||||
|
||||
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
|
||||
{
|
||||
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
|
||||
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
|
||||
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
|
||||
}
|
||||
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
|
||||
}
|
||||
else
|
||||
left = right = Color.Black;
|
||||
left = right = type.MinColor;
|
||||
|
||||
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
|
||||
{
|
||||
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
|
||||
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
|
||||
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
|
||||
}
|
||||
|
||||
return (left, right);
|
||||
}
|
||||
|
||||
public byte[] SavePreview()
|
||||
{
|
||||
var tileset = Rules.TileSet;
|
||||
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
|
||||
var actors = ActorDefinitions.Where(a => actorTypes.Where(ai => ai.Name == a.Value.Value).Any());
|
||||
var positions = new List<(MPos Position, Color Color)>();
|
||||
@@ -715,76 +714,73 @@ namespace OpenRA
|
||||
foreach (var worldimpsi in worldimpsis)
|
||||
worldimpsi.PopulateMapPreviewSignatureCells(this, worldActorInfo, null, positions);
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric;
|
||||
|
||||
// Fudge the heightmap offset by adding as much extra as we need / can.
|
||||
// This tries to correct for our incorrect assumption that MPos == PPos
|
||||
var heightOffset = Math.Min(Grid.MaximumTerrainHeight, MapSize.Y - Bounds.Bottom);
|
||||
var width = Bounds.Width;
|
||||
var height = Bounds.Height + heightOffset;
|
||||
|
||||
var bitmapWidth = width;
|
||||
if (isRectangularIsometric)
|
||||
bitmapWidth = 2 * bitmapWidth - 1;
|
||||
|
||||
var stride = bitmapWidth * 4;
|
||||
var pxStride = 4;
|
||||
var minimapData = new byte[stride * height];
|
||||
(Color Left, Color Right) terrainColor = default((Color, Color));
|
||||
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric;
|
||||
|
||||
// Fudge the heightmap offset by adding as much extra as we need / can.
|
||||
// This tries to correct for our incorrect assumption that MPos == PPos
|
||||
var heightOffset = Math.Min(Grid.MaximumTerrainHeight, MapSize.Y - Bounds.Bottom);
|
||||
var width = Bounds.Width;
|
||||
var height = Bounds.Height + heightOffset;
|
||||
|
||||
var bitmapWidth = width;
|
||||
if (isRectangularIsometric)
|
||||
bitmapWidth = 2 * bitmapWidth - 1;
|
||||
|
||||
var stride = bitmapWidth * 4;
|
||||
var pxStride = 4;
|
||||
var minimapData = new byte[stride * height];
|
||||
(Color Left, Color Right) terrainColor = default((Color, Color));
|
||||
|
||||
for (var y = 0; y < height; y++)
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
var uv = new MPos(x + Bounds.Left, y + Bounds.Top);
|
||||
|
||||
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
|
||||
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
|
||||
if (actorColor.A == 0)
|
||||
terrainColor = GetTerrainColorPair(uv);
|
||||
|
||||
if (isRectangularIsometric)
|
||||
{
|
||||
var uv = new MPos(x + Bounds.Left, y + Bounds.Top);
|
||||
|
||||
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
|
||||
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
|
||||
if (actorColor.A == 0)
|
||||
terrainColor = GetTerrainColorPair(uv);
|
||||
|
||||
if (isRectangularIsometric)
|
||||
// Odd rows are shifted right by 1px
|
||||
var dx = uv.V & 1;
|
||||
var xOffset = pxStride * (2 * x + dx);
|
||||
if (x + dx > 0)
|
||||
{
|
||||
// Odd rows are shifted right by 1px
|
||||
var dx = uv.V & 1;
|
||||
var xOffset = pxStride * (2 * x + dx);
|
||||
if (x + dx > 0)
|
||||
{
|
||||
var z = y * stride + xOffset - pxStride;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
|
||||
if (xOffset < stride)
|
||||
{
|
||||
var z = y * stride + xOffset;
|
||||
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var z = y * stride + pxStride * x;
|
||||
var z = y * stride + xOffset - pxStride;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
|
||||
if (xOffset < stride)
|
||||
{
|
||||
var z = y * stride + xOffset;
|
||||
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var z = y * stride + pxStride * x;
|
||||
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
|
||||
minimapData[z++] = c.R;
|
||||
minimapData[z++] = c.G;
|
||||
minimapData[z++] = c.B;
|
||||
minimapData[z] = c.A;
|
||||
}
|
||||
}
|
||||
|
||||
var png = new Png(minimapData, bitmapWidth, height);
|
||||
return png.Save();
|
||||
}
|
||||
|
||||
var png = new Png(minimapData, SpriteFrameType.Bgra32, bitmapWidth, height);
|
||||
return png.Save();
|
||||
}
|
||||
|
||||
public bool Contains(CPos cell)
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
||||
|
||||
public override string ToString() { return Type + "," + Index; }
|
||||
}
|
||||
|
||||
public struct ResourceTile
|
||||
|
||||
@@ -221,25 +221,30 @@ namespace OpenRA
|
||||
|
||||
public byte GetTerrainIndex(TerrainTile r)
|
||||
{
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl))
|
||||
return defaultWalkableTerrainIndex;
|
||||
|
||||
if (tpl.Contains(r.Index))
|
||||
{
|
||||
var tile = tpl[r.Index];
|
||||
if (tile != null && tile.TerrainType != byte.MaxValue)
|
||||
return tile.TerrainType;
|
||||
}
|
||||
var tile = Templates[r.Type][r.Index];
|
||||
if (tile.TerrainType != byte.MaxValue)
|
||||
return tile.TerrainType;
|
||||
|
||||
return defaultWalkableTerrainIndex;
|
||||
}
|
||||
|
||||
public TerrainTileInfo GetTileInfo(TerrainTile r)
|
||||
{
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl))
|
||||
return null;
|
||||
|
||||
return tpl.Contains(r.Index) ? tpl[r.Index] : null;
|
||||
return Templates[r.Type][r.Index];
|
||||
}
|
||||
|
||||
public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info)
|
||||
{
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl) || !tpl.Contains(r.Index))
|
||||
{
|
||||
info = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
info = tpl[r.Index];
|
||||
return info != null;
|
||||
}
|
||||
|
||||
public TerrainTile DefaultTerrainTile { get { return new TerrainTile(Templates.First().Key, 0); } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,42 +138,6 @@ namespace OpenRA
|
||||
|
||||
public IEnumerable<string> Languages { get; private set; }
|
||||
|
||||
void LoadTranslations(Map map)
|
||||
{
|
||||
var selectedTranslations = new Dictionary<string, string>();
|
||||
var defaultTranslations = new Dictionary<string, string>();
|
||||
|
||||
if (!Manifest.Translations.Any())
|
||||
{
|
||||
Languages = new string[0];
|
||||
return;
|
||||
}
|
||||
|
||||
var yaml = MiniYaml.Load(map, Manifest.Translations, map.TranslationDefinitions);
|
||||
Languages = yaml.Select(t => t.Key).ToArray();
|
||||
|
||||
foreach (var y in yaml)
|
||||
{
|
||||
if (y.Key == Game.Settings.Graphics.Language)
|
||||
selectedTranslations = y.Value.ToDictionary(my => my.Value ?? "");
|
||||
else if (y.Key == Game.Settings.Graphics.DefaultLanguage)
|
||||
defaultTranslations = y.Value.ToDictionary(my => my.Value ?? "");
|
||||
}
|
||||
|
||||
var translations = new Dictionary<string, string>();
|
||||
foreach (var tkv in defaultTranslations.Concat(selectedTranslations))
|
||||
{
|
||||
if (translations.ContainsKey(tkv.Key))
|
||||
continue;
|
||||
if (selectedTranslations.ContainsKey(tkv.Key))
|
||||
translations.Add(tkv.Key, selectedTranslations[tkv.Key]);
|
||||
else
|
||||
translations.Add(tkv.Key, tkv.Value);
|
||||
}
|
||||
|
||||
FieldLoader.SetTranslations(translations);
|
||||
}
|
||||
|
||||
public Map PrepareMap(string uid)
|
||||
{
|
||||
LoadScreen?.Display();
|
||||
@@ -185,8 +149,6 @@ namespace OpenRA
|
||||
using (new Support.PerfTimer("Map"))
|
||||
map = new Map(this, MapCache[uid].Package);
|
||||
|
||||
LoadTranslations(map);
|
||||
|
||||
// Reinitialize all our assets
|
||||
InitializeLoaders(map);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -42,25 +43,48 @@ namespace OpenRA
|
||||
if (resolvedPath == null)
|
||||
throw new FileNotFoundException("Assembly `{0}` not found.".F(path));
|
||||
|
||||
// .NET doesn't provide any way of querying the metadata of an assembly without either:
|
||||
// (a) loading duplicate data into the application domain, breaking the world.
|
||||
// (b) crashing if the assembly has already been loaded.
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
|
||||
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
||||
{
|
||||
assembly = Assembly.LoadFile(resolvedPath);
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
}
|
||||
|
||||
assemblyList.Add(assembly);
|
||||
LoadAssembly(assemblyList, resolvedPath);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => (asm, ns))).ToArray();
|
||||
}
|
||||
|
||||
void LoadAssembly(List<Assembly> assemblyList, string resolvedPath)
|
||||
{
|
||||
// .NET doesn't provide any way of querying the metadata of an assembly without either:
|
||||
// (a) loading duplicate data into the application domain, breaking the world.
|
||||
// (b) crashing if the assembly has already been loaded.
|
||||
// We can't check the internal name of the assembly, so we'll work off the data instead
|
||||
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
|
||||
|
||||
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
|
||||
{
|
||||
#if MONO
|
||||
assembly = Assembly.LoadFile(resolvedPath);
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
|
||||
// Allow mods to use libraries.
|
||||
var assemblyPath = Path.GetDirectoryName(resolvedPath);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
|
||||
{
|
||||
var depedencyPath = Path.Combine(assemblyPath, referencedAssembly.Name + ".dll");
|
||||
if (File.Exists(depedencyPath))
|
||||
LoadAssembly(assemblyList, depedencyPath);
|
||||
}
|
||||
}
|
||||
#else
|
||||
var loader = new AssemblyLoader(resolvedPath);
|
||||
assembly = loader.LoadDefaultAssembly();
|
||||
ResolvedAssemblies.Add(hash, assembly);
|
||||
#endif
|
||||
}
|
||||
|
||||
assemblyList.Add(assembly);
|
||||
}
|
||||
|
||||
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
|
||||
{
|
||||
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
@@ -34,11 +34,16 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.17" />
|
||||
<PackageReference Include="OpenRA-Open.NAT" Version="1.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Mono)' == ''">
|
||||
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="5.0.0-preview.3-runtime.20214.6" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
|
||||
<ItemGroup Condition="'$(Configuration)'=='Release'">
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace OpenRA
|
||||
public readonly Shroud Shroud;
|
||||
public readonly FrozenActorLayer FrozenActorLayer;
|
||||
|
||||
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
|
||||
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
|
||||
public readonly FactionInfo DisplayFaction;
|
||||
|
||||
/// <summary>The spawn point index that was assigned for client-based players.</summary>
|
||||
@@ -80,7 +80,8 @@ namespace OpenRA
|
||||
{
|
||||
get
|
||||
{
|
||||
return spectating || WinState != WinState.Undefined;
|
||||
// Players in mission maps must not leave the player view
|
||||
return !inMissionMap && (spectating || WinState != WinState.Undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace OpenRA.Primitives
|
||||
static class LongBitSetAllocator<T> where T : class
|
||||
{
|
||||
static readonly Cache<string, long> Bits = new Cache<string, long>(Allocate);
|
||||
static long allBits = 1;
|
||||
static long nextBits = 1;
|
||||
|
||||
static long Allocate(string value)
|
||||
@@ -25,6 +26,7 @@ namespace OpenRA.Primitives
|
||||
lock (Bits)
|
||||
{
|
||||
var bits = nextBits;
|
||||
allBits |= bits;
|
||||
nextBits <<= 1;
|
||||
|
||||
if (nextBits == 0)
|
||||
@@ -85,6 +87,8 @@ namespace OpenRA.Primitives
|
||||
nextBits = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static long Mask { get { return allBits; } }
|
||||
}
|
||||
|
||||
// Opitmized BitSet to be used only when guaranteed to be no more than 64 values.
|
||||
@@ -114,6 +118,7 @@ namespace OpenRA.Primitives
|
||||
|
||||
public static bool operator ==(LongBitSet<T> me, LongBitSet<T> other) { return me.bits == other.bits; }
|
||||
public static bool operator !=(LongBitSet<T> me, LongBitSet<T> other) { return !(me == other); }
|
||||
public static LongBitSet<T> operator ~(LongBitSet<T> me) { return new LongBitSet<T>(me.bits ^ LongBitSetAllocator<T>.Mask); }
|
||||
|
||||
public bool Equals(LongBitSet<T> other) { return other == this; }
|
||||
public override bool Equals(object obj) { return obj is LongBitSet<T> && Equals((LongBitSet<T>)obj); }
|
||||
|
||||
@@ -431,24 +431,15 @@ namespace OpenRA
|
||||
var srcWidth = screenSprite.Sheet.Size.Width;
|
||||
var destWidth = screenSprite.Bounds.Width;
|
||||
var destHeight = -screenSprite.Bounds.Height;
|
||||
var channelOrder = new[] { 2, 1, 0, 3 };
|
||||
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
// Convert BGRA to RGBA
|
||||
// Extract the screen rect from the (larger) backing surface
|
||||
var dest = new byte[4 * destWidth * destHeight];
|
||||
for (var y = 0; y < destHeight; y++)
|
||||
{
|
||||
for (var x = 0; x < destWidth; x++)
|
||||
{
|
||||
var destOffset = 4 * (y * destWidth + x);
|
||||
var srcOffset = 4 * (y * srcWidth + x);
|
||||
for (var i = 0; i < 4; i++)
|
||||
dest[destOffset + i] = src[srcOffset + channelOrder[i]];
|
||||
}
|
||||
}
|
||||
Array.Copy(src, 4 * y * srcWidth, dest, 4 * y * destWidth, 4 * destWidth);
|
||||
|
||||
new Png(dest, destWidth, destHeight).Save(path);
|
||||
new Png(dest, SpriteFrameType.Bgra32, destWidth, destHeight).Save(path);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -660,16 +660,28 @@ namespace OpenRA.Server
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchOrdersToClient(Connection c, int client, int frame, byte[] data)
|
||||
byte[] CreateFrame(int client, int frame, byte[] data)
|
||||
{
|
||||
try
|
||||
using (var ms = new MemoryStream(data.Length + 12))
|
||||
{
|
||||
var ms = new MemoryStream(data.Length + 12);
|
||||
ms.WriteArray(BitConverter.GetBytes(data.Length + 4));
|
||||
ms.WriteArray(BitConverter.GetBytes(client));
|
||||
ms.WriteArray(BitConverter.GetBytes(frame));
|
||||
ms.WriteArray(data);
|
||||
SendData(c.Socket, ms.ToArray());
|
||||
return ms.GetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchOrdersToClient(Connection c, int client, int frame, byte[] data)
|
||||
{
|
||||
DispatchFrameToClient(c, client, CreateFrame(client, frame, data));
|
||||
}
|
||||
|
||||
void DispatchFrameToClient(Connection c, int client, byte[] frameData)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendData(c.Socket, frameData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -777,8 +789,9 @@ namespace OpenRA.Server
|
||||
public void DispatchOrdersToClients(Connection conn, int frame, byte[] data)
|
||||
{
|
||||
var from = conn != null ? conn.PlayerIndex : 0;
|
||||
var frameData = CreateFrame(from, frame, data);
|
||||
foreach (var c in Conns.Except(conn).ToList())
|
||||
DispatchOrdersToClient(c, from, frame, data);
|
||||
DispatchFrameToClient(c, from, frameData);
|
||||
|
||||
if (recorder != null)
|
||||
{
|
||||
|
||||
@@ -190,9 +190,6 @@ namespace OpenRA
|
||||
|
||||
public int BatchSize = 8192;
|
||||
public int SheetSize = 2048;
|
||||
|
||||
public string Language = "english";
|
||||
public string DefaultLanguage = "english";
|
||||
}
|
||||
|
||||
public class SoundSettings
|
||||
|
||||
363
OpenRA.Game/Support/AssemblyLoader.cs
Normal file
363
OpenRA.Game/Support/AssemblyLoader.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
// Not used/usable on Mono. Only used for Dotnet Core.
|
||||
// Based on https://github.com/natemcmaster/DotNetCorePlugins and used under the terms of the Apache 2.0 license
|
||||
#if !MONO
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace OpenRA.Support
|
||||
{
|
||||
public class AssemblyLoader
|
||||
{
|
||||
readonly string mainAssembly;
|
||||
readonly AssemblyLoadContext context;
|
||||
|
||||
public Assembly LoadDefaultAssembly() => context.LoadFromAssemblyPath(mainAssembly);
|
||||
|
||||
public AssemblyLoader(string assemblyFile)
|
||||
{
|
||||
mainAssembly = assemblyFile;
|
||||
var baseDir = Path.GetDirectoryName(assemblyFile);
|
||||
|
||||
context = CreateLoadContext(baseDir, assemblyFile);
|
||||
}
|
||||
|
||||
static AssemblyLoadContext CreateLoadContext(string baseDir, string assemblyFile)
|
||||
{
|
||||
var depsJsonFile = Path.Combine(baseDir, Path.GetFileNameWithoutExtension(assemblyFile) + ".deps.json");
|
||||
|
||||
var builder = new AssemblyLoadContextBuilder();
|
||||
|
||||
builder.TryAddDependencyContext(depsJsonFile, out _);
|
||||
builder.SetBaseDirectory(baseDir);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
public class AssemblyLoadContextBuilder
|
||||
{
|
||||
readonly Dictionary<string, ManagedLibrary> managedLibraries = new Dictionary<string, ManagedLibrary>(StringComparer.Ordinal);
|
||||
readonly Dictionary<string, NativeLibrary> nativeLibraries = new Dictionary<string, NativeLibrary>(StringComparer.Ordinal);
|
||||
string basePath;
|
||||
|
||||
public AssemblyLoadContext Build()
|
||||
{
|
||||
return new ManagedLoadContext(basePath, managedLibraries, nativeLibraries);
|
||||
}
|
||||
|
||||
public AssemblyLoadContextBuilder SetBaseDirectory(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentException("Argument must not be null or empty.", nameof(path));
|
||||
|
||||
if (!Path.IsPathRooted(path))
|
||||
throw new ArgumentException("Argument must be a full path.", nameof(path));
|
||||
|
||||
basePath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library)
|
||||
{
|
||||
managedLibraries.Add(library.Name.Name, library);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library)
|
||||
{
|
||||
ValidateRelativePath(library.AppLocalPath);
|
||||
nativeLibraries.Add(library.Name, library);
|
||||
return this;
|
||||
}
|
||||
|
||||
static void ValidateRelativePath(string probingPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(probingPath))
|
||||
throw new ArgumentException("Value must not be null or empty.", nameof(probingPath));
|
||||
|
||||
if (Path.IsPathRooted(probingPath))
|
||||
throw new ArgumentException("Argument must be a relative path.", nameof(probingPath));
|
||||
}
|
||||
}
|
||||
|
||||
class ManagedLoadContext : AssemblyLoadContext
|
||||
{
|
||||
readonly string basePath;
|
||||
readonly Dictionary<string, ManagedLibrary> managedAssemblies;
|
||||
readonly Dictionary<string, NativeLibrary> nativeLibraries;
|
||||
|
||||
static readonly string[] NativeLibraryExtensions;
|
||||
static readonly string[] NativeLibraryPrefixes;
|
||||
|
||||
static readonly string[] ManagedAssemblyExtensions =
|
||||
{
|
||||
".dll",
|
||||
".ni.dll",
|
||||
".exe",
|
||||
".ni.exe"
|
||||
};
|
||||
|
||||
static ManagedLoadContext()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
NativeLibraryPrefixes = new[] { "" };
|
||||
NativeLibraryExtensions = new[] { ".dll" };
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
NativeLibraryPrefixes = new[] { "", "lib", };
|
||||
NativeLibraryExtensions = new[] { ".dylib" };
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
NativeLibraryPrefixes = new[] { "", "lib" };
|
||||
NativeLibraryExtensions = new[] { ".so", ".so.1" };
|
||||
}
|
||||
else
|
||||
{
|
||||
NativeLibraryPrefixes = Array.Empty<string>();
|
||||
NativeLibraryExtensions = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public ManagedLoadContext(string baseDirectory, Dictionary<string, ManagedLibrary> managedAssemblies, Dictionary<string, NativeLibrary> nativeLibraries)
|
||||
{
|
||||
basePath = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory));
|
||||
this.managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies));
|
||||
this.nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries));
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
// If default context is preferred, check first for types in the default context unless the dependency has been declared as private
|
||||
try
|
||||
{
|
||||
var defaultAssembly = Default.LoadFromAssemblyName(assemblyName);
|
||||
if (defaultAssembly != null)
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow errors in loading from the default context
|
||||
}
|
||||
|
||||
if (managedAssemblies.TryGetValue(assemblyName.Name, out var library) && SearchForLibrary(library, out var path))
|
||||
return LoadFromAssemblyPath(path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
foreach (var prefix in NativeLibraryPrefixes)
|
||||
if (nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library) && SearchForLibrary(library, prefix, out var path))
|
||||
return LoadUnmanagedDllFromPath(path);
|
||||
|
||||
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||
}
|
||||
|
||||
bool SearchForLibrary(ManagedLibrary library, out string path)
|
||||
{
|
||||
// 1. Search in base path
|
||||
foreach (var ext in ManagedAssemblyExtensions)
|
||||
{
|
||||
var local = Path.Combine(basePath, library.Name.Name + ext);
|
||||
if (File.Exists(local))
|
||||
{
|
||||
path = local;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SearchForLibrary(NativeLibrary library, string prefix, out string path)
|
||||
{
|
||||
// 1. Search in base path
|
||||
foreach (var ext in NativeLibraryExtensions)
|
||||
{
|
||||
var candidate = Path.Combine(basePath, $"{prefix}{library.Name}{ext}");
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
path = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in base path + app local (for portable deployments of netcoreapp)
|
||||
var local = Path.Combine(basePath, library.AppLocalPath);
|
||||
if (File.Exists(local))
|
||||
{
|
||||
path = local;
|
||||
return true;
|
||||
}
|
||||
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class ManagedLibrary
|
||||
{
|
||||
public AssemblyName Name { get; private set; }
|
||||
|
||||
public static ManagedLibrary CreateFromPackage(string assetPath)
|
||||
{
|
||||
return new ManagedLibrary
|
||||
{
|
||||
Name = new AssemblyName(Path.GetFileNameWithoutExtension(assetPath))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class NativeLibrary
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string AppLocalPath { get; private set; }
|
||||
|
||||
public static NativeLibrary CreateFromPackage(string assetPath)
|
||||
{
|
||||
return new NativeLibrary
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(assetPath),
|
||||
AppLocalPath = assetPath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class DependencyContextExtensions
|
||||
{
|
||||
public static AssemblyLoadContextBuilder TryAddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath, out Exception error)
|
||||
{
|
||||
error = null;
|
||||
try
|
||||
{
|
||||
builder.AddDependencyContext(depsFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath)
|
||||
{
|
||||
var reader = new DependencyContextJsonReader();
|
||||
using (var file = File.OpenRead(depsFilePath))
|
||||
{
|
||||
var deps = reader.Read(file);
|
||||
builder.SetBaseDirectory(Path.GetDirectoryName(depsFilePath));
|
||||
builder.AddDependencyContext(deps);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
static string GetFallbackRid()
|
||||
{
|
||||
string ridBase;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
ridBase = "win10";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
ridBase = "linux";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
ridBase = "osx.10.12";
|
||||
else
|
||||
return "any";
|
||||
|
||||
switch (RuntimeInformation.OSArchitecture)
|
||||
{
|
||||
case Architecture.X86:
|
||||
return ridBase + "-x86";
|
||||
case Architecture.X64:
|
||||
return ridBase + "-x64";
|
||||
case Architecture.Arm:
|
||||
return ridBase + "-arm";
|
||||
case Architecture.Arm64:
|
||||
return ridBase + "-arm64";
|
||||
}
|
||||
|
||||
return ridBase;
|
||||
}
|
||||
|
||||
public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, DependencyContext dependencyContext)
|
||||
{
|
||||
var ridGraph = dependencyContext.RuntimeGraph.Any()
|
||||
? dependencyContext.RuntimeGraph
|
||||
: DependencyContext.Default.RuntimeGraph;
|
||||
|
||||
var rid = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier();
|
||||
var fallbackRid = GetFallbackRid();
|
||||
var fallbackGraph = ridGraph.FirstOrDefault(g => g.Runtime == rid)
|
||||
?? ridGraph.FirstOrDefault(g => g.Runtime == fallbackRid)
|
||||
?? new RuntimeFallbacks("any");
|
||||
|
||||
foreach (var managed in dependencyContext.ResolveRuntimeAssemblies(fallbackGraph))
|
||||
builder.AddManagedLibrary(managed);
|
||||
|
||||
foreach (var native in dependencyContext.ResolveNativeAssets(fallbackGraph))
|
||||
builder.AddNativeLibrary(native);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
static IEnumerable<ManagedLibrary> ResolveRuntimeAssemblies(this DependencyContext depContext, RuntimeFallbacks runtimeGraph)
|
||||
{
|
||||
var rids = GetRids(runtimeGraph);
|
||||
return from library in depContext.RuntimeLibraries
|
||||
from assetPath in SelectAssets(rids, library.RuntimeAssemblyGroups)
|
||||
select ManagedLibrary.CreateFromPackage(assetPath);
|
||||
}
|
||||
|
||||
static IEnumerable<NativeLibrary> ResolveNativeAssets(this DependencyContext depContext, RuntimeFallbacks runtimeGraph)
|
||||
{
|
||||
var rids = GetRids(runtimeGraph);
|
||||
return from library in depContext.RuntimeLibraries
|
||||
from assetPath in SelectAssets(rids, library.NativeLibraryGroups)
|
||||
where !assetPath.EndsWith(".a", StringComparison.Ordinal)
|
||||
select NativeLibrary.CreateFromPackage(assetPath);
|
||||
}
|
||||
|
||||
static IEnumerable<string> GetRids(RuntimeFallbacks runtimeGraph)
|
||||
{
|
||||
return Enumerable.Concat(new[] { runtimeGraph.Runtime }, runtimeGraph?.Fallbacks ?? Enumerable.Empty<string>());
|
||||
}
|
||||
|
||||
static IEnumerable<string> SelectAssets(IEnumerable<string> rids, IEnumerable<RuntimeAssetGroup> groups)
|
||||
{
|
||||
foreach (var rid in rids)
|
||||
{
|
||||
var group = groups.FirstOrDefault(g => g.Runtime == rid);
|
||||
if (group != null)
|
||||
return group.AssetPaths;
|
||||
}
|
||||
|
||||
return groups.GetDefaultAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -17,11 +17,9 @@ namespace OpenRA.Traits
|
||||
[Desc("Required for shroud and fog visibility checks. Add this to the player actor.")]
|
||||
public class ShroudInfo : TraitInfo, ILobbyOptions
|
||||
{
|
||||
[Translate]
|
||||
[Desc("Descriptive label for the fog checkbox in the lobby.")]
|
||||
public readonly string FogCheckboxLabel = "Fog of War";
|
||||
|
||||
[Translate]
|
||||
[Desc("Tooltip description for the fog checkbox in the lobby.")]
|
||||
public readonly string FogCheckboxDescription = "Line of sight is required to view enemy forces";
|
||||
|
||||
@@ -37,11 +35,9 @@ namespace OpenRA.Traits
|
||||
[Desc("Display order for the fog checkbox in the lobby.")]
|
||||
public readonly int FogCheckboxDisplayOrder = 0;
|
||||
|
||||
[Translate]
|
||||
[Desc("Descriptive label for the explored map checkbox in the lobby.")]
|
||||
public readonly string ExploredMapCheckboxLabel = "Explored Map";
|
||||
|
||||
[Translate]
|
||||
[Desc("Tooltip description for the explored map checkbox in the lobby.")]
|
||||
public readonly string ExploredMapCheckboxDescription = "Initial map shroud is revealed";
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace OpenRA.Traits
|
||||
[Desc("The side that the faction belongs to. For example, England belongs to the 'Allies' side.")]
|
||||
public readonly string Side = null;
|
||||
|
||||
[Translate]
|
||||
public readonly string Description = null;
|
||||
|
||||
public readonly bool Selectable = true;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework Condition="'$(Mono)' == ''">net5.0</TargetFramework>
|
||||
<TargetFramework Condition="'$(Mono)' != ''">netstandard2.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
@@ -17,6 +18,8 @@
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Configurations>Release;Debug</Configurations>
|
||||
<AssemblyName>OpenRA</AssemblyName>
|
||||
<IsPublishable Condition="'$(CopyGenericLauncher)' == 'False'">false</IsPublishable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
|
||||
@@ -62,4 +65,8 @@
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="netstandard" />
|
||||
<TrimmerRootAssembly Include="System.IO.Pipes" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -20,15 +20,11 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
bool IsAud(Stream s)
|
||||
{
|
||||
var start = s.Position;
|
||||
s.Position += 10;
|
||||
var readFlag = s.ReadByte();
|
||||
s.Position += 11;
|
||||
var readFormat = s.ReadByte();
|
||||
s.Position = start;
|
||||
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
|
||||
return Enum.IsDefined(typeof(SoundFormat), readFormat);
|
||||
return readFormat == (int)SoundFormat.ImaAdpcm;
|
||||
}
|
||||
|
||||
bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound)
|
||||
@@ -53,8 +49,8 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
|
||||
public sealed class AudFormat : ISoundFormat
|
||||
{
|
||||
public int Channels { get { return 1; } }
|
||||
public int SampleBits { get { return 16; } }
|
||||
public int Channels { get { return channels; } }
|
||||
public int SampleBits { get { return sampleBits; } }
|
||||
public int SampleRate { get { return sampleRate; } }
|
||||
public float LengthInSeconds { get { return AudReader.SoundLength(sourceStream); } }
|
||||
public Stream GetPCMInputStream() { return audStreamFactory(); }
|
||||
@@ -62,13 +58,15 @@ namespace OpenRA.Mods.Cnc.AudioLoaders
|
||||
|
||||
readonly Stream sourceStream;
|
||||
readonly Func<Stream> audStreamFactory;
|
||||
readonly int channels;
|
||||
readonly int sampleBits;
|
||||
readonly int sampleRate;
|
||||
|
||||
public AudFormat(Stream stream)
|
||||
{
|
||||
sourceStream = stream;
|
||||
|
||||
if (!AudReader.LoadSound(stream, out audStreamFactory, out sampleRate))
|
||||
if (!AudReader.LoadSound(stream, out audStreamFactory, out sampleRate, out sampleBits, out channels))
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,6 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
|
||||
public static class AudReader
|
||||
{
|
||||
public static byte[] LoadSound(byte[] raw, ref int index)
|
||||
{
|
||||
return ImaAdpcmReader.LoadImaAdpcmSound(raw, ref index);
|
||||
}
|
||||
|
||||
public static float SoundLength(Stream s)
|
||||
{
|
||||
var sampleRate = s.ReadUInt16();
|
||||
@@ -54,7 +49,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
return (float)samples / sampleRate;
|
||||
}
|
||||
|
||||
public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate)
|
||||
public static bool LoadSound(Stream s, out Func<Stream> result, out int sampleRate, out int sampleBits, out int channels)
|
||||
{
|
||||
result = null;
|
||||
var startPosition = s.Position;
|
||||
@@ -63,15 +58,17 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
sampleRate = s.ReadUInt16();
|
||||
var dataSize = s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
|
||||
var readFlag = s.ReadByte();
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
sampleBits = (readFlag & (int)SoundFlags._16Bit) == 0 ? 8 : 16;
|
||||
channels = (readFlag & (int)SoundFlags.Stereo) == 0 ? 1 : 2;
|
||||
|
||||
var readFormat = s.ReadByte();
|
||||
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
||||
return false;
|
||||
|
||||
if (readFormat == (int)SoundFormat.WestwoodCompressed)
|
||||
throw new NotImplementedException();
|
||||
|
||||
var offsetPosition = s.Position;
|
||||
|
||||
result = () =>
|
||||
|
||||
138
OpenRA.Mods.Cnc/FileSystem/MegFile.cs
Normal file
138
OpenRA.Mods.Cnc/FileSystem/MegFile.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.FileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// This class supports loading unencrypted V3 .meg files using
|
||||
/// reference documentation from here https://modtools.petrolution.net/docs/MegFileFormat
|
||||
/// </summary>
|
||||
public class MegV3Loader : IPackageLoader
|
||||
{
|
||||
const uint UnencryptedMegID = 0xFFFFFFFF;
|
||||
|
||||
// Float value 0.99, but it is simpler to read and compare as an integer
|
||||
const uint MegVersion = 0x3F7D70A4;
|
||||
|
||||
public bool TryParsePackage(Stream s, string filename, OpenRA.FileSystem.FileSystem context, out IReadOnlyPackage package)
|
||||
{
|
||||
var position = s.Position;
|
||||
|
||||
var id = s.ReadUInt32();
|
||||
var version = s.ReadUInt32();
|
||||
|
||||
s.Position = position;
|
||||
|
||||
if (id != UnencryptedMegID || version != MegVersion)
|
||||
{
|
||||
package = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
package = new MegFile(s, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
public sealed class MegFile : IReadOnlyPackage
|
||||
{
|
||||
readonly Stream s;
|
||||
|
||||
readonly Dictionary<string, (uint Offset, int Length)> contents = new Dictionary<string, (uint Offset, int Length)>();
|
||||
|
||||
public MegFile(Stream s, string filename)
|
||||
{
|
||||
Name = filename;
|
||||
this.s = s;
|
||||
|
||||
var id = s.ReadUInt32();
|
||||
var version = s.ReadUInt32();
|
||||
|
||||
if (id != UnencryptedMegID || version != MegVersion)
|
||||
throw new Exception("Invalid file signature for meg file");
|
||||
|
||||
var headerSize = s.ReadUInt32();
|
||||
var numStrings = s.ReadUInt32();
|
||||
var numFiles = s.ReadUInt32();
|
||||
var stringsSize = s.ReadUInt32();
|
||||
var stringsStart = s.Position;
|
||||
|
||||
var filenames = new List<string>();
|
||||
|
||||
// The file names are an indexed array of strings
|
||||
for (var i = 0; i < numStrings; i++)
|
||||
{
|
||||
var length = s.ReadUInt16();
|
||||
filenames.Add(s.ReadASCII(length));
|
||||
}
|
||||
|
||||
// The header indicates where we should be, so verify it
|
||||
if (s.Position != stringsSize + stringsStart)
|
||||
throw new Exception("File name table in .meg file inconsistent");
|
||||
|
||||
// Now we load each file entry and associated info
|
||||
for (var i = 0; i < numFiles; i++)
|
||||
{
|
||||
// Ignore flags, crc, index
|
||||
s.Position += 10;
|
||||
var size = s.ReadUInt32();
|
||||
var offset = s.ReadUInt32();
|
||||
var nameIndex = s.ReadUInt16();
|
||||
contents[filenames[nameIndex]] = (offset, (int)size);
|
||||
}
|
||||
|
||||
if (s.Position != headerSize)
|
||||
throw new Exception("Expected to be at data start offset");
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public IEnumerable<string> Contents => contents.Keys;
|
||||
|
||||
public bool Contains(string filename)
|
||||
{
|
||||
return contents.ContainsKey(filename);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s.Dispose();
|
||||
}
|
||||
|
||||
public Stream GetStream(string filename)
|
||||
{
|
||||
// Look up the index of the filename
|
||||
if (!contents.TryGetValue(filename, out var index))
|
||||
return null;
|
||||
|
||||
return SegmentStream.CreateWithoutOwningStream(s, index.Offset, index.Length);
|
||||
}
|
||||
|
||||
public IReadOnlyPackage OpenPackage(string filename, OpenRA.FileSystem.FileSystem context)
|
||||
{
|
||||
var childStream = GetStream(filename);
|
||||
if (childStream == null)
|
||||
return null;
|
||||
|
||||
if (context.TryParsePackage(childStream, filename, out var package))
|
||||
return package;
|
||||
|
||||
childStream.Dispose();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,10 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
uint IModel.Frames { get { return frames; } }
|
||||
uint IModel.Sections { get { return limbs; } }
|
||||
|
||||
public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva)
|
||||
public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva, (string Vxl, string Hva) files)
|
||||
{
|
||||
if (vxl.LimbCount != hva.LimbCount)
|
||||
throw new InvalidOperationException("Voxel and hva limb counts don't match");
|
||||
throw new InvalidOperationException("{0}.vxl and {1}.hva limb counts don't match.".F(files.Vxl, files.Hva));
|
||||
|
||||
transforms = hva.Transforms;
|
||||
frames = hva.FrameCount;
|
||||
|
||||
@@ -77,8 +77,8 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
var size = new Size(su, sv);
|
||||
var s = sheetBuilder.Allocate(size);
|
||||
var t = sheetBuilder.Allocate(size);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(s, colors);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(t, normals);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(s, colors, SpriteFrameType.Indexed8);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(t, normals, SpriteFrameType.Indexed8);
|
||||
|
||||
// s and t are guaranteed to use the same sheet because
|
||||
// of the custom voxel sheet allocation implementation
|
||||
@@ -216,9 +216,10 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
HvaReader hva;
|
||||
using (var s = fileSystem.Open(files.Vxl + ".vxl"))
|
||||
vxl = new VxlReader(s);
|
||||
|
||||
using (var s = fileSystem.Open(files.Hva + ".hva"))
|
||||
hva = new HvaReader(s, files.Hva + ".hva");
|
||||
return new Voxel(this, vxl, hva);
|
||||
return new Voxel(this, vxl, hva, files);
|
||||
}
|
||||
|
||||
public Voxel Load(string vxl, string hva)
|
||||
|
||||
@@ -86,6 +86,11 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
return loader.Load(vxl, hva);
|
||||
}
|
||||
|
||||
public IModel GetModel(string model)
|
||||
{
|
||||
return loader.Load(model, model);
|
||||
}
|
||||
|
||||
public IModel GetModelSequence(string model, string sequence)
|
||||
{
|
||||
try { return models[model][sequence]; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
@@ -11,6 +11,7 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<IsPublishable Condition="'$(CopyCncDll)' == 'False'">false</IsPublishable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
@@ -37,4 +38,4 @@
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
|
||||
class ShpD2Frame : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get { return Size; } }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
|
||||
class ImageHeader : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get { return reader.Size; } }
|
||||
public Size FrameSize { get { return reader.Size; } }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
{
|
||||
class TmpRAFrame : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
{
|
||||
class TmpTDFrame : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get { return float2.Zero; } }
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
{
|
||||
readonly TmpTSFrame parent;
|
||||
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get { return parent.Size; } }
|
||||
public Size FrameSize { get { return Size; } }
|
||||
public float2 Offset { get { return parent.Offset; } }
|
||||
@@ -36,7 +36,7 @@ namespace OpenRA.Mods.Cnc.SpriteLoaders
|
||||
|
||||
class TmpTSFrame : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get { return Size; } }
|
||||
public float2 Offset { get; private set; }
|
||||
|
||||
@@ -79,8 +79,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("The condition to grant to self while disguised.")]
|
||||
public readonly string DisguisedCondition = null;
|
||||
|
||||
[Desc("What diplomatic stances can this actor disguise as.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Player relationships the owner of the disguise target needs.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Target types of actors that this actor disguise as.")]
|
||||
public readonly BitSet<TargetableType> TargetTypes = new BitSet<TargetableType>("Disguise");
|
||||
@@ -299,7 +299,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.RelationshipWith(target.Owner);
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
if (!info.ValidRelationships.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.TargetTypes.Overlaps(target.GetAllTargetTypes());
|
||||
@@ -308,7 +308,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.RelationshipWith(target.Owner);
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
if (!info.ValidRelationships.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.TargetTypes.Overlaps(target.Info.GetAllTargetTypes());
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
protected override bool ShouldRender(Actor self)
|
||||
{
|
||||
return infiltrators.Any(i => Info.ValidStances.HasStance(i.RelationshipWith(self.World.RenderPlayer)));
|
||||
return infiltrators.Any(i => Info.ValidRelationships.HasStance(i.RelationshipWith(self.World.RenderPlayer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Color to use for the target line.")]
|
||||
public readonly Color TargetLineColor = Color.Crimson;
|
||||
|
||||
[Desc("What diplomatic stances can be infiltrated by this actor.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Player relationships the owner of the infiltration target needs.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Behaviour when entering the target.",
|
||||
"Possible values are Exit, Suicide, Dispose.")]
|
||||
@@ -101,10 +101,10 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
case TargetType.Actor:
|
||||
return Info.Types.Overlaps(target.Actor.GetEnabledTargetTypes()) &&
|
||||
Info.ValidStances.HasStance(self.Owner.RelationshipWith(target.Actor.Owner));
|
||||
Info.ValidRelationships.HasStance(self.Owner.RelationshipWith(target.Actor.Owner));
|
||||
case TargetType.FrozenActor:
|
||||
return target.FrozenActor.IsValid && Info.Types.Overlaps(target.FrozenActor.TargetTypes) &&
|
||||
Info.ValidStances.HasStance(self.Owner.RelationshipWith(target.FrozenActor.Owner));
|
||||
Info.ValidRelationships.HasStance(self.Owner.RelationshipWith(target.FrozenActor.Owner));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.RelationshipWith(target.Owner);
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
if (!info.ValidRelationships.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.Types.Overlaps(target.GetAllTargetTypes());
|
||||
@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = self.Owner.RelationshipWith(target.Owner);
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
if (!info.ValidRelationships.HasStance(stance))
|
||||
return false;
|
||||
|
||||
return info.Types.Overlaps(target.Info.GetAllTargetTypes());
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
return self.World.NoPlayersMask;
|
||||
|
||||
// Friendly units should move around!
|
||||
return info.BlockFriendly ? self.Owner.EnemyPlayersMask : self.World.AllPlayersMask;
|
||||
return info.BlockFriendly ? ~self.Owner.AlliedPlayersMask : self.World.AllPlayersMask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
[Desc("This actor has turret art with facings baked into the sprite.")]
|
||||
public class WithEmbeddedTurretSpriteBodyInfo : WithSpriteBodyInfo, Requires<TurretedInfo>, Requires<BodyOrientationInfo>
|
||||
{
|
||||
[Desc("Number of facings for gameplay calculations. -1 indicates auto-detection from the sequence.")]
|
||||
public readonly int QuantizedFacings = -1;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new WithEmbeddedTurretSpriteBody(init, this); }
|
||||
|
||||
public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
|
||||
@@ -43,6 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
|
||||
public class WithEmbeddedTurretSpriteBody : WithSpriteBody
|
||||
{
|
||||
readonly WithEmbeddedTurretSpriteBodyInfo info;
|
||||
readonly Turreted turreted;
|
||||
|
||||
static Func<WAngle> MakeTurretFacingFunc(Actor self)
|
||||
@@ -52,22 +56,23 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
return () => turreted.WorldOrientation.Yaw;
|
||||
}
|
||||
|
||||
public WithEmbeddedTurretSpriteBody(ActorInitializer init, WithSpriteBodyInfo info)
|
||||
public WithEmbeddedTurretSpriteBody(ActorInitializer init, WithEmbeddedTurretSpriteBodyInfo info)
|
||||
: base(init, info, MakeTurretFacingFunc(init.Self))
|
||||
{
|
||||
this.info = info;
|
||||
turreted = init.Self.TraitsImplementing<Turreted>().FirstOrDefault();
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
base.TraitEnabled(self);
|
||||
turreted.QuantizedFacings = DefaultAnimation.CurrentSequence.Facings;
|
||||
turreted.QuantizedFacings = info.QuantizedFacings >= 0 ? info.QuantizedFacings : DefaultAnimation.CurrentSequence.Facings;
|
||||
}
|
||||
|
||||
protected override void DamageStateChanged(Actor self)
|
||||
{
|
||||
base.DamageStateChanged(self);
|
||||
turreted.QuantizedFacings = DefaultAnimation.CurrentSequence.Facings;
|
||||
turreted.QuantizedFacings = info.QuantizedFacings >= 0 ? info.QuantizedFacings : DefaultAnimation.CurrentSequence.Facings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("The prerequisite type that this provides.")]
|
||||
public readonly string Prerequisite = null;
|
||||
|
||||
[Translate]
|
||||
[Desc("Label to display over the support power icon and in its tooltip while the power is active.")]
|
||||
public readonly string ActiveText = "ACTIVE";
|
||||
|
||||
[Translate]
|
||||
[Desc("Label to display over the support power icon and in its tooltip while the power is available but not active.")]
|
||||
public readonly string AvailableText = "READY";
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Cnc.SpriteLoaders;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -35,7 +36,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
var dest = inputFiles[0].Split('-').First() + ".shp";
|
||||
|
||||
var frames = inputFiles.Select(a => new Png(File.OpenRead(a))).ToList();
|
||||
if (frames.Any(f => f.Palette == null))
|
||||
if (frames.Any(f => f.Type != SpriteFrameType.Indexed8))
|
||||
throw new InvalidOperationException("All frames must be paletted");
|
||||
|
||||
var size = new Size(frames[0].Width, frames[0].Height);
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
foreach (var t in enterActor.TraitsImplementing<INotifyCapture>())
|
||||
t.OnCapture(enterActor, self, oldOwner, self.Owner, captures.Info.CaptureTypes);
|
||||
|
||||
if (self.Owner.RelationshipWith(oldOwner).HasStance(captures.Info.PlayerExperienceStances))
|
||||
if (self.Owner.RelationshipWith(oldOwner).HasStance(captures.Info.PlayerExperienceRelationships))
|
||||
self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>()?.GiveExperience(captures.Info.PlayerExperience);
|
||||
|
||||
if (captures.Info.ConsumedByCapture)
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
// Make sure we can still repair the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
var stance = self.Owner.RelationshipWith(enterActor.Owner);
|
||||
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || enterEngineerRepariable == null || enterEngineerRepariable.IsTraitDisabled || !info.ValidStances.HasStance(stance))
|
||||
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || enterEngineerRepariable == null || enterEngineerRepariable.IsTraitDisabled || !info.ValidRelationships.HasStance(stance))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return;
|
||||
|
||||
var stance = self.Owner.RelationshipWith(enterActor.Owner);
|
||||
if (!info.ValidStances.HasStance(stance))
|
||||
if (!info.ValidRelationships.HasStance(stance))
|
||||
return;
|
||||
|
||||
if (enterHealth.DamageState == DamageState.Undamaged)
|
||||
|
||||
@@ -89,9 +89,6 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
var tile = world.Map.Tiles[cell];
|
||||
var tileInfo = world.Map.Rules.TileSet.GetTileInfo(tile);
|
||||
if (tileInfo == null)
|
||||
return false;
|
||||
|
||||
var terrainType = world.Map.Rules.TileSet.TerrainInfo[tileInfo.TerrainType];
|
||||
|
||||
if (mapResources[cell].Type == ResourceType.ResourceType)
|
||||
|
||||
@@ -23,16 +23,14 @@ namespace OpenRA.Mods.Common.Effects
|
||||
readonly RallyPoint rp;
|
||||
readonly Animation flag;
|
||||
readonly Animation circles;
|
||||
readonly ExitInfo[] exits;
|
||||
|
||||
List<WPos> targetLineNodes = new List<WPos> { };
|
||||
List<CPos> cachedLocations;
|
||||
|
||||
public RallyPointIndicator(Actor building, RallyPoint rp, ExitInfo[] exits)
|
||||
public RallyPointIndicator(Actor building, RallyPoint rp)
|
||||
{
|
||||
this.building = building;
|
||||
this.rp = rp;
|
||||
this.exits = exits;
|
||||
|
||||
if (rp.Info.Image != null)
|
||||
{
|
||||
@@ -73,21 +71,8 @@ namespace OpenRA.Mods.Common.Effects
|
||||
if (targetLineNodes.Count == 0)
|
||||
return;
|
||||
|
||||
var exitPos = building.CenterPosition;
|
||||
|
||||
// Find closest exit
|
||||
var dist = int.MaxValue;
|
||||
foreach (var exit in exits)
|
||||
{
|
||||
var ep = building.CenterPosition + exit.SpawnOffset;
|
||||
var len = (targetLineNodes[0] - ep).Length;
|
||||
if (len < dist)
|
||||
{
|
||||
dist = len;
|
||||
exitPos = ep;
|
||||
}
|
||||
}
|
||||
|
||||
var exit = building.NearestExitOrDefault(targetLineNodes[0]);
|
||||
var exitPos = exit != null ? building.World.Map.CenterOfCell(building.Location + exit.Info.ExitCell) : building.CenterPosition;
|
||||
targetLineNodes.Insert(0, exitPos);
|
||||
}
|
||||
|
||||
|
||||
25
OpenRA.Mods.Common/Lint/CheckMapTiles.cs
Normal file
25
OpenRA.Mods.Common/Lint/CheckMapTiles.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Common.Lint
|
||||
{
|
||||
public class CheckMapTiles : ILintMapPass
|
||||
{
|
||||
public void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, Map map)
|
||||
{
|
||||
foreach (var kv in map.ReplacedInvalidTerrainTiles)
|
||||
emitError("Cell {0} references invalid terrain tile {1}.".F(kv.Key, kv.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.Lint
|
||||
if (!string.IsNullOrWhiteSpace(player.Faction) && !factions.Contains(player.Faction))
|
||||
emitError("Invalid faction {0} chosen for player {1}.".F(player.Faction, player.Name));
|
||||
|
||||
if (worldActor.HasTraitInfo<MPStartLocationsInfo>())
|
||||
if (worldActor.HasTraitInfo<MapStartingLocationsInfo>())
|
||||
{
|
||||
var playerCount = players.Count(p => p.Value.Playable);
|
||||
var spawns = new List<CPos>();
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace OpenRA.Mods.Common.LoadScreens
|
||||
public LaunchArguments Launch;
|
||||
protected ModData ModData { get; private set; }
|
||||
|
||||
bool initialized;
|
||||
|
||||
public virtual void Init(ModData modData, Dictionary<string, string> info)
|
||||
{
|
||||
ModData = modData;
|
||||
@@ -31,12 +33,15 @@ namespace OpenRA.Mods.Common.LoadScreens
|
||||
|
||||
public virtual void Display()
|
||||
{
|
||||
if (Game.Renderer == null)
|
||||
if (Game.Renderer == null || initialized)
|
||||
return;
|
||||
|
||||
// Draw a black screen
|
||||
Game.Renderer.BeginUI();
|
||||
Game.Renderer.EndFrame(new NullInputHandler());
|
||||
|
||||
// PERF: draw the screen only once
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public virtual void StartGame(Arguments args)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
@@ -11,6 +11,7 @@
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
@@ -24,6 +25,7 @@
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
|
||||
|
||||
@@ -96,8 +96,8 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
[Desc("Terrain where the projectile explodes instead of bouncing.")]
|
||||
public readonly HashSet<string> InvalidBounceTerrain = new HashSet<string>();
|
||||
|
||||
[Desc("If projectile touches an actor with one of these stances during or after the first bounce, trigger explosion.")]
|
||||
public readonly PlayerRelationship ValidBounceBlockerStances = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
[Desc("Trigger the explosion if the projectile touches an actor thats owner has these player relationships.")]
|
||||
public readonly PlayerRelationship ValidBounceBlockerRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
|
||||
[Desc("Altitude above terrain below which to explode. Zero effectively deactivates airburst.")]
|
||||
public readonly WDist AirburstAltitude = WDist.Zero;
|
||||
@@ -319,7 +319,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
if (checkTargetType && !Target.FromActor(victim).IsValidFor(firedBy))
|
||||
continue;
|
||||
|
||||
if (!info.ValidBounceBlockerStances.HasStance(firedBy.Owner.RelationshipWith(victim.Owner)))
|
||||
if (!info.ValidBounceBlockerRelationships.HasStance(firedBy.Owner.RelationshipWith(victim.Owner)))
|
||||
continue;
|
||||
|
||||
// If the impact position is within any actor's HitShape, we have a direct hit
|
||||
|
||||
@@ -13,6 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BeaconLib;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Server;
|
||||
@@ -107,7 +108,7 @@ namespace OpenRA.Mods.Common.Server
|
||||
{
|
||||
isBusy = true;
|
||||
|
||||
Action a = () =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -160,9 +161,7 @@ namespace OpenRA.Mods.Common.Server
|
||||
}
|
||||
|
||||
isBusy = false;
|
||||
};
|
||||
|
||||
a.BeginInvoke(null, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,24 +61,22 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
RegionsFromSlices(png, out frameRegions, out frameOffsets);
|
||||
|
||||
frames = new ISpriteFrame[frameRegions.Count];
|
||||
|
||||
var stride = png.PixelStride;
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
var frameStart = frameRegions[i].X + frameRegions[i].Y * png.Width;
|
||||
var frameSize = new Size(frameRegions[i].Width, frameRegions[i].Height);
|
||||
var pixelLength = png.Palette == null ? 4 : 1;
|
||||
|
||||
frames[i] = new PngSheetFrame()
|
||||
{
|
||||
Size = frameSize,
|
||||
FrameSize = frameSize,
|
||||
Offset = frameOffsets[i],
|
||||
Data = new byte[frameRegions[i].Width * frameRegions[i].Height * pixelLength],
|
||||
Type = png.Palette == null ? SpriteFrameType.BGRA : SpriteFrameType.Indexed
|
||||
Data = new byte[frameRegions[i].Width * frameRegions[i].Height * stride],
|
||||
Type = png.Type
|
||||
};
|
||||
|
||||
for (var y = 0; y < frames[i].Size.Height; y++)
|
||||
Array.Copy(png.Data, (frameStart + y * png.Width) * pixelLength, frames[i].Data, y * frames[i].Size.Width * pixelLength, frames[i].Size.Width * pixelLength);
|
||||
Array.Copy(png.Data, (frameStart + y * png.Width) * stride, frames[i].Data, y * frames[i].Size.Width * stride, frames[i].Size.Width * stride);
|
||||
}
|
||||
|
||||
metadata = new TypeDictionary
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
class ShpTSFrame : ISpriteFrame
|
||||
{
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed; } }
|
||||
public SpriteFrameType Type { get { return SpriteFrameType.Indexed8; } }
|
||||
public Size Size { get; private set; }
|
||||
public Size FrameSize { get; private set; }
|
||||
public float2 Offset { get; private set; }
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Accepted `DeliversCash` types. Leave empty to accept all types.")]
|
||||
public readonly HashSet<string> ValidTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Stance the delivering actor needs to enter.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally;
|
||||
[Desc("Player relationships the owner of the delivering actor needs.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally;
|
||||
|
||||
[Desc("Play a randomly selected sound from this list when accepting cash.")]
|
||||
public readonly string[] Sounds = { };
|
||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public class AcceptsDeliveredCash : INotifyCashTransfer
|
||||
{
|
||||
AcceptsDeliveredCashInfo info;
|
||||
readonly AcceptsDeliveredCashInfo info;
|
||||
|
||||
public AcceptsDeliveredCash(Actor self, AcceptsDeliveredCashInfo info)
|
||||
{
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Accepted `DeliversExperience` types. Leave empty to accept all types.")]
|
||||
public readonly HashSet<string> ValidTypes = new HashSet<string>();
|
||||
|
||||
[Desc("Stance the delivering actor needs to enter.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally;
|
||||
[Desc("Player relationships the owner of the delivering actor needs.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AcceptsDeliveredExperience(init.Self, this); }
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public WeaponInfo WeaponInfo { get; private set; }
|
||||
public WDist ModifiedRange { get; private set; }
|
||||
|
||||
public readonly PlayerRelationship TargetStances = PlayerRelationship.Enemy;
|
||||
public readonly PlayerRelationship ForceTargetStances = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally;
|
||||
public readonly PlayerRelationship TargetRelationships = PlayerRelationship.Enemy;
|
||||
public readonly PlayerRelationship ForceTargetRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally;
|
||||
|
||||
// TODO: instead of having multiple Armaments and unique AttackBase,
|
||||
// an actor should be able to have multiple AttackBases with
|
||||
|
||||
@@ -377,7 +377,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
return Armaments.Where(a =>
|
||||
!a.IsTraitDisabled
|
||||
&& (owner == null || (forceAttack ? a.Info.ForceTargetStances : a.Info.TargetStances).HasStance(self.Owner.RelationshipWith(owner)))
|
||||
&& (owner == null || (forceAttack ? a.Info.ForceTargetRelationships : a.Info.TargetRelationships).HasStance(self.Owner.RelationshipWith(owner)))
|
||||
&& a.Weapon.IsValidAgainst(t, self.World, self));
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var stances = PlayerRelationship.None;
|
||||
foreach (var armament in Armaments)
|
||||
if (!armament.IsTraitDisabled)
|
||||
stances |= armament.Info.TargetStances;
|
||||
stances |= armament.Info.TargetRelationships;
|
||||
|
||||
return stances;
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return activeTargetPriorities.Any(ati =>
|
||||
{
|
||||
// Incompatible stances
|
||||
if (!ati.ValidStances.HasStance(self.Owner.RelationshipWith(owner)))
|
||||
if (!ati.ValidRelationships.HasStance(self.Owner.RelationshipWith(owner)))
|
||||
return false;
|
||||
|
||||
// Incompatible target types
|
||||
@@ -391,7 +391,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
// Incompatible stances
|
||||
if (!ati.ValidStances.HasStance(self.Owner.RelationshipWith(owner)))
|
||||
if (!ati.ValidRelationships.HasStance(self.Owner.RelationshipWith(owner)))
|
||||
return false;
|
||||
|
||||
// Incompatible target types
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Target types that can't be AutoTargeted.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
[Desc("Stances between actor's and target's owner which can be AutoTargeted.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Relationships between actor's and target's owner needed for AutoTargeting.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("ValidTargets with larger priorities will be AutoTargeted before lower priorities.")]
|
||||
public readonly int Priority = 1;
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Should visibility (Shroud, Fog, Cloak, etc) be considered when searching for capturable targets?")]
|
||||
public readonly bool CheckCaptureTargetsForVisibility = true;
|
||||
|
||||
[Desc("Player stances that capturers should attempt to target.")]
|
||||
public readonly PlayerRelationship CapturableStances = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
[Desc("Player relationships that capturers should attempt to target.")]
|
||||
public readonly PlayerRelationship CapturableRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new CaptureManagerBotModule(init.Self, this); }
|
||||
}
|
||||
@@ -133,7 +133,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
var randPlayer = world.Players.Where(p => !p.Spectating
|
||||
&& Info.CapturableStances.HasStance(player.RelationshipWith(p))).Random(world.LocalRandom);
|
||||
&& Info.CapturableRelationships.HasStance(player.RelationshipWith(p))).Random(world.LocalRandom);
|
||||
|
||||
var targetOptions = Info.CheckCaptureTargetsForVisibility
|
||||
? GetVisibleActorsBelongingToPlayer(randPlayer)
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// Use for proactive targeting.
|
||||
public bool IsPreferredEnemyUnit(Actor a)
|
||||
{
|
||||
if (a == null || a.IsDead || Player.RelationshipWith(a.Owner) != PlayerRelationship.Enemy || a.Info.HasTraitInfo<HuskInfo>())
|
||||
if (a == null || a.IsDead || Player.RelationshipWith(a.Owner) != PlayerRelationship.Enemy || a.Info.HasTraitInfo<HuskInfo>() || a.Info.HasTraitInfo<AircraftInfo>())
|
||||
return false;
|
||||
|
||||
var targetTypes = a.GetEnabledTargetTypes();
|
||||
|
||||
@@ -68,6 +68,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
class GroundUnitsAttackMoveState : GroundStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
@@ -91,6 +95,27 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (leader == null)
|
||||
return;
|
||||
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3)
|
||||
.Where(a => a.Owner == owner.Units.First().Owner && owner.Units.Contains(a)).ToHashSet();
|
||||
|
||||
@@ -126,6 +151,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
class GroundUnitsAttackState : GroundStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
@@ -145,6 +174,28 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var a in owner.Units)
|
||||
if (!BusyAttack(a))
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
|
||||
|
||||
@@ -93,6 +93,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
class NavyUnitsAttackMoveState : NavyStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
@@ -116,6 +120,27 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
if (leader == null)
|
||||
return;
|
||||
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3)
|
||||
.Where(a => a.Owner == owner.Units.First().Owner && owner.Units.Contains(a)).ToHashSet();
|
||||
|
||||
@@ -151,6 +176,10 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
|
||||
class NavyUnitsAttackState : NavyStateBase, IState
|
||||
{
|
||||
int lastUpdatedTick;
|
||||
CPos? lastLeaderLocation;
|
||||
Actor lastTarget;
|
||||
|
||||
public void Activate(Squad owner) { }
|
||||
|
||||
public void Tick(Squad owner)
|
||||
@@ -170,6 +199,28 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
|
||||
}
|
||||
}
|
||||
|
||||
var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition);
|
||||
if (leader.Location != lastLeaderLocation)
|
||||
{
|
||||
lastLeaderLocation = leader.Location;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
if (owner.TargetActor != lastTarget)
|
||||
{
|
||||
lastTarget = owner.TargetActor;
|
||||
lastUpdatedTick = owner.World.WorldTick;
|
||||
}
|
||||
|
||||
// HACK: Drop back to the idle state if we haven't moved in 2.5 seconds
|
||||
// This works around the squad being stuck trying to attack-move to a location
|
||||
// that they cannot path to, generating expensive pathfinding calls each tick.
|
||||
if (owner.World.WorldTick > lastUpdatedTick + 63)
|
||||
{
|
||||
owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsIdleState(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var a in owner.Units)
|
||||
if (!BusyAttack(a))
|
||||
owner.Bot.QueueOrder(new Order("Attack", a, Target.FromActor(owner.TargetActor), false));
|
||||
|
||||
@@ -54,7 +54,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Sort order for the production palette. Smaller numbers are presented earlier.")]
|
||||
public readonly int BuildPaletteOrder = 9999;
|
||||
|
||||
[Translate]
|
||||
[Desc("Text shown in the production tooltip.")]
|
||||
public readonly string Description = "";
|
||||
|
||||
|
||||
@@ -75,13 +75,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
// Display only the first level of priority
|
||||
var priorityExits = self.Info.TraitInfos<ExitInfo>()
|
||||
.GroupBy(e => e.Priority)
|
||||
.FirstOrDefault();
|
||||
|
||||
var exits = priorityExits != null ? priorityExits.ToArray() : new ExitInfo[0];
|
||||
self.World.Add(new RallyPointIndicator(self, this, exits));
|
||||
self.World.Add(new RallyPointIndicator(self, this));
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("CaptureTypes (from the Captures trait) that are able to capture this.")]
|
||||
public readonly BitSet<CaptureType> Types = default(BitSet<CaptureType>);
|
||||
|
||||
[Desc("What diplomatic stances can be captured by this actor.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("What player relationships the target's owner needs to be captured by this actor.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Cancel the actor's current activity when getting captured.")]
|
||||
public readonly bool CancelActivity = false;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// Actors with FrozenUnderFog should therefore not disable the Capturable trait.
|
||||
var stance = captor.Owner.RelationshipWith(frozenActor.Owner);
|
||||
return frozenActor.Info.TraitInfos<CapturableInfo>()
|
||||
.Any(c => c.ValidStances.HasStance(stance) && captures.Info.CaptureTypes.Overlaps(c.Types));
|
||||
.Any(c => c.ValidRelationships.HasStance(stance) && captures.Info.CaptureTypes.Overlaps(c.Types));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,13 +108,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
allyCapturableTypes = neutralCapturableTypes = enemyCapturableTypes = default(BitSet<CaptureType>);
|
||||
foreach (var c in enabledCapturable)
|
||||
{
|
||||
if (c.Info.ValidStances.HasStance(PlayerRelationship.Ally))
|
||||
if (c.Info.ValidRelationships.HasStance(PlayerRelationship.Ally))
|
||||
allyCapturableTypes = allyCapturableTypes.Union(c.Info.Types);
|
||||
|
||||
if (c.Info.ValidStances.HasStance(PlayerRelationship.Neutral))
|
||||
if (c.Info.ValidRelationships.HasStance(PlayerRelationship.Neutral))
|
||||
neutralCapturableTypes = neutralCapturableTypes.Union(c.Info.Types);
|
||||
|
||||
if (c.Info.ValidStances.HasStance(PlayerRelationship.Enemy))
|
||||
if (c.Info.ValidRelationships.HasStance(PlayerRelationship.Enemy))
|
||||
enemyCapturableTypes = enemyCapturableTypes.Union(c.Info.Types);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Experience granted to the capturing player.")]
|
||||
public readonly int PlayerExperience = 0;
|
||||
|
||||
[Desc("Stance that the structure's previous owner needs to have for the capturing player to receive Experience.")]
|
||||
public readonly PlayerRelationship PlayerExperienceStances = PlayerRelationship.Enemy;
|
||||
[Desc("Relationships that the structure's previous owner needs to have for the capturing player to receive Experience.")]
|
||||
public readonly PlayerRelationship PlayerExperienceRelationships = PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Cursor to display when the health of the target actor is above the sabotage threshold.")]
|
||||
public readonly string SabotageCursor = "capture";
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
"Ignored if 0 (actors are selected regardless of vertical distance).")]
|
||||
public readonly WDist MaximumVerticalOffset = WDist.Zero;
|
||||
|
||||
[Desc("What diplomatic stances are affected.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally;
|
||||
[Desc("What player relationships are affected.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally;
|
||||
|
||||
[Desc("Condition is applied permanently to this actor.")]
|
||||
public readonly bool AffectsParent = false;
|
||||
@@ -111,7 +111,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
var stance = self.Owner.RelationshipWith(a.Owner);
|
||||
if (!Info.ValidStances.HasStance(stance))
|
||||
if (!Info.ValidRelationships.HasStance(stance))
|
||||
return;
|
||||
|
||||
var external = a.TraitsImplementing<ExternalCondition>()
|
||||
@@ -135,7 +135,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if ((produced.CenterPosition - self.CenterPosition).HorizontalLengthSquared <= Info.Range.LengthSquared)
|
||||
{
|
||||
var stance = self.Owner.RelationshipWith(produced.Owner);
|
||||
if (!Info.ValidStances.HasStance(stance))
|
||||
if (!Info.ValidRelationships.HasStance(stance))
|
||||
return;
|
||||
|
||||
var external = produced.TraitsImplementing<ExternalCondition>()
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class CreatesShroudInfo : AffectsShroudInfo
|
||||
{
|
||||
[Desc("Stance the watching player needs to see the generated shroud.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Relationship the watching player needs to see the generated shroud.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new CreatesShroud(init.Self, this); }
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
protected override void AddCellsToPlayerShroud(Actor self, Player p, PPos[] uv)
|
||||
{
|
||||
if (!info.ValidStances.HasStance(self.Owner.RelationshipWith(p)))
|
||||
if (!info.ValidRelationships.HasStance(self.Owner.RelationshipWith(p)))
|
||||
return;
|
||||
|
||||
p.Shroud.AddSource(this, Shroud.SourceType.Shroud, uv);
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (IsTraitDisabled || !self.IsAtGroundLevel() || !Info.CrushClasses.Overlaps(crushClasses))
|
||||
return self.World.NoPlayersMask;
|
||||
|
||||
return Info.CrushedByFriendlies ? self.World.AllPlayersMask : self.Owner.EnemyPlayersMask;
|
||||
return Info.CrushedByFriendlies ? self.World.AllPlayersMask : ~self.Owner.AlliedPlayersMask;
|
||||
}
|
||||
|
||||
bool CrushableInner(BitSet<CrushClass> crushClasses, Player crushOwner)
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var targetInfo = target.Info.TraitInfoOrDefault<AcceptsDeliveredCashInfo>();
|
||||
if (targetInfo == null || !targetInfo.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (targetInfo == null || !targetInfo.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (targetInfo.ValidTypes.Count == 0)
|
||||
@@ -112,7 +112,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var targetInfo = target.Info.TraitInfoOrDefault<AcceptsDeliveredCashInfo>();
|
||||
if (targetInfo == null || !targetInfo.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (targetInfo == null || !targetInfo.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (targetInfo.ValidTypes.Count == 0)
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
var targetInfo = target.Info.TraitInfoOrDefault<AcceptsDeliveredExperienceInfo>();
|
||||
if (targetInfo == null || !targetInfo.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (targetInfo == null || !targetInfo.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (targetInfo.ValidTypes.Count == 0)
|
||||
@@ -129,7 +129,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
var targetInfo = target.Info.TraitInfoOrDefault<AcceptsDeliveredExperienceInfo>();
|
||||
if (targetInfo == null || !targetInfo.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (targetInfo == null || !targetInfo.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (targetInfo.ValidTypes.Count == 0)
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Color to use for the target line.")]
|
||||
public readonly Color TargetLineColor = Color.Crimson;
|
||||
|
||||
public readonly PlayerRelationship TargetStances = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
public readonly PlayerRelationship ForceTargetStances = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally;
|
||||
public readonly PlayerRelationship TargetRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral;
|
||||
public readonly PlayerRelationship ForceTargetRelationships = PlayerRelationship.Enemy | PlayerRelationship.Neutral | PlayerRelationship.Ally;
|
||||
|
||||
[Desc("Cursor to display when hovering over a demolishable target.")]
|
||||
public readonly string Cursor = "c4";
|
||||
@@ -118,10 +118,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
var stance = target.Owner.RelationshipWith(self.Owner);
|
||||
if (!info.TargetStances.HasStance(stance) && !modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
if (!info.TargetRelationships.HasStance(stance) && !modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return false;
|
||||
|
||||
if (!info.ForceTargetStances.HasStance(stance) && modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
if (!info.ForceTargetRelationships.HasStance(stance) && modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return false;
|
||||
|
||||
return target.TraitsImplementing<IDemolishable>().Any(i => i.IsValidTarget(target, self));
|
||||
@@ -130,10 +130,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
var stance = target.Owner.RelationshipWith(self.Owner);
|
||||
if (!info.TargetStances.HasStance(stance) && !modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
if (!info.TargetRelationships.HasStance(stance) && !modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return false;
|
||||
|
||||
if (!info.ForceTargetStances.HasStance(stance) && modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
if (!info.ForceTargetRelationships.HasStance(stance) && modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return false;
|
||||
|
||||
return target.Info.TraitInfos<IDemolishableInfo>().Any(i => i.IsValidTarget(target.Info, self));
|
||||
|
||||
@@ -33,8 +33,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
"Possible values are Exit, Suicide, Dispose.")]
|
||||
public readonly EnterBehaviour EnterBehaviour = EnterBehaviour.Dispose;
|
||||
|
||||
[Desc("What diplomatic stances allow target to be repaired by this actor.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Ally;
|
||||
[Desc("What player relationship the target's owner needs to be repaired by this actor.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally;
|
||||
|
||||
[Desc("Sound to play when repairing is done.")]
|
||||
public readonly string RepairSound = null;
|
||||
@@ -117,7 +117,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!engineerRepairable.Info.Types.IsEmpty && !engineerRepairable.Info.Types.Overlaps(info.Types))
|
||||
return false;
|
||||
|
||||
if (!info.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (!info.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (target.GetDamageState() == DamageState.Undamaged)
|
||||
@@ -139,7 +139,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!engineerRepairable.Types.IsEmpty && !engineerRepairable.Types.Overlaps(info.Types))
|
||||
return false;
|
||||
|
||||
if (!info.ValidStances.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
if (!info.ValidRelationships.HasStance(target.Owner.RelationshipWith(self.Owner)))
|
||||
return false;
|
||||
|
||||
if (target.DamageState == DamageState.Undamaged)
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Percentage of the killed actor's Cost or CustomSellValue to be given.")]
|
||||
public readonly int Percentage = 10;
|
||||
|
||||
[Desc("Stance the attacking player needs to receive the bounty.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Player relationships the attacking player needs to receive the bounty.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Whether to show a floating text announcing the won bounty.")]
|
||||
public readonly bool ShowBounty = true;
|
||||
@@ -64,7 +64,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (e.Attacker == null || e.Attacker.Disposed || IsTraitDisabled)
|
||||
return;
|
||||
|
||||
if (!Info.ValidStances.HasStance(e.Attacker.Owner.RelationshipWith(self.Owner)))
|
||||
if (!Info.ValidRelationships.HasStance(e.Attacker.Owner.RelationshipWith(self.Owner)))
|
||||
return;
|
||||
|
||||
if (!Info.DeathTypes.IsEmpty && !e.Damage.DamageTypes.Overlaps(Info.DeathTypes))
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("If -1, use the value of the unit cost.")]
|
||||
public readonly int Experience = -1;
|
||||
|
||||
[Desc("Stance the attacking player needs to receive the experience.")]
|
||||
public readonly PlayerRelationship ValidStances = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("Player relationships the attacking player needs to receive the experience.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Percentage of the `Experience` value that is being granted to the killing actor.")]
|
||||
public readonly int ActorExperienceModifier = 10000;
|
||||
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (exp == 0 || e.Attacker == null || e.Attacker.Disposed)
|
||||
return;
|
||||
|
||||
if (!info.ValidStances.HasStance(e.Attacker.Owner.RelationshipWith(self.Owner)))
|
||||
if (!info.ValidRelationships.HasStance(e.Attacker.Owner.RelationshipWith(self.Owner)))
|
||||
return;
|
||||
|
||||
exp = Util.ApplyPercentageModifiers(exp, experienceModifiers);
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
@@ -28,6 +30,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Chance (out of 100) the unit has to enter panic mode when attacking.")]
|
||||
public readonly int AttackPanicChance = 20;
|
||||
|
||||
[Desc("The terrain types that this actor should avoid running on to while panicking.")]
|
||||
public readonly HashSet<string> AvoidTerrainTypes = new HashSet<string>();
|
||||
|
||||
[SequenceReference(prefix: true)]
|
||||
public readonly string PanicSequencePrefix = "panic-";
|
||||
|
||||
@@ -39,6 +44,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
readonly ScaredyCatInfo info;
|
||||
readonly Mobile mobile;
|
||||
readonly Actor self;
|
||||
readonly Func<CPos, bool> avoidTerrainFilter;
|
||||
|
||||
[Sync]
|
||||
int panicStartedTick;
|
||||
@@ -52,6 +58,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
mobile = self.Trait<Mobile>();
|
||||
|
||||
if (info.AvoidTerrainTypes.Count > 0)
|
||||
avoidTerrainFilter = c => info.AvoidTerrainTypes.Contains(self.World.Map.GetTerrainInfo(c).Type);
|
||||
}
|
||||
|
||||
public void Panic()
|
||||
@@ -79,7 +88,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!Panicking)
|
||||
return;
|
||||
|
||||
mobile.Nudge(self);
|
||||
// Note: This is just a modified copy of Mobile.Nudge
|
||||
var cell = mobile.GetAdjacentCell(self.Location, avoidTerrainFilter);
|
||||
if (cell != null)
|
||||
self.QueueActivity(false, mobile.MoveTo(cell.Value, 0));
|
||||
}
|
||||
|
||||
void INotifyDamage.Damaged(Actor self, AttackInfo e)
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Range of the deflection.")]
|
||||
public readonly WDist Range = WDist.Zero;
|
||||
|
||||
[Desc("What diplomatic stances are affected.")]
|
||||
public readonly PlayerRelationship DeflectionStances = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
[Desc("What player relationships are affected.")]
|
||||
public readonly PlayerRelationship DeflectionRelationships = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Chance of deflecting missiles.")]
|
||||
public readonly int Chance = 100;
|
||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public class JamsMissiles : ConditionalTrait<JamsMissilesInfo>
|
||||
{
|
||||
public WDist Range { get { return IsTraitDisabled ? WDist.Zero : Info.Range; } }
|
||||
public PlayerRelationship DeflectionStances { get { return Info.DeflectionStances; } }
|
||||
public PlayerRelationship DeflectionStances { get { return Info.DeflectionRelationships; } }
|
||||
public int Chance { get { return Info.Chance; } }
|
||||
|
||||
public JamsMissiles(JamsMissilesInfo info)
|
||||
|
||||
@@ -364,14 +364,14 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.QueueActivity(false, MoveTo(cell.Value, 0));
|
||||
}
|
||||
|
||||
public CPos? GetAdjacentCell(CPos nextCell)
|
||||
public CPos? GetAdjacentCell(CPos nextCell, Func<CPos, bool> preferToAvoid = null)
|
||||
{
|
||||
var availCells = new List<CPos>();
|
||||
var notStupidCells = new List<CPos>();
|
||||
foreach (CVec direction in CVec.Directions)
|
||||
{
|
||||
var p = ToCell + direction;
|
||||
if (CanEnterCell(p) && CanStayInCell(p))
|
||||
if (CanEnterCell(p) && CanStayInCell(p) && (preferToAvoid == null || !preferToAvoid(p)))
|
||||
availCells.Add(p);
|
||||
else if (p != nextCell && p != ToCell)
|
||||
notStupidCells.Add(p);
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("This actor will remain visible (but not updated visually) under fog, once discovered.")]
|
||||
public class FrozenUnderFogInfo : TraitInfo, Requires<BuildingInfo>, IDefaultVisibilityInfo
|
||||
{
|
||||
[Desc("Players with these stances can always see the actor.")]
|
||||
public readonly PlayerRelationship AlwaysVisibleStances = PlayerRelationship.Ally;
|
||||
[Desc("Players with these relationships can always see the actor.")]
|
||||
public readonly PlayerRelationship AlwaysVisibleRelationships = PlayerRelationship.Ally;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new FrozenUnderFog(init, this); }
|
||||
}
|
||||
@@ -106,7 +106,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return true;
|
||||
|
||||
var stance = self.Owner.RelationshipWith(byPlayer);
|
||||
return info.AlwaysVisibleStances.HasStance(stance) || IsVisibleInner(self, byPlayer);
|
||||
return info.AlwaysVisibleRelationships.HasStance(stance) || IsVisibleInner(self, byPlayer);
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("The actor stays invisible under the shroud.")]
|
||||
public class HiddenUnderShroudInfo : TraitInfo, IDefaultVisibilityInfo
|
||||
{
|
||||
[Desc("Players with these stances can always see the actor.")]
|
||||
public readonly PlayerRelationship AlwaysVisibleStances = PlayerRelationship.Ally;
|
||||
[Desc("Players with these relationships can always see the actor.")]
|
||||
public readonly PlayerRelationship AlwaysVisibleRelationships = PlayerRelationship.Ally;
|
||||
|
||||
[Desc("Possible values are CenterPosition (reveal when the center is visible) and ",
|
||||
"Footprint (reveal when any footprint cell is visible).")]
|
||||
@@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return true;
|
||||
|
||||
var stance = self.Owner.RelationshipWith(byPlayer);
|
||||
return Info.AlwaysVisibleStances.HasStance(stance) || IsVisibleInner(self, byPlayer);
|
||||
return Info.AlwaysVisibleRelationships.HasStance(stance) || IsVisibleInner(self, byPlayer);
|
||||
}
|
||||
|
||||
IEnumerable<IRenderable> IRenderModifier.ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Delay for the end game notification in milliseconds.")]
|
||||
public readonly int NotificationDelay = 1500;
|
||||
|
||||
[Translate]
|
||||
[Desc("Description of the objective.")]
|
||||
public readonly string Objective = "Destroy all opposition!";
|
||||
|
||||
|
||||
@@ -19,11 +19,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Attach this to the player actor.")]
|
||||
public class DeveloperModeInfo : TraitInfo, ILobbyOptions
|
||||
{
|
||||
[Translate]
|
||||
[Desc("Descriptive label for the developer mode checkbox in the lobby.")]
|
||||
public readonly string CheckboxLabel = "Debug Menu";
|
||||
|
||||
[Translate]
|
||||
[Desc("Tooltip description for the developer mode checkbox in the lobby.")]
|
||||
public readonly string CheckboxDescription = "Enables cheats and developer commands";
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
@@ -58,15 +59,19 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[NotificationReference("Speech")]
|
||||
public readonly string LeaveNotification = null;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new MissionObjectives(init.World, this); }
|
||||
public override object Create(ActorInitializer init) { return new MissionObjectives(init.Self.Owner, this); }
|
||||
}
|
||||
|
||||
public class MissionObjectives : INotifyWinStateChanged, ISync, IResolveOrder
|
||||
public class MissionObjectives : INotifyWinStateChanged, ISync, IResolveOrder, IWorldLoaded
|
||||
{
|
||||
public readonly MissionObjectivesInfo Info;
|
||||
readonly List<MissionObjective> objectives = new List<MissionObjective>();
|
||||
readonly Player player;
|
||||
public ReadOnlyList<MissionObjective> Objectives;
|
||||
|
||||
Player[] enemies;
|
||||
Player[] allies;
|
||||
|
||||
[Sync]
|
||||
public int ObjectivesHash
|
||||
{
|
||||
@@ -83,12 +88,21 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// The player's WinState is only updated when his allies have all completed their objective as well.
|
||||
public WinState WinStateCooperative { get; private set; }
|
||||
|
||||
public MissionObjectives(World world, MissionObjectivesInfo info)
|
||||
public MissionObjectives(Player player, MissionObjectivesInfo info)
|
||||
{
|
||||
Info = info;
|
||||
this.player = player;
|
||||
Objectives = new ReadOnlyList<MissionObjective>(objectives);
|
||||
}
|
||||
|
||||
void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)
|
||||
{
|
||||
// Players and NonCombatants are fixed once the game starts, but the result of IsAlliedWith
|
||||
// may change once players are marked as spectators, so cache these
|
||||
allies = player.World.Players.Where(p => !p.NonCombatant && player.IsAlliedWith(p)).ToArray();
|
||||
enemies = player.World.Players.Where(p => !p.NonCombatant && player.RelationshipWith(p) == PlayerRelationship.Enemy).ToArray();
|
||||
}
|
||||
|
||||
public int Add(Player player, string description, string type, bool required = true, bool inhibitAnnouncement = false)
|
||||
{
|
||||
var newID = objectives.Count;
|
||||
@@ -140,10 +154,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void CheckIfGameIsOver(Player player)
|
||||
{
|
||||
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||
|
||||
var gameOver = players.All(p => p.WinState != WinState.Undefined || !p.HasObjectives);
|
||||
var gameOver = player.World.Players.All(p => p.NonCombatant || p.WinState != WinState.Undefined || !p.HasObjectives);
|
||||
if (gameOver)
|
||||
{
|
||||
Game.RunAfterDelay(Info.GameOverDelay, () =>
|
||||
{
|
||||
if (!Game.IsCurrentWorld(player.World))
|
||||
@@ -153,17 +166,14 @@ namespace OpenRA.Mods.Common.Traits
|
||||
player.World.SetPauseState(true);
|
||||
player.World.PauseStateLocked = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyWinStateChanged.OnPlayerWon(Player player)
|
||||
{
|
||||
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||
var enemies = players.Where(p => !p.IsAlliedWith(player));
|
||||
|
||||
if (Info.Cooperative)
|
||||
{
|
||||
WinStateCooperative = WinState.Won;
|
||||
var allies = players.Where(p => p.IsAlliedWith(player));
|
||||
|
||||
if (allies.All(p => p.PlayerActor.Trait<MissionObjectives>().WinStateCooperative == WinState.Won))
|
||||
{
|
||||
@@ -193,13 +203,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void INotifyWinStateChanged.OnPlayerLost(Player player)
|
||||
{
|
||||
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||
var enemies = players.Where(p => !p.IsAlliedWith(player));
|
||||
|
||||
if (Info.Cooperative)
|
||||
{
|
||||
WinStateCooperative = WinState.Lost;
|
||||
var allies = players.Where(p => p.IsAlliedWith(player));
|
||||
|
||||
if (allies.Any(p => p.PlayerActor.Trait<MissionObjectives>().WinStateCooperative == WinState.Lost))
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user