Compare commits

...

75 Commits

Author SHA1 Message Date
Paul Chote
afde6850d1 Remove obsolete --check-runtime-assemblies utility command. 2021-01-01 18:48:05 +00:00
Paul Chote
297d01d463 Change install_assemblies_mono to install all bin contents. 2021-01-01 18:48:05 +00:00
Paul Chote
f9d6338a98 Switch Appimages to net 5. 2021-01-01 18:47:57 +00:00
Paul Chote
18d1d7d62a Run appimagetool directly. 2021-01-01 18:47:56 +00:00
Paul Chote
63dd4b382e Use ULFO format for non-compat disk images (requires macOS 10.11+). 2021-01-01 18:47:56 +00:00
Paul Chote
f61fda19f5 Trim unused assemblies to reduce packaged size further. 2021-01-01 18:47:56 +00:00
Paul Chote
c18d509ddd Replace duplicate runtime files with hardlinks to reduce dmg size. 2021-01-01 18:47:56 +00:00
Paul Chote
1782794bac Switch macOS packages to .NET 5. 2021-01-01 18:47:43 +00:00
teinarss
5e74e58b22 Add support for dotnet core for Windows 2021-01-01 19:42:01 +01:00
abcdefg30
fef7a018f2 Add an update rule 2020-12-31 12:09:56 +01:00
abcdefg30
1d4891b017 Rename several MP traits 2020-12-31 12:09:56 +01:00
Ivaylo Draganov
e9a803e3c1 Update launch.json to work with the new launcher 2020-12-30 17:31:37 +01:00
Nah
4abe7d5895 Update defaults.yaml
Fix the Building's SoundOnDamageTransition DamagedSounds with the correct one and add a second sound file to DestroyedSounds for more variation like in the original Tiberian Dawn.
2020-12-30 14:06:56 +00:00
abcdefg30
cd68db5c11 Move myes1 and mrise1 from Select to Move Mechanic Voices 2020-12-30 02:01:00 +00:00
abcdefg30
ae9f437bb8 Enable mechanic voices from Aftermath 2020-12-30 02:01:00 +00:00
Smittytron
a047893049 Remove out of bounds actors from Exodus 2020-12-30 01:51:27 +00:00
Smittytron
5b2733decf Fix lag issue on Exodus by narrowing exit area 2020-12-30 01:51:27 +00:00
Ivaylo Draganov
ff60540fac Add player random faction info in the objectives panel 2020-12-30 01:44:50 +00:00
abcdefg30
a68a91bb39 Explicitly return on failure in make.ps1 2020-12-29 21:26:07 +01:00
abcdefg30
0bf8d2241f Correct Shock Trooper Move and Select voices 2020-12-29 21:15:53 +01:00
Orb
06850e6271 Fix-MT-Sound-Commit 2020-12-28 10:56:20 +01:00
Paul Chote
62fa3b7c9c Rename SpriteFrameType enums. 2020-12-25 18:51:25 +01:00
Paul Chote
ce09b402d0 Fix definition and use of non-indexed sprite color channels.
Our SpriteFrameType names refer to the byte channel order rather than
the bit order, meaning that SpriteFrameType.BGRA corresponds to the
standard Color.ToArgb() etc byte order when the (little-endian) integer
is read as 4 individual bytes.

The previous code did not account for the fact that non-indexed Png
uses big-endian storage for its RGBA colours, and that SheetBuilder
had the color channels incorrectly swapped to match and cancel this out.

New SpriteFrameType enums are introduced to distinguish between BGRA
(little-endian) and RGBA (big-endian) formats, and also for 24bit data
without alpha. The channel swizzling / alpha creation is now handled
when copying into the texture atlas, removing the need for non-png
ISpriteLoader implementations to allocate an additional temporary array
and reorder the channels during load.
2020-12-25 18:51:25 +01:00
Paul Chote
6e7ad9df25 Remove vestigial translation plumbing.
This was never completed to the level required to
be properly used ingame.
2020-12-25 16:18:28 +01:00
Matthias Mailänder
fb20479379 Add .vxl support to the asset browser. 2020-12-25 00:00:11 +00:00
Matthias Mailänder
9d181e88d2 Name the files that cause crashes. 2020-12-25 00:00:11 +00:00
Vapre
e90fc1ef39 As proposed by Leonardo-Ferreira. 2020-12-24 23:43:35 +00:00
Vapre
ce013f17d6 Server DispatchOrdersToClients create frame once for all clients.
Avoid creating frame data per client connection. Avoid
the allocation of a memory stream and setting frame header
and copying frame data.
2020-12-24 23:43:35 +00:00
Vapre
78253ce284 Activity, fixes.
Do not call SkipDoneActivities method recursively via the
NextActivity property. Rather use the nextActivity member.
Avoiding additional function calls and a recursively
growing stack.

Do not call ChildActivity and NextActivity properties
twice in a row. Once to test for null and after to access
it's value. It will cause the complete list of activities
to be traversed twice looking for non done activities.

Replace Queue method with a version that does not the
NextActivity property causing an extra call to
SkipDoneActivities. Avoid calling Queue recursively.

Similar replace QueueChild with a version that does
not call additional methods.

Note that ActivitiesImplementing returns only non
done activities. The method name does not suggest this.

Please consider making NextActivity a method to cleary indicate it
involves the logic of skipping Done activities. To let
the called know it is 'expensive'.

Please consider renaming the protected property ChildActivity to
FirstChildActivityNotDone to avoid it being used as childActivity.

Please consider maintaining a pointer to the first
non done activity. This avoids the need the each time find it.
2020-12-24 23:02:07 +00:00
Taryn Hill
2671e40c1d feat: ActorSpawnManager.SpawnInterval supports 1 or 2 values
Providing 2 values creates a range from which a value is randomly selected.
2020-12-24 22:15:15 +00:00
Matthias Mailänder
04cda69ef9 This compression is actually not yet supported. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
b4c483ce1a Fix channels. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
9a9f58d744 Support 8 bit .aud files. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
d38fe542a2 Improve performance. 2020-12-24 15:56:18 +00:00
Matthias Mailänder
80503fbf36 Bump SharpZipLib. 2020-12-24 13:48:21 +00:00
Paul Chote
99a23b4056 Fix an incorrect comment in install_assemblies_mono 2020-12-24 12:08:56 +01:00
Matthias Mailänder
13a7de4b6b Allow a system chat label override. 2020-12-24 10:01:54 +00:00
teinarss
73bba97aaa Update MasterServerPinger to modern approach 2020-12-22 20:57:40 +01:00
abcdefg30
d6e9cdab5b Add the 9th Dark Tournament map as "Oil Spill" 2020-12-21 21:08:39 +01:00
Paul Chote
1a177bc2de Remove unused variables from Map.SavePreview. 2020-12-19 13:07:01 +01:00
Paul Chote
e0b3e631fe Remove obsolete null checks. 2020-12-19 13:07:01 +01:00
Paul Chote
2518a353af Add lint test for invalid map tiles. 2020-12-19 13:07:01 +01:00
Paul Chote
989800efff Fix missing tiles in upstream maps. 2020-12-19 13:07:01 +01:00
Paul Chote
c02846e2cb Replace invalid tiles on map load. 2020-12-19 13:07:01 +01:00
Matthias Mailänder
09db4a0e25 Enable debug mode by default. 2020-12-15 23:06:05 +01:00
Trevor Nichols
a85da9d86c Implement loader for MegV3 file format 2020-12-15 11:11:05 +01:00
Paul Chote
920d00bbae Use nearest-neighbour scaling in --debug-chrome-regions utility command. 2020-12-14 22:38:25 +01:00
Ivaylo Draganov
62475279ee Remove unneeded chrome yaml declarations 2020-12-14 20:52:38 +01:00
Matthias Mailänder
d8e979d283 Remove dead code. 2020-12-14 20:38:50 +01:00
Paul Chote
299b8880dd Fix api output directory. 2020-12-14 18:33:35 +01:00
Paul Chote
61027e4067 Fix docs.openra.net repository reference. 2020-12-14 18:33:35 +01:00
Paul Chote
a7249c10dc Fix docs.openra.net documentation workflow. 2020-12-13 15:35:31 +01:00
Paul Chote
611d12ac78 Migrate CI and packaging from Travis CI to GitHub Actions. 2020-12-12 20:17:29 +00:00
abcdefg30
ef9f26a60d Fix MissionObjectives not properly ending the game 2020-12-12 14:59:49 +01:00
abcdefg30
aeaffc0a8e Properly restrict the spectator view in mission maps 2020-12-12 14:59:49 +01:00
Paul Chote
e3084e230e Switch mirror for nsis3 package. 2020-12-12 14:54:50 +01:00
Paul Chote
4c01c772f8 Fix incorrect animation playing when moving infantry stop to attack. 2020-12-12 14:40:13 +01:00
Paul Chote
ed94f7680a Revert "Fix WithInfantryBody wrongly overwriting attack animations"
This reverts commit 1a63cc4a41.
2020-12-12 14:40:13 +01:00
yuantse
408d66cdaf Add Allies 09a 2020-12-12 13:18:08 +01:00
Smittytron
c4a0f2f169 Add Counterstrike mission Sarin Gas 3: Controlled Burn 2020-12-12 13:15:18 +01:00
Paul Chote
26b28d26da Prevent Civilians from wandering onto Tiberium. 2020-12-12 12:54:46 +01:00
Paul Chote
8ded6dafd4 Add AvoidTerrainTypes to ScaredyCat. 2020-12-12 12:54:46 +01:00
Paul Chote
57a94ad667 Add AvoidTerrainTypes to Wanders. 2020-12-12 12:54:46 +01:00
Paul Chote
53933a4d8f Fix restart black screen race condition. 2020-12-11 22:57:57 +01:00
Paul Chote
6606d7dd93 Add DisplayFaction details to the replay metadata. 2020-12-11 18:05:44 +01:00
Paul Chote
7a256dcafa Fix rally point target line exit display. 2020-12-11 17:25:30 +01:00
abcdefg30
7899c52b6d Add an update rule 2020-12-11 17:13:02 +01:00
abcdefg30
919c670502 Update the rules of the default mods 2020-12-11 17:13:02 +01:00
abcdefg30
aac3174efc Rename Stances to Relationships in the yaml api 2020-12-11 17:13:02 +01:00
Paul Chote
a8d3d5c79a Fix Neutral crushing checks. 2020-12-08 20:17:11 +01:00
Paul Chote
7c852d90fb Ignore aircraft when searching for enemy targets. 2020-12-07 23:39:22 +01:00
Paul Chote
269ce9c406 Exclude carryalls from AI squads. 2020-12-07 23:39:22 +01:00
Paul Chote
53d98ec255 Abort squad states that are not able to move. 2020-12-07 23:39:22 +01:00
Paul Chote
7a7cd21578 Fix TD SAM Site facings being reset when damaged while closed. 2020-12-07 01:44:04 +01:00
abcdefg30
2b363e4e1c Fix WithInfantryBody wrongly overwriting attack animations 2020-12-07 00:29:16 +01:00
279 changed files with 6277 additions and 1591 deletions

55
.github/workflows/ci.yaml vendored Normal file
View 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
View 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
View 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
View 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/*

View File

@@ -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
View File

@@ -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",
},
]

View File

@@ -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`.

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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]];
}
}

View File

@@ -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();

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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");

View File

@@ -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)

View File

@@ -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

View File

@@ -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); } }
}
}

View File

@@ -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);

View File

@@ -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())

View File

@@ -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'">

View File

@@ -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);
}
}

View File

@@ -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); }

View File

@@ -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);
});
}

View File

@@ -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)
{

View File

@@ -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

View 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

View File

@@ -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";

View File

@@ -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;

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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 = () =>

View 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;
}
}
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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]; }

View File

@@ -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>

View File

@@ -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; } }

View File

@@ -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; } }

View File

@@ -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; } }

View File

@@ -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; } }

View File

@@ -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; }

View File

@@ -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());

View File

@@ -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)));
}
}
}

View File

@@ -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());

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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";

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);
}

View 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));
}
}
}

View File

@@ -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>();

View File

@@ -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)

View File

@@ -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" />

View File

@@ -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

View File

@@ -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);
});
}
}
}

View File

@@ -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

View File

@@ -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; }

View File

@@ -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)
{

View File

@@ -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); }
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();

View File

@@ -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));

View File

@@ -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));

View File

@@ -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 = "";

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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";

View File

@@ -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>()

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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));

View File

@@ -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)

View File

@@ -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))

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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!";

View File

@@ -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";

View File

@@ -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