Compare commits

..

60 Commits

Author SHA1 Message Date
abcdefg30
5943ecc943 Correct the support dir location in ExtractSettingsDocsCommand 2020-04-26 12:38:42 +01:00
Paul Chote
c13e078ebd Disable debug callbacks on Intel HD 4000. 2020-04-25 21:03:25 +02:00
Paul Chote
426c3d68a1 Expose GL Profile in settings menu. 2020-04-25 21:03:23 +02:00
Paul Chote
5b6b4ab4ee Restore legacy OpenGL 2.1 support. 2020-04-25 21:03:20 +02:00
Paul Chote
835a9e605a Replace PreferGLES settings flag with GLProfile enum. 2020-04-25 21:03:05 +02:00
Paul Chote
390fbe6718 Split GLProfile from GLFeatures. 2020-04-25 21:00:51 +02:00
abcdefg30
1bf3d090d5 Fix a crash when completing objectives in Allies06 out of order 2020-04-23 21:57:22 +01:00
abcdefg30
9e279b9987 Treat transit-only tiles as invalid locations for minelayers 2020-04-18 13:35:27 +01:00
Adam Mitchell
4b6780bc67 Fix units stuck on transit-only when resupplying 2020-04-17 11:10:10 +02:00
abcdefg30
0a9aa488db Adapt the utility commands to import crates as well 2020-04-17 10:49:53 +02:00
abcdefg30
97ecfa548e Remove a TODO about grey nod colors 2020-04-17 10:49:38 +02:00
abcdefg30
82a98ae6e6 Set the wcrate and scrate sequences up 2020-04-17 10:49:25 +02:00
abcdefg30
32b241eae7 Add missing money crates to TD campaign missions 2020-04-17 10:49:09 +02:00
abcdefg30
e8084ade68 Move money crates to a default in the shared campaign rules 2020-04-17 10:48:58 +02:00
Paul Chote
16c2cbf577 Work around and explain color picker conversion issue. 2020-04-17 10:40:32 +02:00
Paul Chote
b51980a9ea Fix map-specific factions remaining selected when changing map. 2020-04-16 16:49:16 +02:00
Paul Chote
631a61e219 Block profiles with revoked keys from joining auth-only servers. 2020-04-16 16:43:33 +02:00
abcdefg30
5ff2546a5d Add more engineers to the wave in nod07
Otherwise buildings will only be damaged
2020-04-16 13:21:53 +02:00
abcdefg30
bf2640308a Fix APC reinforcements in nod07a and b 2020-04-16 13:21:52 +02:00
abcdefg30
63cee6a0e3 Fix a crash in nod07b 2020-04-16 13:21:50 +02:00
Paul Chote
cab47c4975 Disable IP tooltip in skirmish games. 2020-04-15 23:16:07 +02:00
Paul Chote
6385c24875 Switch GeoIP database from MaxMind to IP2Location.
The IP2Location data is licensed under CC BY-SA, which
allows us to distribute the database with releases.
2020-04-15 23:16:05 +02:00
Paul Chote
1d1bafd836 Fix long player locations overrunning the player tooltip. 2020-04-15 23:16:03 +02:00
netnazgul
5cacce3c93 Modify preset colors to not get flagged by color validator 2020-04-14 18:31:35 +02:00
Paul Chote
798c92abeb Fix mine layer desync.
World.FogObscures depends on the local RenderPlayer and should not
be used from simulation code!
2020-04-12 23:06:25 +02:00
abcdefg30
9236a05d53 Fix a yaml error in GDI08a 2020-04-07 20:59:46 +02:00
netnazgul
fc7b3d83e5 Fix tile errors on the map "Pie of Animosity" 2020-04-07 20:53:59 +02:00
Punsho
00ddf82e85 RA Balance patch 2020-03-29 21:51:52 +02:00
Paul Chote
c10ebb455b Fix actors with no footprint leaving stale data when deleted. 2020-03-29 21:08:25 +02:00
Paul Chote
d999a0534b Move selectableActor check inside InputOverridesSelection. 2020-03-29 13:26:05 +02:00
Ivaylo Draganov
02454fb764 Adjust spacing and width in editor category dropdown 2020-03-28 20:41:31 +00:00
abcdefg30
df81857abe Adjust the map visibility panel height 2020-03-28 20:41:23 +00:00
abcdefg30
ae0745b542 Start with randomized wind strength 2020-03-28 19:47:21 +01:00
Paul Chote
89b5e34320 Fix and simplify WeatherOverlay:
- Fix rendering issues
- Track particles in world pixels instead of screen pixels
- Removed un/underused fade in/out support
- Update wind once per tick instead of once per particle
- Make Particle struct readonly
2020-03-28 19:47:19 +01:00
Paul Chote
47cc6eda7e Make the right edge of the airfield transitable. 2020-03-28 19:14:43 +01:00
Paul Chote
6da23ed3d7 Fix minelayers leaking enemy mine positions through the fog. 2020-03-28 18:49:24 +01:00
Paul Chote
3ee4613a98 Fix SpriteEffect updating twice in the first tick. 2020-03-28 17:18:12 +01:00
Paul Chote
616bfbe553 Fix and simplify ScrollPanelWidget thumb rect calculation. 2020-03-26 16:53:09 +01:00
Ivaylo Draganov
ecbcc9c8bb Align lobby bits in the player tab in TD 2020-03-25 13:02:34 +01:00
Paul Chote
068ce9b215 Restore ability to configure RMB orders + RMB panning. 2020-03-25 12:37:43 +01:00
Paul Chote
170bcd500c Remove text caching from CncLoadScreen.
We have repeatedly failed at invalidating these
cached values when things change, so the small perf
win is not worth the hassle.
2020-03-25 12:21:15 +01:00
Paul Chote
6efb40c9a6 Fix detection circle line rendering. 2020-03-24 20:59:58 +01:00
Ivaylo Draganov
3c92c2d789 Adjust the stroke of the muted indicator glyph 2020-03-24 16:57:48 +01:00
Paul Chote
f823d20a86 Replace deprecated native OpenAL with OpenAL Soft on macOS. 2020-03-23 11:17:46 +01:00
Paul Chote
ca4b97cd03 Remove invalid caching from GCOT. 2020-03-23 11:17:08 +01:00
Matthias Mailänder
8b5e322891 Remove .lua scripts from the .NET solution file. 2020-03-21 21:24:03 +01:00
unknown
70e1c4bb0c Add gdi09ea 2020-03-21 21:11:14 +01:00
abcdefg30
8690ad4e19 Add support for destroying enemy carryalls 2020-03-21 11:00:39 +01:00
abcdefg30
a01233c3df Add support for an announcement function for carryall reinforcements 2020-03-21 11:00:14 +01:00
abcdefg30
d0d55b3fc1 Add Ordos06a 2020-03-21 10:59:59 +01:00
abcdefg30
2a26a07ce6 Let VS2019 remove a duplicate line from the solution 2020-03-21 10:59:45 +01:00
abcdefg30
65e088204f Fix the settings tooltip container being overwritten ingame 2020-03-20 16:06:46 +01:00
Michael Silber
77e38ceea9 Add gdi08a 2020-03-17 19:04:53 +01:00
Paul Chote
989d1475b9 Update macOS launcher to fix "View Logs" button. 2020-03-16 19:17:12 +00:00
Paul Chote
580a96cda4 Cache CandidateMovementCells within the same tick. 2020-03-12 17:07:45 +01:00
Paul Chote
5a8964aabe Fix infantry switching subcells and blocking eachother while moving. 2020-03-11 15:40:58 +01:00
Paul Chote
c9dcf305c9 Fix FreeSubCell ignoring preferred subcell requests. 2020-03-11 15:40:56 +01:00
Paul Chote
5489d6909c Fix pathing across transit-only cells. 2020-03-11 15:40:54 +01:00
Paul Chote
85412a6d98 Fix variable naming in Locomotor. 2020-03-11 15:40:53 +01:00
abcdefg30
b69806123a Fix aircraft not taking off properly 2020-03-08 17:19:56 +01:00
1738 changed files with 52744 additions and 73882 deletions

View File

@@ -6,101 +6,13 @@ charset=utf-8
[*]
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
; 4-column tab indentation and .NET coding conventions
; 4-column tab indentation
[*.cs]
indent_style = tab
indent_size = 4
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_prefer_braces = when_multiline:suggestion
csharp_using_directive_placement = outside_namespace:suggestion
csharp_new_line_before_open_brace = all
csharp_space_around_binary_operators = before_and_after
#### Naming styles ####
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.pascal_case.capitalization = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.const_private_field.applicable_kinds = field
dotnet_naming_symbols.const_private_field.required_modifiers = const
dotnet_naming_symbols.const_private_field.applicable_accessibilities = private
dotnet_naming_symbols.internal_field.applicable_kinds = field
dotnet_naming_symbols.internal_field.applicable_accessibilities = internal
dotnet_naming_symbols.static_private_or_internal_field.required_modifiers = static
dotnet_naming_symbols.static_private_or_internal_field.applicable_accessibilities = internal, private
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
# Naming rules
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.severity = none
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.symbols = static_private_or_internal_field
dotnet_naming_rule.static_private_or_internal_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.const_private_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.const_private_field_should_be_pascal_case.symbols = const_private_field
dotnet_naming_rule.const_private_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.symbols = internal_field
dotnet_naming_rule.const_private_or_internal_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
# Naming rules
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#Formatting - wrapping options
#leave code block on single line
csharp_preserve_single_line_blocks = true
#leave statements and member declarations on the same line
csharp_preserve_single_line_statements = true
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
; 4-column tab indentation
[*.yaml]
indent_style = tab
indent_size = 4
indent_size = 4

View File

@@ -1,14 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Frequently Asked Questions
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
about: Explanations for common problems and questions.
- name: OpenRA Forum
url: https://forum.openra.net/
about: Please ask questions about modding here.
about: "Please ask questions about modding here."
- name: OpenRA Discord server
url: https://discord.openra.net/
about: Join the community Discord server for community discussion and support.
about: "Join the community Discord server for community discussion and support."
- name: OpenRA IRC Channel
url: https://webchat.freenode.net/#openra
about: Join our development IRC channel on freenode for discussion of development topics.
about: "Join our development IRC channel on freenode for discussion of development topics."

View File

@@ -1,6 +1,6 @@
---
name: Crash report
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
about: Report a game crash.
title: My game crashed
labels: Crash
assignees: ''

View File

@@ -1,49 +0,0 @@
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
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make test
windows:
name: Windows (Framework 4.7)
runs-on: windows-2019
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Check Code
shell: powershell
run: |
dotnet nuget locals all --clear
.\make.ps1 check
Invoke-Expression "$home\.nuget\packages\nunit.consolerunner\3.11.1\tools\nunit3-console.exe --noresult bin/OpenRA.Test.dll"
- 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

View File

@@ -1,107 +0,0 @@
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

View File

@@ -1,86 +0,0 @@
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
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
- name: Publish Windows Installer
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: win
ITCH_GAME: openra
ITCH_USER: openra-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

View File

@@ -1,114 +0,0 @@
name: Release Packaging
on:
push:
tags:
- 'release-*'
- 'playtest-*'
- 'devtest-*'
jobs:
source:
name: Source Tarball
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 Source
run: |
mkdir -p build/source
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
- name: Upload Packages
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/source/*
linux:
name: Linux AppImages
runs-on: ubuntu-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: 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: Prepare Environment
run: |
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
sudo apt-get update
sudo apt-get install nsis
- 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/*

86
.travis.yml Normal file
View File

@@ -0,0 +1,86 @@
# Travis-CI Build for OpenRA
# see travis-ci.org for details
language: csharp
mono: 6.4.0
jobs:
include:
- os: linux
dist: xenial
- os: osx
if: tag IS present
osx_image: xcode10
cache:
directories:
- thirdparty/download
addons:
apt:
packages:
- lua5.1
- dpkg
- zsync
- markdown
# Environment variables
env:
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
# Fetch dependencies
# Run the build script
# Check source code with StyleCop
# call OpenRA to check for YAML errors
# Run the NUnit tests
script:
- travis_retry make all-dependencies
- make all
- test "$TRAVIS_OS_NAME" == "linux" && make check || echo "Skipping check"
- test "$TRAVIS_OS_NAME" == "linux" && make check-scripts || echo "Skipping scripts check"
- test "$TRAVIS_OS_NAME" == "linux" && make test || echo "Skipping tests"
- test "$TRAVIS_OS_NAME" == "linux" && make nunit || echo "Skipping nunit tests"
# Only watch the development branch and tagged release.
branches:
only:
- /^release-.*$/
- /^playtest-.*$/
- /^pkgtest-.*$/
- /^prep-.*$/
- bleed
# Notify developers when build passed/failed.
notifications:
irc:
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;
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
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/
deploy:
provider: releases
api_key:
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
file_glob: true
file: build/*
skip_cleanup: true
on:
all_branches: true
tags: true
repo: OpenRA/OpenRA

View File

@@ -1,7 +0,0 @@
{
"recommendations": [
"ms-dotnettools.csharp",
"EditorConfig.EditorConfig",
"ms-vscode.mono-debug"
]
}

61
.vscode/launch.json vendored
View File

@@ -1,61 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch (TD)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.exe",
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (RA)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.exe",
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (D2k)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.exe",
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (TS)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"request": "launch",
"program": "${workspaceRoot}/bin/OpenRA.exe",
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
]
}

View File

@@ -1,3 +0,0 @@
{
"omnisharp.enableRoslynAnalyzers": true
}

13
.vscode/tasks.json vendored
View File

@@ -1,13 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "make",
"args": ["all"],
"windows": {
"command": "make.cmd"
}
}
]
}

12
AUTHORS
View File

@@ -40,7 +40,6 @@ Also thanks to:
* Biofreak
* Braxton Williams (Buddytex)
* Brendan Gluth (Mechanical_Man)
* Brent Gardner (bggardner)
* Bryan Wilbur
* Bugra Cuhadaroglu (BugraC)
* Chris Cameron (Vesuvian)
@@ -138,19 +137,16 @@ Also thanks to:
* Sebastien Kerguen (xanax)
* Shawn Collins (UberWaffe)
* Simon Verbeke (Saticmotion)
* Stuart McHattie (SDJMcHattie)
* Taryn Hill (Phrohdoh)
* Teemu Nieminen (Temeez)
* Tim Mylemans (gecko)
* Tirili
* Tomas Einarsson (Mesacer)
* Tom van Leth (tovl)
* Trevor Nichols (ocdi)
* Tristan Keating (Kilkakon)
* Tristan Mühlbacher (MicroBit)
* UnknownProgrammer
* Vladimir Komarov (VrKomarov)
* Wojciech Walaszek (Voidwalker)
* Wuschel
Using GNU FreeFont distributed under the GNU GPL
@@ -183,14 +179,6 @@ Krueger and distributed under the GNU GPL terms.
Using rix0rrr.BeaconLib developed by Rico Huijbers
distributed under MIT License.
Using DiscordRichPresence developed by Lachee
distributed under MIT License.
Using Json.NET developed by James Newton-King
distributed under MIT License.
Using ANGLE distributed under the BS3 3-Clause license.
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.

65
ConvertFrom-Markdown.ps1 Normal file

File diff suppressed because one or more lines are too long

View File

@@ -8,9 +8,15 @@ 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 Framework 4.6.1 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net461) (or via Visual Studio 2017)
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
Type `make dependencies` in a command terminal to download pre-compiled native libraries for:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [zlib](http://gnuwin32.sourceforge.net/packages/zlib.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with MSBuild or use the Makefile analogue command `make all` scripted in PowerShell syntax.
@@ -19,19 +25,13 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
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 5.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
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.
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
To compile OpenRA, run `make all` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
Arch Linux
----------
@@ -89,23 +89,24 @@ sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
----------------------------------------------------
The EPEL repository is required in order for the following command to run properly.
The EPEL repository is required in order for the following command to run properly.
```
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
```
macOS
OSX
=====
Before compiling OpenRA you must install the following dependencies:
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
* [Mono >= 5.4](https://www.mono-project.com/download/stable/#download-mac)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
Use `make dependencies` to download pre-compiled native libraries for:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
To compile OpenRA, run `make` from the command line.
Run with `./launch-game.sh`.

398
Makefile
View File

@@ -3,47 +3,55 @@
# to compile, run:
# make [DEBUG=true]
#
# to compile using system libraries for native dependencies, run:
# make [DEBUG=true] TARGETPLATFORM=unix-generic
#
# to check unit tests (requires NUnit version >= 2.6), run:
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
# to check the official mods for erroneous yaml files, run:
# make test
#
# to check the engine and official mod dlls for code style violations, run:
# to check the official mod dlls for StyleCop violations, run:
# make check
#
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
# to install, run:
# make [prefix=/foo] [bindir=/bar/bin] install
#
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
# using system libraries for native dependencies, run:
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
# to install Linux startup scripts, desktop files and icons:
# make install-linux-shortcuts [DEBUG=false]
#
# to install Linux startup scripts, desktop files, icons, and MIME metadata
# make install-linux-shortcuts
# to install the engine and common mod files (omitting the default mods):
# make install-engine
# make install-common-mod-files
#
# to install Linux AppStream metadata
# make install-linux-appdata
# to uninstall, run:
# make uninstall
#
# for help, run:
# make help
#
# to start the game, run:
# openra
############################## TOOLCHAIN ###############################
#
# List of .NET assemblies that we can guarantee exist
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
# OpenRA.Game.dll is a harmless false positive that we can ignore
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll
NUNIT_LIBS_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
######################### UTILITIES/SETTINGS ###########################
#
# Install locations for local installs and downstream packaging
# install locations
prefix ?= /usr/local
datarootdir ?= $(prefix)/share
datadir ?= $(datarootdir)
@@ -52,132 +60,322 @@ bindir ?= $(prefix)/bin
libdir ?= $(prefix)/lib
gameinstalldir ?= $(libdir)/openra
# Toolchain
CWD = $(shell pwd)
MSBUILD = msbuild -verbosity:m -nologo
MONO = mono
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
# install tools
RM = rm
RM_R = $(RM) -r
RM_F = $(RM) -f
RM_RF = $(RM) -rf
CP = cp
CP_R = $(CP) -r
INSTALL = install
INSTALL_DIR = $(INSTALL) -d
INSTALL_PROGRAM = $(INSTALL) -m755
INSTALL_DATA = $(INSTALL) -m644
# Only for use in target version:
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
# Toolchain
MSBUILD = msbuild -verbosity:m -nologo
# Detect target platform for dependencies if not given by the user
ifndef TARGETPLATFORM
# Enable 32 bit builds while generating the windows installer
WIN32 = false
# program targets
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
# dependencies
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Darwin)
TARGETPLATFORM = osx-x64
os-dependencies = osx-dependencies
else
ifeq ($(UNAME_M),x86_64)
TARGETPLATFORM = linux-x64
else
TARGETPLATFORM = unix-generic
os-dependencies = linux-dependencies
endif
endif
endif
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
##################### 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)
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@./fetch-geoip.sh
clean:
@-$(RM_RF) ./bin ./*/bin ./*/obj
@$(MSBUILD) -t:Clean
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM)
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@echo
@echo "Checking runtime assemblies..."
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
@echo
@echo "Checking for explicit interface violations..."
@$(OPENRA_UTILITY) all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
test: all
check: dependencies
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -p:Configuration=Debug
@echo
@echo "Checking runtime assemblies..."
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
@echo
@echo "Checking for explicit interface violations..."
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
nunit: core
@echo
@echo "Checking unit tests..."
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
echo 'nunit[3|2]-console not found!'; \
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
echo 'See "make help".'; \
exit 1; \
fi
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
echo 'NUnit version >= 2.6 required'>&2; \
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
echo 'See "make help".'; \
exit 1; \
fi
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
test: core
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@$(OPENRA_UTILITY) ts --check-yaml
@mono --debug OpenRA.Utility.exe ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@$(OPENRA_UTILITY) d2k --check-yaml
@mono --debug OpenRA.Utility.exe d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@$(OPENRA_UTILITY) cnc --check-yaml
@mono --debug OpenRA.Utility.exe cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@$(OPENRA_UTILITY) ra --check-yaml
@mono --debug OpenRA.Utility.exe ra --check-yaml
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
########################## MAKE/INSTALL RULES ##########################
#
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
ifeq ($(VERSION),)
$(error Unable to determine new version (requires git or override of variable VERSION))
all: dependencies core
core:
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
else
@$(MSBUILD) -t:build -p:Configuration=Release
endif
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
@./fetch-geoip.sh
install:
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) True True True'
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
clean:
@ $(MSBUILD) -t:clean
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_RF) ./*/bin ./*/obj
@-$(RM_RF) ./thirdparty/download
install-linux-shortcuts:
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
distclean: clean
cli-dependencies:
@./thirdparty/fetch-thirdparty-deps.sh
@ $(CP_R) thirdparty/download/*.dll .
@ $(CP_R) thirdparty/download/*.dll.config .
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
linux-dependencies: cli-dependencies linux-native-dependencies
linux-native-dependencies:
@./thirdparty/configure-native-deps.sh
windows-dependencies: cli-dependencies
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
else
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
endif
osx-dependencies: cli-dependencies
@./thirdparty/fetch-thirdparty-deps-osx.sh
@ $(CP_R) thirdparty/download/osx/*.dylib .
@ $(CP_R) thirdparty/download/osx/*.dll.config .
dependencies: $(os-dependencies)
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
@echo "$(VERSION)" > VERSION
@for i in $? ; do \
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
rm $${i}.tmp; \
done
install: dependencies core install-core
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
install-engine:
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
@$(INSTALL_DATA) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP "$(DATA_INSTALL_DIR)/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
install-default-mods:
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
install-core: install-engine install-common-mod-files install-default-mods
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-icons:
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
$(INSTALL_DATA) packaging/linux/icons/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(INSTALL_DATA) packaging/linux/icons/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(INSTALL_DATA) packaging/linux/icons/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
$(INSTALL_DATA) packaging/linux/icons/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
$(INSTALL_DATA) packaging/linux/icons/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
install-linux-desktop:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
install-linux-mime:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
install-linux-appdata:
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
install-man-page:
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
@-$(RM) openra.6
install-linux-scripts:
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
else
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
endif
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
uninstall:
@-$(RM_R) "$(DATA_INSTALL_DIR)"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
help:
@echo 'to compile, run:'
@echo ' make [DEBUG=true]'
@echo ' make [DEBUG=false]'
@echo
@echo 'to compile using system libraries for native dependencies, run:'
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
@echo
@echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make test'
@echo
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make test'
@echo 'to install, run:'
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
@echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
@echo ' make [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
@echo 'to install Linux startup scripts, desktop files and icons'
@echo ' make install-linux-shortcuts [DEBUG=false]'
@echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
@echo 'using system libraries for native dependencies, run:'
@echo ' make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
@echo 'to uninstall, run:'
@echo ' make uninstall'
@echo
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
@echo ' make install-linux-shortcuts'
@echo
@echo 'to install Linux AppStream metadata'
@echo ' make install-linux-appdata'
@echo 'to start the game, run:'
@echo ' openra'
########################### MAKEFILE SETTINGS ##########################
#
@@ -185,4 +383,4 @@ help:
.SUFFIXES:
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Activities
public readonly Color Color;
public readonly Sprite Tile;
public TargetLineNode(in Target target, Color color, Sprite tile = null)
public TargetLineNode(Target target, Color color, Sprite tile = null)
{
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
// if "yield break" in TargetLineNode(Actor self) is not feasible.
@@ -185,7 +185,8 @@ namespace OpenRA.Activities
/// </summary>
internal void OnActorDisposeOuter(Actor self)
{
ChildActivity?.OnActorDisposeOuter(self);
if (ChildActivity != null)
ChildActivity.OnActorDisposeOuter(self);
OnActorDispose(self);
}
@@ -198,7 +199,8 @@ namespace OpenRA.Activities
if (!IsInterruptible)
return;
ChildActivity?.Cancel(self);
if (ChildActivity != null)
ChildActivity.Cancel(self);
// Directly mark activities that are queued and therefore didn't run yet as done
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
@@ -241,9 +243,11 @@ namespace OpenRA.Activities
Console.WriteLine(GetType().ToString().Split('.').Last());
ChildActivity?.PrintActivityTree(self, origin, level + 1);
if (ChildActivity != null)
ChildActivity.PrintActivityTree(self, origin, level + 1);
NextActivity?.PrintActivityTree(self, origin, level);
if (NextActivity != null)
NextActivity.PrintActivityTree(self, origin, level);
}
}

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Activities
public override bool Tick(Actor self)
{
a?.Invoke();
if (a != null) a();
return true;
}
}

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
@@ -69,40 +68,16 @@ namespace OpenRA
{
get
{
return facing != null ? facing.Orientation : WRot.None;
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
var facingValue = facing != null ? facing.Facing : 0;
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
}
}
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class ConditionState
{
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>();
}
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
int nextConditionToken = 1;
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
internal SyncHash[] SyncHashes { get; private set; }
readonly IFacing facing;
readonly IHealth health;
readonly IResolveOrder[] resolveOrders;
readonly IRenderModifier[] renderModifiers;
readonly IRender[] renders;
readonly IMouseBounds[] mouseBounds;
@@ -110,28 +85,18 @@ namespace OpenRA
readonly IDefaultVisibility defaultVisibility;
readonly INotifyBecomingIdle[] becomingIdles;
readonly INotifyIdle[] tickIdles;
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
readonly ITargetablePositions[] targetablePositions;
WPos[] staticTargetablePositions;
bool created;
bool setStaticTargetablePositions;
internal Actor(World world, string name, TypeDictionary initDict)
{
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
.FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null)
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
var init = new ActorInitializer(this, initDict);
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
World = world;
ActorID = world.NextAID();
var ownerInit = init.GetOrDefault<OwnerInit>();
if (ownerInit != null)
Owner = ownerInit.Value(world);
if (initDict.Contains<OwnerInit>())
Owner = init.Get<OwnerInit, Player>();
if (name != null)
{
@@ -141,102 +106,51 @@ namespace OpenRA
throw new NotImplementedException("No rules definition for unit " + name);
Info = world.Map.Rules.Actors[name];
IPositionable positionable = null;
var resolveOrdersList = new List<IResolveOrder>();
var renderModifiersList = new List<IRenderModifier>();
var rendersList = new List<IRender>();
var mouseBoundsList = new List<IMouseBounds>();
var visibilityModifiersList = new List<IVisibilityModifier>();
var becomingIdlesList = new List<INotifyBecomingIdle>();
var tickIdlesList = new List<INotifyIdle>();
var targetablesList = new List<ITargetable>();
var targetablePositionsList = new List<ITargetablePositions>();
var syncHashesList = new List<SyncHash>();
foreach (var traitInfo in Info.TraitsInConstructOrder())
foreach (var trait in Info.TraitsInConstructOrder())
{
var trait = traitInfo.Create(init);
AddTrait(trait);
AddTrait(trait.Create(init));
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
// rules for spacing in order to keep these assignments compact and readable.
{ if (trait is IPositionable t) positionable = t; }
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
{ if (trait is IFacing t) facing = t; }
{ if (trait is IHealth t) health = t; }
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
{ if (trait is IRender t) rendersList.Add(t); }
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
{ if (trait is ITargetable t) targetablesList.Add(t); }
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
// Some traits rely on properties provided by IOccupySpace in their initialization,
// so we must ready it now, we cannot wait until all traits have finished construction.
if (trait is IOccupySpaceInfo)
OccupiesSpace = Trait<IOccupySpace>();
}
resolveOrders = resolveOrdersList.ToArray();
renderModifiers = renderModifiersList.ToArray();
renders = rendersList.ToArray();
mouseBounds = mouseBoundsList.ToArray();
visibilityModifiers = visibilityModifiersList.ToArray();
becomingIdles = becomingIdlesList.ToArray();
tickIdles = tickIdlesList.ToArray();
Targetables = targetablesList.ToArray();
var targetablePositions = targetablePositionsList.ToArray();
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
SyncHashes = syncHashesList.ToArray();
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
}
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
facing = TraitOrDefault<IFacing>();
health = TraitOrDefault<IHealth>();
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
renders = TraitsImplementing<IRender>().ToArray();
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
defaultVisibility = Trait<IDefaultVisibility>();
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
Targetables = TraitsImplementing<ITargetable>().ToArray();
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
world.AddFrameEndTask(w =>
{
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
});
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
}
internal void Initialize(bool addToWorld = true)
internal void Created()
{
created = true;
// Make sure traits are usable for condition notifiers
foreach (var t in TraitsImplementing<INotifyCreated>())
t.Created(this);
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
foreach (var provider in TraitsImplementing<IObservesVariables>())
{
foreach (var variableUser in provider.GetVariableObservers())
{
allObserverNotifiers.Add(variableUser.Notifier);
foreach (var variable in variableUser.Variables)
{
var cs = conditionStates.GetOrAdd(variable);
cs.Notifiers.Add(variableUser.Notifier);
// Initialize conditions that have not yet been granted to 0
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
conditionCache[variable] = cs.Tokens.Count;
}
}
}
// Update all traits with their initial condition state
foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache);
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (setStaticTargetablePositions)
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
// TODO: Other traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead.
// The initial activity should run before any activities queued by INotifyCreated.Created
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
ICreationActivity creationActivity = null;
@@ -257,9 +171,6 @@ namespace OpenRA
activity.Queue(CurrentActivity);
CurrentActivity = activity;
}
if (addToWorld)
World.Add(this);
}
public void Tick()
@@ -322,7 +233,7 @@ namespace OpenRA
yield return r;
}
public Polygon MouseBounds(WorldRenderer wr)
public Rectangle MouseBounds(WorldRenderer wr)
{
foreach (var mb in mouseBounds)
{
@@ -331,7 +242,7 @@ namespace OpenRA
return bounds;
}
return Polygon.Empty;
return Rectangle.Empty;
}
public void QueueActivity(bool queued, Activity nextActivity)
@@ -355,7 +266,8 @@ namespace OpenRA
public void CancelActivity()
{
CurrentActivity?.Cancel(this);
if (CurrentActivity != null)
CurrentActivity.Cancel(this);
}
public override int GetHashCode()
@@ -407,7 +319,8 @@ namespace OpenRA
{
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
// This should be done before the FrameEndTask to avoid dependency issues.
CurrentActivity?.OnActorDisposeOuter(this);
if (CurrentActivity != null)
CurrentActivity.OnActorDisposeOuter(this);
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
WillDispose = true;
@@ -426,16 +339,11 @@ namespace OpenRA
World.TraitDict.RemoveActor(this);
Disposed = true;
luaInterface?.Value.OnActorDestroyed();
if (luaInterface != null)
luaInterface.Value.OnActorDestroyed();
});
}
public void ResolveOrder(Order order)
{
foreach (var r in resolveOrders)
r.ResolveOrder(this, order);
}
// TODO: move elsewhere.
public void ChangeOwner(Player newOwner)
{
@@ -539,71 +447,13 @@ namespace OpenRA
if (staticTargetablePositions != null)
return staticTargetablePositions;
if (enabledTargetablePositions.Any())
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
if (enabledTargetablePositionTraits.Any())
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
return new[] { CenterPosition };
}
#region Conditions
void UpdateConditionState(string condition, int token, bool isRevoke)
{
ConditionState conditionState = conditionStates.GetOrAdd(condition);
if (isRevoke)
conditionState.Tokens.Remove(token);
else
conditionState.Tokens.Add(token);
conditionCache[condition] = conditionState.Tokens.Count;
// Conditions may be granted or revoked before the state is initialized.
// These notifications will be processed after INotifyCreated.Created.
if (created)
foreach (var notify in conditionState.Notifiers)
notify(this, readOnlyConditionCache);
}
/// <summary>
/// Grants a specified condition if it is valid.
/// Otherwise, just returns InvalidConditionToken.
/// </summary>
/// <returns>The token that is used to revoke this condition.</returns>
public int GrantCondition(string condition)
{
if (string.IsNullOrEmpty(condition))
return InvalidConditionToken;
var token = nextConditionToken++;
conditionTokens.Add(token, condition);
UpdateConditionState(condition, token, false);
return token;
}
/// <summary>
/// Revokes a previously granted condition.
/// </summary>
/// <param name="token">The token ID returned by GrantCondition.</param>
/// <returns>The invalid token ID.</returns>
public int RevokeCondition(int token)
{
if (!conditionTokens.TryGetValue(token, out var condition))
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
conditionTokens.Remove(token);
UpdateConditionState(condition, token, true);
return InvalidConditionToken;
}
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
public bool TokenValid(int token)
{
return conditionTokens.ContainsKey(token);
}
#endregion
#region Scripting interface
Lazy<ScriptActorInterface> luaInterface;
@@ -621,7 +471,8 @@ namespace OpenRA
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
Actor a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
return false;
return a == b;

View File

@@ -89,7 +89,9 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
CPos a;
CVec b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
@@ -97,18 +99,21 @@ namespace OpenRA
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CPos a;
var rightType = right.WrappedClrType();
if (!left.TryGetClrValue(out CPos a))
if (!left.TryGetClrValue(out a))
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
if (rightType == typeof(CPos))
{
right.TryGetClrValue(out CPos b);
CPos b;
right.TryGetClrValue(out b);
return new LuaCustomClrObject(a - b);
}
else if (rightType == typeof(CVec))
{
right.TryGetClrValue(out CVec b);
CVec b;
right.TryGetClrValue(out b);
return new LuaCustomClrObject(a - b);
}
@@ -117,7 +122,8 @@ namespace OpenRA
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CPos b))
CPos a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
return false;
return a == b;

View File

@@ -75,7 +75,8 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
CVec a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
@@ -83,7 +84,8 @@ namespace OpenRA
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
CVec a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a - b);
@@ -96,7 +98,8 @@ namespace OpenRA
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
CVec a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
return false;
return a == b;

View File

@@ -1,20 +0,0 @@
#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 OpenRA.Primitives;
namespace OpenRA
{
public class DefaultPlayer : IGlobalModData
{
public readonly Color Color = Color.FromAhsl(0, 0, 238);
}
}

View File

@@ -90,7 +90,8 @@ namespace OpenRA
public void CancelAsync()
{
lock (syncObject)
wc?.CancelAsync();
if (wc != null)
wc.CancelAsync();
}
}
}

View File

@@ -121,7 +121,7 @@ namespace OpenRA
mods[key] = mod;
}
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
{
if (mod.Metadata.Hidden)
return;
@@ -133,7 +133,7 @@ namespace OpenRA
new MiniYamlNode("Version", mod.Metadata.Version),
new MiniYamlNode("Title", mod.Metadata.Title),
new MiniYamlNode("LaunchPath", launchPath),
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
}));
using (var stream = mod.Package.GetStream("icon.png"))

View File

@@ -80,7 +80,7 @@ namespace OpenRA
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
{
return Math.Sign((v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y));
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
}
public static bool PolygonContains(this int2[] polygon, int2 p)
@@ -101,16 +101,6 @@ namespace OpenRA
return windingNumber != 0;
}
public static bool LinesIntersect(int2 a, int2 b, int2 c, int2 d)
{
// If line segments AB and CD intersect:
// - the triangles ACD and BCD must have opposite sense (clockwise or anticlockwise)
// - the triangles CAB and DAB must have opposite sense
// Segments intersect if the orientation (clockwise or anticlockwise) of the two points in each line segment are opposite with respect to the other
// Assumes that lines are not colinear
return WindingDirectionTest(c, d, a) != WindingDirectionTest(c, d, b) && WindingDirectionTest(a, b, c) != WindingDirectionTest(a, b, d);
}
public static bool HasModifier(this Modifiers k, Modifiers mod)
{
// PERF: Enum.HasFlag is slower and requires allocations.
@@ -120,19 +110,13 @@ namespace OpenRA
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
where V : new()
{
return d.GetOrAdd(k, new V());
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
{
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = v);
return ret;
return d.GetOrAdd(k, _ => new V());
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)
{
if (!d.TryGetValue(k, out var ret))
V ret;
if (!d.TryGetValue(k, out ret))
d.Add(k, ret = createFn(k));
return ret;
}
@@ -359,7 +343,8 @@ namespace OpenRA
public static int IntegerDivisionRoundingAwayFromZero(int dividend, int divisor)
{
var quotient = Math.DivRem(dividend, divisor, out var remainder);
int remainder;
var quotient = Math.DivRem(dividend, divisor, out remainder);
if (remainder == 0)
return quotient;
return quotient + (Math.Sign(dividend) == Math.Sign(divisor) ? 1 : -1);
@@ -410,7 +395,8 @@ namespace OpenRA
// Check for a key conflict:
if (d.ContainsKey(key))
{
if (!dupKeys.TryGetValue(key, out var dupKeyMessages))
List<string> dupKeyMessages;
if (!dupKeys.TryGetValue(key, out dupKeyMessages))
{
// Log the initial conflicting value already inserted:
dupKeyMessages = new List<string>();

View File

@@ -18,6 +18,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
@@ -121,7 +122,8 @@ namespace OpenRA
{
ret = null;
if (!md.TryGetValue(yamlName, out var yaml))
MiniYaml yaml;
if (!md.TryGetValue(yamlName, out yaml))
return false;
ret = GetValue(field.Name, field.FieldType, yaml, field);
@@ -179,36 +181,42 @@ namespace OpenRA
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
{
var value = yaml.Value?.Trim();
var value = yaml.Value;
if (value != null) value = value.Trim();
if (fieldType == typeof(int))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
int res;
if (Exts.TryParseIntegerInvariant(value, out res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(ushort))
{
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
ushort res;
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
if (fieldType == typeof(long))
{
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
long res;
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float))
{
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
float res;
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
return res * (value.Contains('%') ? 0.01f : 1f);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(decimal))
{
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
decimal res;
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
return res * (value.Contains('%') ? 0.01m : 1m);
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -220,14 +228,16 @@ namespace OpenRA
}
else if (fieldType == typeof(Color))
{
if (value != null && Color.TryParse(value, out var color))
Color color;
if (value != null && Color.TryParse(value, out color))
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(Hotkey))
{
if (Hotkey.TryParse(value, out var res))
Hotkey res;
if (Hotkey.TryParse(value, out res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
@@ -238,7 +248,8 @@ namespace OpenRA
}
else if (fieldType == typeof(WDist))
{
if (WDist.TryParse(value, out var res))
WDist res;
if (WDist.TryParse(value, out res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
@@ -250,7 +261,8 @@ namespace OpenRA
var parts = value.Split(',');
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
WDist rx, ry, rz;
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
return new WVec(rx, ry, rz);
}
}
@@ -270,7 +282,8 @@ namespace OpenRA
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
WDist rx, ry, rz;
if (WDist.TryParse(parts[3 * i], out rx) && WDist.TryParse(parts[3 * i + 1], out ry) && WDist.TryParse(parts[3 * i + 2], out rz))
vecs[i] = new WVec(rx, ry, rz);
}
@@ -286,7 +299,8 @@ namespace OpenRA
var parts = value.Split(',');
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
WDist rx, ry, rz;
if (WDist.TryParse(parts[0], out rx) && WDist.TryParse(parts[1], out ry) && WDist.TryParse(parts[2], out rz))
return new WPos(rx, ry, rz);
}
}
@@ -295,7 +309,8 @@ namespace OpenRA
}
else if (fieldType == typeof(WAngle))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
int res;
if (Exts.TryParseIntegerInvariant(value, out res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -306,7 +321,8 @@ namespace OpenRA
var parts = value.Split(',');
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
int rr, rp, ry;
if (Exts.TryParseIntegerInvariant(parts[0], out rr) && Exts.TryParseIntegerInvariant(parts[1], out rp) && Exts.TryParseIntegerInvariant(parts[2], out ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
@@ -345,7 +361,8 @@ namespace OpenRA
var vecs = new CVec[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
int rx, ry;
if (int.TryParse(parts[2 * i], out rx) && int.TryParse(parts[2 * i + 1], out ry))
vecs[i] = new CVec(rx, ry);
}
@@ -399,7 +416,8 @@ namespace OpenRA
}
else if (fieldType == typeof(bool))
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
bool result;
if (bool.TryParse(value.ToLowerInvariant(), out result))
return result;
return InvalidValueAction(value, fieldType, fieldName);
@@ -435,7 +453,7 @@ namespace OpenRA
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
}
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(HashSet<>))
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
@@ -492,7 +510,8 @@ namespace OpenRA
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float xx = 0;
float yy = 0;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
float res;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
@@ -506,11 +525,13 @@ namespace OpenRA
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
float x = 0;
float y = 0;
float z = 0;
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out y);
// z component is optional for compatibility with older float2 definitions
float z = 0;
if (parts.Length > 2)
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
@@ -546,16 +567,14 @@ namespace OpenRA
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
else if (fieldType == typeof(DateTime))
{
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
DateTime dt;
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out dt))
return dt;
return InvalidValueAction(value, fieldType, fieldName);
}
@@ -705,7 +724,8 @@ namespace OpenRA
if (translations == null)
return key;
if (!translations.TryGetValue(key, out var value))
string value;
if (!translations.TryGetValue(key, out value))
return key;
return value;

View File

@@ -15,6 +15,7 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA
@@ -94,7 +95,7 @@ namespace OpenRA
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
{
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}

View File

@@ -63,16 +63,19 @@ namespace OpenRA.FileSystem
{
// Raw directories are the easiest and one of the most common cases, so try these first
var resolvedPath = Platform.ResolvePath(filename);
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
return new Folder(resolvedPath);
// Children of another package require special handling
if (TryGetPackageContaining(filename, out var parent, out var subPath))
IReadOnlyPackage parent;
string subPath = null;
if (TryGetPackageContaining(filename, out parent, out subPath))
return parent.OpenPackage(subPath, this);
// Try and open it normally
IReadOnlyPackage package;
var stream = Open(filename);
if (TryParsePackage(stream, filename, out var package))
if (TryParsePackage(stream, filename, out package))
return package;
// No package loaders took ownership of the stream, so clean it up
@@ -94,7 +97,8 @@ namespace OpenRA.FileSystem
{
name = name.Substring(1);
if (!installedMods.TryGetValue(name, out var mod))
Manifest mod;
if (!installedMods.TryGetValue(name, out mod))
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
package = mod.Package;
@@ -118,7 +122,8 @@ namespace OpenRA.FileSystem
public void Mount(IReadOnlyPackage package, string explicitName = null)
{
if (mountedPackages.TryGetValue(package, out var mountCount))
var mountCount = 0;
if (mountedPackages.TryGetValue(package, out mountCount))
{
// Package is already mounted
// Increment the mount count and bump up the file loading priority
@@ -144,7 +149,8 @@ namespace OpenRA.FileSystem
public bool Unmount(IReadOnlyPackage package)
{
if (!mountedPackages.TryGetValue(package, out var mountCount))
var mountCount = 0;
if (!mountedPackages.TryGetValue(package, out mountCount))
return false;
if (--mountCount <= 0)
@@ -197,12 +203,16 @@ namespace OpenRA.FileSystem
var package = fileIndex[filename]
.LastOrDefault(x => x.Contains(filename));
return package?.GetStream(filename);
if (package != null)
return package.GetStream(filename);
return null;
}
public Stream Open(string filename)
{
if (!TryOpen(filename, out var s))
Stream s;
if (!TryOpen(filename, out s))
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
return s;
@@ -228,7 +238,8 @@ namespace OpenRA.FileSystem
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0)
{
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
IReadOnlyPackage explicitPackage;
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
{
s = explicitPackage.GetStream(filename.Substring(explicitSplit + 1));
if (s != null)
@@ -263,9 +274,12 @@ namespace OpenRA.FileSystem
{
var explicitSplit = filename.IndexOf('|');
if (explicitSplit > 0)
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
{
IReadOnlyPackage explicitPackage;
if (explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
if (explicitPackage.Contains(filename.Substring(explicitSplit + 1)))
return true;
}
return fileIndex.ContainsKey(filename);
}
@@ -279,7 +293,8 @@ namespace OpenRA.FileSystem
if (explicitSplit < 0)
return false;
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out var explicitPackage))
IReadOnlyPackage explicitPackage;
if (!explicitMounts.TryGetValue(filename.Substring(0, explicitSplit), out explicitPackage))
return false;
if (installedMods[modID].Package == explicitPackage)
@@ -295,7 +310,7 @@ namespace OpenRA.FileSystem
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
{
var explicitSplit = path.IndexOf('|');
if (explicitSplit > 0 && !path.StartsWith("^"))
if (explicitSplit > 0)
{
var parent = path.Substring(0, explicitSplit);
var filename = path.Substring(explicitSplit + 1);
@@ -306,7 +321,8 @@ namespace OpenRA.FileSystem
if (parentPath.StartsWith("$", StringComparison.Ordinal))
{
if (!installedMods.TryGetValue(parentPath.Substring(1), out var mod))
Manifest mod;
if (!installedMods.TryGetValue(parentPath.Substring(1), out mod))
return null;
if (!(mod.Package is Folder))

View File

@@ -12,7 +12,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA.FileSystem
{
@@ -33,11 +32,10 @@ namespace OpenRA.FileSystem
{
get
{
// Order may vary on different file systems and it matters for hashing.
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
.Concat(Directory.GetDirectories(path))
.Select(Path.GetFileName)
.OrderBy(f => f);
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
yield return Path.GetFileName(filename);
foreach (var filename in Directory.GetDirectories(path))
yield return Path.GetFileName(filename);
}
}
@@ -60,15 +58,17 @@ namespace OpenRA.FileSystem
return new Folder(resolvedPath);
// Zip files loaded from Folders (and *only* from Folders) can be read-write
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
IReadWritePackage readWritePackage;
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
return readWritePackage;
// Other package types can be loaded normally
IReadOnlyPackage package;
var s = GetStream(filename);
if (s == null)
return null;
if (context.TryParsePackage(s, filename, out var package))
if (context.TryParsePackage(s, filename, out package))
return package;
s.Dispose();

View File

@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip;
using OpenRA.Primitives;
namespace OpenRA.FileSystem
{
@@ -66,7 +67,8 @@ namespace OpenRA.FileSystem
public void Dispose()
{
pkg?.Close();
if (pkg != null)
pkg.Close();
}
public IReadOnlyPackage OpenPackage(string filename, FileSystem context)
@@ -80,11 +82,12 @@ namespace OpenRA.FileSystem
return new ZipFolder(this, filename);
// Other package types can be loaded normally
IReadOnlyPackage package;
var s = GetStream(filename);
if (s == null)
return null;
if (context.TryParsePackage(s, filename, out var package))
if (context.TryParsePackage(s, filename, out package))
return package;
s.Dispose();

View File

@@ -17,7 +17,6 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.Graphics;
@@ -52,6 +51,7 @@ namespace OpenRA
public static Renderer Renderer;
public static Sound Sound;
public static bool HasInputFocus = false;
public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile;
@@ -62,26 +62,26 @@ namespace OpenRA
public static event Action OnShellmapLoaded = () => { };
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
{
var connection = new NetworkConnection(endpoint);
var connection = new NetworkConnection(host, port);
if (recordReplay)
connection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(endpoint, password, connection);
var om = new OrderManager(host, port, password, connection);
JoinInner(om);
return om;
}
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
static string TimestampedFilename(bool includemilliseconds = false)
{
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
return "OpenRA-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
}
static void JoinInner(OrderManager om)
{
OrderManager?.Dispose();
if (OrderManager != null) OrderManager.Dispose();
OrderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager);
@@ -89,12 +89,12 @@ namespace OpenRA
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
}
// More accurate replacement for Environment.TickCount
@@ -105,14 +105,14 @@ namespace OpenRA
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static void RemoteDirectConnect(ConnectionTarget endpoint)
public static void RemoteDirectConnect(string host, int port)
{
OnRemoteDirectConnect(endpoint);
OnRemoteDirectConnect(host, port);
}
// Hacky workaround for orderManager visibility
@@ -155,7 +155,8 @@ namespace OpenRA
internal static void StartGame(string mapUID, WorldType type)
{
// Dispose of the old world before creating a new one.
worldRenderer?.Dispose();
if (worldRenderer != null)
worldRenderer.Dispose();
Cursor.SetCursor(null);
BeforeGameStart();
@@ -171,13 +172,11 @@ namespace OpenRA
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
using (new PerfTimer("LoadComplete"))
OrderManager.World.LoadComplete(worldRenderer);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
if (OrderManager.GameStarted)
@@ -192,14 +191,6 @@ namespace OpenRA
worldRenderer.RefreshPalette();
Cursor.SetCursor("default");
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
// - All the temporary garbage created during loading can be collected.
// - Live objects are likely to live for the length of the game or longer,
// thus promoting them into a higher generation is not an issue.
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
// - A loading screen is visible, so a delay won't matter to the user.
// Much better to clean up now then to drop frames during gameplay for GC pauses.
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
}
@@ -243,7 +234,7 @@ namespace OpenRA
LobbyInfoChanged += lobbyReady;
om = JoinServer(CreateLocalServer(mapUID), "");
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
}
public static bool IsHost
@@ -262,24 +253,18 @@ namespace OpenRA
public static void InitializeSettings(Arguments args)
{
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
}
public static RunStatus InitializeAndRun(string[] args)
{
Initialize(new Arguments(args));
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
return Run();
}
static void Initialize(Arguments args)
{
var engineDirArg = args.GetValue("Engine.EngineDir", null);
if (!string.IsNullOrEmpty(engineDirArg))
Platform.OverrideEngineDir(engineDirArg);
var supportDirArg = args.GetValue("Engine.SupportDir", null);
if (!string.IsNullOrEmpty(supportDirArg))
Platform.OverrideSupportDir(supportDirArg);
@@ -289,7 +274,7 @@ namespace OpenRA
// Load the engine version as early as possible so it can be written to exception logs
try
{
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
}
catch { }
@@ -317,7 +302,6 @@ namespace OpenRA
Log.AddChannel("graphics", "graphics.log");
Log.AddChannel("geoip", "geoip.log");
Log.AddChannel("nat", "nat.log");
Log.AddChannel("client", "client.log");
var platforms = new[] { Settings.Game.Platform, "Default", null };
foreach (var p in platforms)
@@ -328,7 +312,7 @@ namespace OpenRA
Settings.Game.Platform = p;
try
{
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
var assembly = Assembly.LoadFile(rendererPath);
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
@@ -346,9 +330,11 @@ namespace OpenRA
Log.Write("graphics", "{0}", e);
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
Renderer?.Dispose();
if (Renderer != null)
Renderer.Dispose();
Sound?.Dispose();
if (Sound != null)
Sound.Dispose();
}
}
@@ -358,7 +344,7 @@ namespace OpenRA
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ?
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
new[] { Path.Combine(Platform.EngineDir, "mods") };
new[] { Path.Combine(".", "mods") };
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
@@ -369,28 +355,20 @@ namespace OpenRA
ExternalMods = new ExternalMods();
if (modID != null && Mods.TryGetValue(modID, out _))
Manifest currentMod;
if (modID != null && Mods.TryGetValue(modID, out currentMod))
{
var launchPath = args.GetValue("Engine.LaunchPath", null);
var launchArgs = new List<string>();
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
// Sanitize input from platform-specific launchers
// Process.Start requires paths to not be quoted, even if they contain spaces
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
if (launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath.Substring(1, launchPath.Length - 2);
if (launchPath == null)
{
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
// Platform-specific launchers are expected to manage this internally.
launchPath = Assembly.GetEntryAssembly().Location;
if (!string.IsNullOrEmpty(engineDirArg))
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
}
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
ExternalMod activeMod;
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
}
@@ -407,15 +385,18 @@ namespace OpenRA
LobbyInfoChanged = () => { };
ConnectionStateChanged = om => { };
BeforeGameStart = () => { };
OnRemoteDirectConnect = endpoint => { };
OnRemoteDirectConnect = (a, b) => { };
delayedActions = new ActionQueue();
Ui.ResetAll();
worldRenderer?.Dispose();
if (worldRenderer != null)
worldRenderer.Dispose();
worldRenderer = null;
server?.Shutdown();
OrderManager?.Dispose();
if (server != null)
server.Shutdown();
if (OrderManager != null)
OrderManager.Dispose();
if (ModData != null)
{
@@ -437,7 +418,7 @@ namespace OpenRA
ModData = new ModData(Mods[mod], Mods, true);
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
if (!ModData.LoadScreen.BeforeLoad())
return;
@@ -451,7 +432,8 @@ namespace OpenRA
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
Renderer.InitializeDepthBuffer(grid);
Cursor?.Dispose();
if (Cursor != null)
Cursor.Dispose();
Cursor = new CursorManager(ModData.CursorProvider);
@@ -460,13 +442,13 @@ namespace OpenRA
PerfHistory.Items["render_world"].HasNormalTick = false;
PerfHistory.Items["render_widgets"].HasNormalTick = false;
PerfHistory.Items["render_flip"].HasNormalTick = false;
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
JoinLocal();
try
{
discoverNat?.Wait();
if (discoverNat != null)
discoverNat.Wait();
}
catch (Exception e)
{
@@ -553,7 +535,7 @@ namespace OpenRA
using (new PerfTimer("Renderer.SaveScreenshot"))
{
var mod = ModData.Manifest.Metadata;
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
Directory.CreateDirectory(directory);
var filename = TimestampedFilename(true);
@@ -629,7 +611,8 @@ namespace OpenRA
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
}
benchmark?.Tick(LocalTick);
if (benchmark != null)
benchmark.Tick(LocalTick);
}
}
@@ -727,7 +710,6 @@ namespace OpenRA
PerfHistory.Items["render_world"].Tick();
PerfHistory.Items["render_widgets"].Tick();
PerfHistory.Items["render_flip"].Tick();
PerfHistory.Items["terrain_lighting"].Tick();
}
static void Loop()
@@ -854,10 +836,12 @@ namespace OpenRA
finally
{
// Ensure that the active replay is properly saved
OrderManager?.Dispose();
if (OrderManager != null)
OrderManager.Dispose();
}
worldRenderer?.Dispose();
if (worldRenderer != null)
worldRenderer.Dispose();
ModData.Dispose();
ChromeProvider.Deinitialize();
@@ -896,7 +880,8 @@ namespace OpenRA
public static void Disconnect()
{
OrderManager.World?.TraitDict.PrintReport();
if (OrderManager.World != null)
OrderManager.World.TraitDict.PrintReport();
OrderManager.Dispose();
CloseServer();
@@ -905,7 +890,8 @@ namespace OpenRA
public static void CloseServer()
{
server?.Shutdown();
if (server != null)
server.Shutdown();
}
public static T CreateObject<T>(string name)
@@ -913,19 +899,12 @@ namespace OpenRA
return ModData.ObjectCreator.CreateObject<T>(name);
}
public static ConnectionTarget CreateServer(ServerSettings settings)
public static void CreateServer(ServerSettings settings)
{
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
new IPEndPoint(IPAddress.Any, settings.ListenPort)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
return server.GetEndpointForLocalConnection();
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
}
public static ConnectionTarget CreateLocalServer(string map)
public static int CreateLocalServer(string map)
{
var settings = new ServerSettings()
{
@@ -934,16 +913,9 @@ namespace OpenRA
AdvertiseOnline = false
};
// Always connect to local games using the same loopback connection
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
return server.GetEndpointForLocalConnection();
return server.Port;
}
public static bool IsCurrentWorld(World world)

View File

@@ -35,7 +35,6 @@ namespace OpenRA
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
public IList<Player> Players { get; private set; }
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
@@ -119,14 +118,11 @@ namespace OpenRA
IsBot = runtimePlayer.IsBot,
FactionName = runtimePlayer.Faction.Name,
FactionId = runtimePlayer.Faction.InternalName,
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
Color = runtimePlayer.Color,
Team = client.Team,
Handicap = client.Handicap,
SpawnPoint = runtimePlayer.SpawnPoint,
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
Fingerprint = client.Fingerprint
};
@@ -137,7 +133,9 @@ namespace OpenRA
/// <summary>Gets the player information for the specified runtime player instance.</summary>
public Player GetPlayer(OpenRA.Player runtimePlayer)
{
playersByRuntime.TryGetValue(runtimePlayer, out var player);
Player player;
playersByRuntime.TryGetValue(runtimePlayer, out player);
return player;
}
@@ -160,14 +158,9 @@ namespace OpenRA
public string FactionId;
public Color Color;
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
public string DisplayFactionName;
public string DisplayFactionId;
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
public int Team;
public int SpawnPoint;
public int Handicap;
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
public bool IsRandomFaction;
@@ -188,9 +181,6 @@ namespace OpenRA
/// <summary>The time when this player won or lost the game.</summary>
public DateTime OutcomeTimestampUtc;
/// <summary>The frame at which this player disconnected.</summary>
public int DisconnectFrame;
#endregion
}
}

View File

@@ -23,7 +23,6 @@ namespace OpenRA
public class ActorInfo
{
public const string AbstractActorPrefix = "^";
public const char TraitInstanceSeparator = '@';
/// <summary>
/// The actor name can be anything, but the sprites used in the Render*: traits default to this one.
@@ -33,7 +32,7 @@ namespace OpenRA
/// </summary>
public readonly string Name;
readonly TypeDictionary traits = new TypeDictionary();
List<TraitInfo> constructOrderCache = null;
List<ITraitInfo> constructOrderCache = null;
public ActorInfo(ObjectCreator creator, string name, MiniYaml node)
{
@@ -47,7 +46,7 @@ namespace OpenRA
{
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
// LoadTraitInfo will only return null to signal us to abort here if the linter is running
var trait = LoadTraitInfo(creator, t.Key, t.Value);
var trait = LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value);
if (trait != null)
traits.Add(trait);
}
@@ -65,7 +64,7 @@ namespace OpenRA
}
}
public ActorInfo(string name, params TraitInfo[] traitInfos)
public ActorInfo(string name, params ITraitInfo[] traitInfos)
{
Name = name;
foreach (var t in traitInfos)
@@ -73,7 +72,7 @@ namespace OpenRA
traits.TrimExcess();
}
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
{
if (!string.IsNullOrEmpty(my.Value))
throw new YamlException("Junk value `{0}` on trait node {1}"
@@ -81,16 +80,12 @@ namespace OpenRA
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
// ObjectCreator will only return null to signal us to abort here if the linter is running
var traitInstance = traitName.Split(TraitInstanceSeparator);
var info = creator.CreateObject<TraitInfo>(traitInstance[0] + "Info");
var info = creator.CreateObject<ITraitInfo>(traitName + "Info");
if (info == null)
return null;
try
{
if (traitInstance.Length > 1)
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
FieldLoader.Load(info, my);
}
catch (FieldLoader.MissingFieldsException e)
@@ -102,12 +97,12 @@ namespace OpenRA
return info;
}
public IEnumerable<TraitInfo> TraitsInConstructOrder()
public IEnumerable<ITraitInfo> TraitsInConstructOrder()
{
if (constructOrderCache != null)
return constructOrderCache;
var source = traits.WithInterface<TraitInfo>().Select(i => new
var source = traits.WithInterface<ITraitInfo>().Select(i => new
{
Trait = i,
Type = i.GetType(),
@@ -153,7 +148,7 @@ namespace OpenRA
return constructOrderCache;
}
public static IEnumerable<Type> PrerequisitesOf(TraitInfo info)
public static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
{
return info
.GetType()

View File

@@ -41,7 +41,8 @@ namespace OpenRA.GameRules
public void Load(IReadOnlyFileSystem fileSystem)
{
if (!fileSystem.TryOpen(Filename, out var stream))
Stream stream;
if (!fileSystem.TryOpen(Filename, out stream))
return;
try
@@ -49,7 +50,8 @@ namespace OpenRA.GameRules
Exists = true;
foreach (var loader in Game.ModData.SoundLoaders)
{
if (loader.TryParseSound(stream, out var soundFormat))
ISoundFormat soundFormat;
if (loader.TryParseSound(stream, out soundFormat))
{
Length = (int)soundFormat.LengthInSeconds;
soundFormat.Dispose();

View File

@@ -208,7 +208,7 @@ namespace OpenRA
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
new SequenceProvider(fileSystem, modData, ts, mapSequences);
var modelSequences = dr.ModelSequences;
if (mapModelSequences != null)

View File

@@ -24,8 +24,8 @@ namespace OpenRA.GameRules
public int[] DamageModifiers;
public int[] InaccuracyModifiers;
public int[] RangeModifiers;
public WAngle Facing;
public Func<WAngle> CurrentMuzzleFacing;
public int Facing;
public Func<int> CurrentMuzzleFacing;
public WPos Source;
public Func<WPos> CurrentSource;
public Actor SourceActor;
@@ -38,8 +38,6 @@ namespace OpenRA.GameRules
public WeaponInfo Weapon;
public int[] DamageModifiers = { };
public WPos? Source;
public WRot ImpactOrientation;
public WPos ImpactPosition;
public Actor SourceActor;
public Target WeaponTarget;
@@ -47,22 +45,11 @@ namespace OpenRA.GameRules
{
Weapon = args.Weapon;
DamageModifiers = args.DamageModifiers;
ImpactPosition = args.PassiveTarget;
Source = args.Source;
SourceActor = args.SourceActor;
WeaponTarget = args.GuidedTarget;
}
// For places that only want to update some of the fields (usually DamageModifiers)
public WarheadArgs(WarheadArgs args)
{
Weapon = args.Weapon;
DamageModifiers = args.DamageModifiers;
Source = args.Source;
SourceActor = args.SourceActor;
WeaponTarget = args.WeaponTarget;
}
// Default empty constructor for callers that want to initialize fields themselves
public WarheadArgs() { }
}
@@ -105,12 +92,6 @@ namespace OpenRA.GameRules
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
public readonly BitSet<TargetableType> InvalidTargets;
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
public readonly WDist AirThreshold = new WDist(128);
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
"If multiple entries, their number needs to match Burst - 1.")]
public readonly int[] BurstDelays = { 5 };
@@ -137,7 +118,8 @@ namespace OpenRA.GameRules
static object LoadProjectile(MiniYaml yaml)
{
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
MiniYaml proj;
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
FieldLoader.Load(ret, proj);
@@ -163,7 +145,7 @@ namespace OpenRA.GameRules
}
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
public bool IsValidAgainst(Target target, World world, Actor firedBy)
{
if (target.Type == TargetType.Actor)
return IsValidAgainst(target.Actor, firedBy);
@@ -173,10 +155,6 @@ namespace OpenRA.GameRules
if (target.Type == TargetType.Terrain)
{
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
if (dat > AirThreshold)
return IsValidTarget(TargetTypeAir);
var cell = world.Map.CellContaining(target.CenterPosition);
if (!world.Map.Contains(cell))
return false;
@@ -220,24 +198,20 @@ namespace OpenRA.GameRules
}
/// <summary>Applies all the weapon's warheads to the target.</summary>
public void Impact(in Target target, WarheadArgs args)
public void Impact(Target target, WarheadArgs args)
{
var world = args.SourceActor.World;
foreach (var warhead in Warheads)
{
if (warhead.Delay > 0)
{
// Lambdas can't use 'in' variables, so capture a copy for later
var delayedTarget = target;
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
}
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
else
warhead.DoImpact(target, args);
}
}
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
public void Impact(in Target target, Actor firedBy)
public void Impact(Target target, Actor firedBy)
{
// The impact will happen immediately at target.CenterPosition.
var args = new WarheadArgs

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Graphics
public bool IsDecoration { get; set; }
readonly SequenceProvider sequenceProvider;
readonly Func<WAngle> facingFunc;
readonly Func<int> facingFunc;
readonly Func<bool> paused;
int frame;
@@ -33,15 +33,15 @@ namespace OpenRA.Graphics
Action tickFunc = () => { };
public Animation(World world, string name)
: this(world, name, () => WAngle.Zero) { }
: this(world, name, () => 0) { }
public Animation(World world, string name, Func<WAngle> facingFunc)
public Animation(World world, string name, Func<int> facingFunc)
: this(world, name, facingFunc, null) { }
public Animation(World world, string name, Func<bool> paused)
: this(world, name, () => WAngle.Zero, paused) { }
: this(world, name, () => 0, paused) { }
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
{
sequenceProvider = world.Map.Rules.Sequences;
Name = name.ToLowerInvariant();
@@ -54,12 +54,12 @@ namespace OpenRA.Graphics
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
{
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration);
if (CurrentSequence.ShadowStart >= 0)
{
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true);
return new IRenderable[] { shadowRenderable, imageRenderable };
}
@@ -156,7 +156,7 @@ namespace OpenRA.Graphics
{
frame = CurrentSequence.Length - 1;
tickFunc = () => { };
after?.Invoke();
if (after != null) after();
}
};
}

View File

@@ -54,10 +54,10 @@ namespace OpenRA.Graphics
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
static Dictionary<string, Collection> collections;
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
static Dictionary<string, Pair<Sheet, int>> cachedSheets;
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
static Dictionary<string, Sprite[]> cachedPanelSprites;
static Dictionary<Collection, (Sheet Sheet, int)> cachedCollectionSheets;
static Dictionary<Collection, Pair<Sheet, int>> cachedCollectionSheets;
static IReadOnlyFileSystem fileSystem;
static float dpiScale = 1;
@@ -72,10 +72,10 @@ namespace OpenRA.Graphics
fileSystem = modData.DefaultFileSystem;
collections = new Dictionary<string, Collection>();
cachedSheets = new Dictionary<string, (Sheet, int)>();
cachedSheets = new Dictionary<string, Pair<Sheet, int>>();
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
cachedCollectionSheets = new Dictionary<Collection, Pair<Sheet, int>>();
Collections = new ReadOnlyDictionary<string, Collection>(collections);
@@ -91,7 +91,7 @@ namespace OpenRA.Graphics
{
if (cachedSheets != null)
foreach (var sheet in cachedSheets.Values)
sheet.Sheet.Dispose();
sheet.First.Dispose();
collections = null;
cachedSheets = null;
@@ -108,10 +108,12 @@ namespace OpenRA.Graphics
collections.Add(name, FieldLoader.Load<Collection>(yaml));
}
static (Sheet Sheet, int Density) SheetForCollection(Collection c)
static Pair<Sheet, int> SheetForCollection(Collection c)
{
Pair<Sheet, int> sheetDensity;
// Outer cache avoids recalculating image names
if (!cachedCollectionSheets.TryGetValue(c, out (Sheet, int) sheetDensity))
if (!cachedCollectionSheets.TryGetValue(c, out sheetDensity))
{
var image = c.Image;
var density = 1;
@@ -135,7 +137,7 @@ namespace OpenRA.Graphics
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
sheetDensity = (sheet, density);
sheetDensity = Pair.New(sheet, density);
cachedSheets.Add(image, sheetDensity);
}
@@ -151,16 +153,20 @@ namespace OpenRA.Graphics
return null;
// Cached sprite
if (cachedSprites.TryGetValue(collectionName, out var cachedCollection) && cachedCollection.TryGetValue(imageName, out var sprite))
Dictionary<string, Sprite> cachedCollection;
Sprite sprite;
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
return sprite;
if (!collections.TryGetValue(collectionName, out var collection))
Collection collection;
if (!collections.TryGetValue(collectionName, out collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null;
}
if (!collection.Regions.TryGetValue(imageName, out var mi))
Rectangle mi;
if (!collection.Regions.TryGetValue(imageName, out mi))
return null;
// Cache the sprite
@@ -171,7 +177,7 @@ namespace OpenRA.Graphics
cachedSprites.Add(collectionName, cachedCollection);
}
var image = new Sprite(sheetDensity.Sheet, sheetDensity.Density * mi, TextureChannel.RGBA, 1f / sheetDensity.Density);
var image = new Sprite(sheetDensity.First, sheetDensity.Second * mi, TextureChannel.RGBA, 1f / sheetDensity.Second);
cachedCollection.Add(imageName, image);
return image;
@@ -183,10 +189,12 @@ namespace OpenRA.Graphics
return null;
// Cached sprite
if (cachedPanelSprites.TryGetValue(collectionName, out var cachedSprites))
Sprite[] cachedSprites;
if (cachedPanelSprites.TryGetValue(collectionName, out cachedSprites))
return cachedSprites;
if (!collections.TryGetValue(collectionName, out var collection))
Collection collection;
if (!collections.TryGetValue(collectionName, out collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return null;
@@ -206,20 +214,20 @@ namespace OpenRA.Graphics
var pr = collection.PanelRegion;
var ps = collection.PanelSides;
var sides = new (PanelSides PanelSides, Rectangle Bounds)[]
var sides = new[]
{
(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
Pair.New(PanelSides.Top | PanelSides.Left, new Rectangle(pr[0], pr[1], pr[2], pr[3])),
Pair.New(PanelSides.Top, new Rectangle(pr[0] + pr[2], pr[1], pr[4], pr[3])),
Pair.New(PanelSides.Top | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1], pr[6], pr[3])),
Pair.New(PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3], pr[2], pr[5])),
Pair.New(PanelSides.Center, new Rectangle(pr[0] + pr[2], pr[1] + pr[3], pr[4], pr[5])),
Pair.New(PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3], pr[6], pr[5])),
Pair.New(PanelSides.Bottom | PanelSides.Left, new Rectangle(pr[0], pr[1] + pr[3] + pr[5], pr[2], pr[7])),
Pair.New(PanelSides.Bottom, new Rectangle(pr[0] + pr[2], pr[1] + pr[3] + pr[5], pr[4], pr[7])),
Pair.New(PanelSides.Bottom | PanelSides.Right, new Rectangle(pr[0] + pr[2] + pr[4], pr[1] + pr[3] + pr[5], pr[6], pr[7]))
};
sprites = sides.Select(x => ps.HasSide(x.PanelSides) ? new Sprite(sheetDensity.Sheet, sheetDensity.Density * x.Bounds, TextureChannel.RGBA, 1f / sheetDensity.Density) : null)
sprites = sides.Select(x => ps.HasSide(x.First) ? new Sprite(sheetDensity.First, sheetDensity.Second * x.Second, TextureChannel.RGBA, 1f / sheetDensity.Second) : null)
.ToArray();
}
else
@@ -248,7 +256,8 @@ namespace OpenRA.Graphics
if (string.IsNullOrEmpty(collectionName))
return new Size(0, 0);
if (!collections.TryGetValue(collectionName, out var collection))
Collection collection;
if (!collections.TryGetValue(collectionName, out collection))
{
Log.Write("debug", "Could not find collection '{0}'", collectionName);
return new Size(0, 0);

View File

@@ -236,14 +236,15 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < width; i++)
{
var rgba = palette[frame.Data[j * width + i]];
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
var c = 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);
data[k] = bytes[2];
data[k + 1] = bytes[1];
data[k + 2] = bytes[0];
data[k + 3] = bytes[3];
}
}

View File

@@ -31,10 +31,8 @@ namespace OpenRA.Graphics
Palette = palette;
Name = name;
Frames = cache[cursorSrc].Skip(Start).ToArray();
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
Length = Frames.Length;
Length = Frames.Length - Start;
else if (d.ContainsKey("Length"))
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
else if (d.ContainsKey("End"))
@@ -42,17 +40,22 @@ namespace OpenRA.Graphics
else
Length = 1;
Frames = Frames.Take(Length).ToArray();
Frames = cache[cursorSrc]
.Skip(Start)
.Take(Length)
.ToArray();
if (d.ContainsKey("X"))
{
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
int x;
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
Hotspot = Hotspot.WithX(x);
}
if (d.ContainsKey("Y"))
{
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
int y;
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
Hotspot = Hotspot.WithY(y);
}
}

View File

@@ -38,16 +38,19 @@ namespace OpenRA.Graphics
public IPalette GetPalette(string name)
{
if (modifiablePalettes.TryGetValue(name, out var mutable))
MutablePalette mutable;
if (modifiablePalettes.TryGetValue(name, out mutable))
return mutable.AsReadOnly();
if (palettes.TryGetValue(name, out var immutable))
ImmutablePalette immutable;
if (palettes.TryGetValue(name, out immutable))
return immutable;
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
}
public int GetPaletteIndex(string name)
{
if (!indices.TryGetValue(name, out var ret))
int ret;
if (!indices.TryGetValue(name, out ret))
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
return ret;
}

View File

@@ -10,6 +10,7 @@
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA.Graphics
@@ -18,12 +19,12 @@ namespace OpenRA.Graphics
{
public readonly IModel Model;
public readonly Func<WVec> OffsetFunc;
public readonly Func<WRot> RotationFunc;
public readonly Func<IEnumerable<WRot>> RotationFunc;
public readonly Func<bool> DisableFunc;
public readonly Func<uint> FrameFunc;
public readonly bool ShowShadow;
public ModelAnimation(IModel model, Func<WVec> offset, Func<WRot> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
public ModelAnimation(IModel model, Func<WVec> offset, Func<IEnumerable<WRot>> rotation, Func<bool> disable, Func<uint> frame, bool showshadow)
{
Model = model;
OffsetFunc = offset;

View File

@@ -48,7 +48,7 @@ namespace OpenRA.Graphics
readonly Dictionary<Sheet, IFrameBuffer> mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
readonly Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
readonly List<(Sheet Sheet, Action Func)> doRender = new List<(Sheet, Action)>();
readonly List<Pair<Sheet, Action>> doRender = new List<Pair<Sheet, Action>>();
SheetBuilder sheetBuilderForFrame;
bool isInFrame;
@@ -79,8 +79,8 @@ namespace OpenRA.Graphics
}
public ModelRenderProxy RenderAsync(
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
{
if (!isInFrame)
@@ -114,7 +114,8 @@ namespace OpenRA.Graphics
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
var worldTransform = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
var worldTransform = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
@@ -160,8 +161,10 @@ namespace OpenRA.Graphics
}
// Shadows are rendered at twice the resolution to reduce artifacts
CalculateSpriteGeometry(tl, br, 1, out var spriteSize, out var spriteOffset);
CalculateSpriteGeometry(stl, sbr, 2, out var shadowSpriteSize, out var shadowSpriteOffset);
Size spriteSize, shadowSpriteSize;
int2 spriteOffset, shadowSpriteOffset;
CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
if (sheetBuilderForFrame == null)
sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
@@ -178,7 +181,7 @@ namespace OpenRA.Graphics
var correctionTransform = Util.MatrixMultiply(translateMtx, FlipMtx);
var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, ShadowScaleFlipMtx);
doRender.Add((sprite.Sheet, () =>
doRender.Add(Pair.New<Sheet, Action>(sprite.Sheet, () =>
{
foreach (var m in models)
{
@@ -186,7 +189,8 @@ namespace OpenRA.Graphics
var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
var rotations = Util.MakeFloatMatrix(m.RotationFunc().AsMatrix());
var rotations = m.RotationFunc().Aggregate(Util.IdentityMatrix(),
(x, y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
@@ -322,16 +326,16 @@ namespace OpenRA.Graphics
foreach (var v in doRender)
{
// Change sheet
if (v.Sheet != currentSheet)
if (v.First != currentSheet)
{
if (fbo != null)
DisableFrameBuffer(fbo);
currentSheet = v.Sheet;
currentSheet = v.First;
fbo = EnableFrameBuffer(currentSheet);
}
v.Func();
v.Second();
}
if (fbo != null)

View File

@@ -17,8 +17,6 @@ namespace OpenRA
{
public enum GLProfile
{
Automatic,
ANGLE,
Modern,
Embedded,
Legacy
@@ -26,7 +24,7 @@ namespace OpenRA
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -41,10 +39,7 @@ namespace OpenRA
Subtractive,
Multiply,
Multiplicative,
DoubleMultiplicative,
LowAdditive,
Screen,
Translucent
DoubleMultiplicative
}
public interface IPlatformWindow : IDisposable
@@ -58,7 +53,6 @@ namespace OpenRA
Size SurfaceSize { get; }
int DisplayCount { get; }
int CurrentDisplay { get; }
bool HasInputFocus { get; }
event Action<float, float, float, float> OnWindowScaleChanged;
@@ -103,7 +97,8 @@ namespace OpenRA
{
void Bind();
void SetData(T[] vertices, int length);
void SetData(T[] vertices, int offset, int start, int length);
void SetData(T[] vertices, int start, int length);
void SetData(IntPtr data, int start, int length);
}
public interface IShader

View File

@@ -47,13 +47,14 @@ namespace OpenRA.Graphics
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
}
remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
.ToDictionary(u => u.Item1, u => u.Item2);
remapColors = remapRamp.Select((x, i) => Pair.New(baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
.ToDictionary(u => u.First, u => u.Second);
}
public Color GetRemappedColor(Color original, int index)
{
return remapColors.TryGetValue(index, out var c)
Color c;
return remapColors.TryGetValue(index, out c)
? c : original;
}
}

View File

@@ -28,11 +28,6 @@ namespace OpenRA.Graphics
IFinalizedRenderable PrepareRender(WorldRenderer wr);
}
public interface ITintableRenderable
{
IRenderable WithTint(in float3 newTint);
}
public interface IFinalizedRenderable
{
void Render(WorldRenderer wr);

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
public void DrawLine(float3 start, float3 end, float width, Color startColor, Color endColor)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
@@ -55,7 +55,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void DrawLine(in float3 start, in float3 end, float width, Color color)
public void DrawLine(float3 start, float3 end, float width, Color color)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float2(-delta.Y, delta.X);
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
/// Will behave badly if the lines are parallel.
/// Z position is the average of a and b (ignores actual intersection point if it exists)
/// </summary>
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
{
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
@@ -193,14 +193,14 @@ namespace OpenRA.Graphics
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
}
public void DrawRect(in float3 tl, in float3 br, float width, Color color)
public void DrawRect(float3 tl, float3 br, float width, Color color)
{
var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z);
DrawPolygon(new[] { tl, tr, br, bl }, width, color);
}
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
public void FillTriangle(float3 a, float3 b, float3 c, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -214,14 +214,14 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void FillRect(in float3 tl, in float3 br, Color color)
public void FillRect(float3 tl, float3 br, Color color)
{
var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z);
FillRect(tl, tr, br, bl, color);
}
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -238,7 +238,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
{
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor);
@@ -250,7 +250,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
static Vertex VertexWithColor(in float3 xyz, Color color)
static Vertex VertexWithColor(float3 xyz, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
}
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
public void FillEllipse(float3 tl, float3 br, Color color, int vertices = 32)
{
// TODO: Create an ellipse polygon instead
var a = (br.X - tl.X) / 2;

View File

@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawSprite(Sprite s, in float3 location, in float3 size)
public void DrawSprite(Sprite s, float3 location, float3 size)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, location, 0, size);
}
public void DrawSprite(Sprite s, in float3 location)
public void DrawSprite(Sprite s, float3 location)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -38,28 +38,12 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, location, 0, s.Size);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, a, b, c, d);
}
public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
}
}
}

View File

@@ -32,29 +32,29 @@ namespace OpenRA.Graphics
int ShadowZOffset { get; }
int[] Frames { get; }
Rectangle Bounds { get; }
bool IgnoreWorldTint { get; }
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing);
Sprite GetSprite(int frame, int facing);
Sprite GetShadow(int frame, int facing);
}
public interface ISpriteSequenceLoader
{
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
Action<string> OnMissingSpriteError { get; set; }
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
}
public class SequenceProvider : IDisposable
{
readonly ModData modData;
readonly string tileSet;
readonly TileSet tileSet;
readonly Lazy<Sequences> sequences;
readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
{
this.modData = modData;
this.tileSet = tileSet;
@@ -69,17 +69,17 @@ namespace OpenRA.Graphics
public ISpriteSequence GetSequence(string unitName, string sequenceName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
ISpriteSequence seq;
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
return seq;
}
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
public bool HasSequence(string unitName)
{
return sequences.Value.ContainsKey(unitName);
@@ -87,7 +87,8 @@ namespace OpenRA.Graphics
public bool HasSequence(string unitName, string sequenceName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.ContainsKey(sequenceName);
@@ -95,7 +96,8 @@ namespace OpenRA.Graphics
public IEnumerable<string> Sequences(string unitName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.Keys;
@@ -105,15 +107,15 @@ namespace OpenRA.Graphics
{
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
var items = new Dictionary<string, UnitSequences>();
foreach (var node in nodes)
foreach (var n in nodes)
{
// Nodes starting with ^ are inheritable but never loaded directly
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
continue;
// Work around the loop closure issue in older versions of C#
var node = n;
var key = node.Value.ToLines(node.Key).JoinWith("|");
if (sequenceCache.TryGetValue(key, out var t))
UnitSequences t;
if (sequenceCache.TryGetValue(key, out t))
items.Add(node.Key, t);
else
{

View File

@@ -146,7 +146,8 @@ namespace OpenRA.Graphics
public void Dispose()
{
texture?.Dispose();
if (texture != null)
texture.Dispose();
}
}
}

View File

@@ -76,7 +76,7 @@ namespace OpenRA.Graphics
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(byte[] src, Size size, float zRamp, float3 spriteOffset)
{
// Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0)
@@ -115,7 +115,7 @@ namespace OpenRA.Graphics
}
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset, float scale = 1f)
{
if (imageSize.Width + p.X + margin > current.Size.Width)
{

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Graphics
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
{
Sheet = sheet;
Bounds = bounds;

View File

@@ -23,8 +23,8 @@ namespace OpenRA.Graphics
readonly SheetBuilder builder;
readonly Func<string, float> lineWidth;
readonly IFont font;
readonly Cache<char, GlyphInfo> glyphs;
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
readonly Cache<Tuple<char, Color, int>, Sprite> contrastGlyphs;
readonly Cache<int, float[]> dilationElements;
float deviceScale;
@@ -39,20 +39,17 @@ namespace OpenRA.Graphics
this.builder = builder;
font = Game.Renderer.CreateFont(data);
glyphs = new Cache<char, GlyphInfo>(CreateGlyph);
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
contrastGlyphs = new Cache<Tuple<char, Color, int>, Sprite>(CreateContrastGlyph);
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
// PERF: Cache these delegates for Measure calls.
Func<char, float> characterWidth = character => glyphs[character].Advance;
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
lineWidth = line => line.Sum(characterWidth) / deviceScale;
// Pre-cache small font sizes so glyphs are immediately available when we need them
if (size <= 24)
using (new PerfTimer("Precache {0} {1}px".F(name, size)))
for (var n = (char)0x20; n < (char)0x7f; n++)
if (glyphs[n] == null)
throw new InvalidOperationException();
PrecacheColor(Color.White, name);
TopOffset = size - ascender;
}
@@ -64,6 +61,14 @@ namespace OpenRA.Graphics
contrastGlyphs.Clear();
}
void PrecacheColor(Color c, string name)
{
using (new PerfTimer("PrecacheColor {0} {1}px {2}".F(name, size, c)))
for (var n = (char)0x20; n < (char)0x7f; n++)
if (glyphs[Pair.New(n, c)] == null)
throw new InvalidOperationException();
}
void DrawTextContrast(string text, float2 location, Color contrastColor, int contrastOffset)
{
// Offset from the baseline position to the top-left of the glyph for rendering
@@ -73,7 +78,6 @@ namespace OpenRA.Graphics
var screenContrast = (int)(contrastOffset * deviceScale);
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
var contrastVector = new float2(screenContrast, screenContrast);
var tint = new float3(contrastColor.R / 255f, contrastColor.G / 255f, contrastColor.B / 255f);
foreach (var s in text)
{
if (s == '\n')
@@ -83,16 +87,15 @@ namespace OpenRA.Graphics
continue;
}
var g = glyphs[s];
var g = glyphs[Pair.New(s, Color.Black)];
// Convert screen coordinates back to UI coordinates for drawing
if (g.Sprite != null)
{
var contrastSprite = contrastGlyphs[(s, screenContrast)];
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
var contrastSprite = contrastGlyphs[Tuple.Create(s, contrastColor, screenContrast)];
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
(screen + g.Offset - contrastVector) / deviceScale,
contrastSprite.Size / deviceScale,
tint);
contrastSprite.Size / deviceScale);
}
screen += new int2((int)(g.Advance + 0.5f), 0);
@@ -106,7 +109,6 @@ namespace OpenRA.Graphics
// Calculate positions in screen pixel coordinates
var screen = new int2((int)(location.X * deviceScale + 0.5f), (int)(location.Y * deviceScale + 0.5f));
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
foreach (var s in text)
{
if (s == '\n')
@@ -116,14 +118,13 @@ namespace OpenRA.Graphics
continue;
}
var g = glyphs[s];
var g = glyphs[Pair.New(s, c)];
// Convert screen coordinates back to UI coordinates for drawing
if (g.Sprite != null)
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
(screen + g.Offset).ToFloat2() / deviceScale,
g.Sprite.Size / deviceScale,
tint);
g.Sprite.Size / deviceScale);
screen += new int2((int)(g.Advance + 0.5f), 0);
}
@@ -143,7 +144,6 @@ namespace OpenRA.Graphics
var offset = new float2(0, size);
var cosa = (float)Math.Cos(-angle);
var sina = (float)Math.Sin(-angle);
var tint = new float3(c.R / 255f, c.G / 255f, c.B / 255f);
var p = offset;
foreach (var s in text)
@@ -155,7 +155,7 @@ namespace OpenRA.Graphics
continue;
}
var g = glyphs[s];
var g = glyphs[Pair.New(s, c)];
if (g.Sprite != null)
{
var tl = new float2(
@@ -172,12 +172,11 @@ namespace OpenRA.Graphics
// Offset rotated glyph to align the top-left corner with the screen pixel grid
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
ra + screenOffset,
rb + screenOffset,
rc + screenOffset,
rd + screenOffset,
tint);
rd + screenOffset);
}
p += new float2(g.Advance / deviceScale, 0);
@@ -239,20 +238,13 @@ namespace OpenRA.Graphics
return new int2(0, size);
var lines = text.Split('\n');
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
}
static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
GlyphInfo CreateGlyph(Pair<char, Color> c)
{
var maxWidth = 0f;
foreach (var line in lines)
maxWidth = Math.Max(maxWidth, lineWidth(line));
return maxWidth;
}
var glyph = font.CreateGlyph(c.First, size, deviceScale);
GlyphInfo CreateGlyph(char c)
{
var glyph = font.CreateGlyph(c, size, deviceScale);
if (glyph.Data == null)
{
return new GlyphInfo
@@ -282,10 +274,12 @@ namespace OpenRA.Graphics
if (p != 0)
{
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
dest[q] = p;
dest[q + 1] = p;
dest[q + 2] = p;
dest[q + 3] = p;
var pmc = Util.PremultiplyAlpha(Color.FromArgb(p, c.Second));
dest[q] = pmc.B;
dest[q + 1] = pmc.G;
dest[q + 2] = pmc.R;
dest[q + 3] = pmc.A;
}
}
}
@@ -353,12 +347,16 @@ namespace OpenRA.Graphics
return elem;
}
Sprite CreateContrastGlyph((char C, int Radius) c)
Sprite CreateContrastGlyph(Tuple<char, Color, int> c)
{
var glyph = glyphs[c.C];
var r = c.Radius;
// Source glyph color doesn't matter, so use black
var glyph = glyphs[Pair.New(c.Item1, Color.Black)];
var color = c.Item2;
var r = c.Item3;
var s = builder.Allocate(new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r));
var size = new Size(glyph.Sprite.Bounds.Width + 2 * r, glyph.Sprite.Bounds.Height + 2 * r);
var s = builder.Allocate(size);
var dest = s.Sheet.GetData();
var destStride = s.Sheet.Size.Width * 4;
@@ -400,10 +398,11 @@ namespace OpenRA.Graphics
if (alpha > 0)
{
var q = destStride * (j + s.Bounds.Top) + 4 * (i + s.Bounds.Left);
dest[q] = alpha;
dest[q + 1] = alpha;
dest[q + 2] = alpha;
dest[q + 3] = alpha;
var pmc = Util.PremultiplyAlpha(Color.FromArgb(alpha, color));
dest[q] = pmc.B;
dest[q + 1] = pmc.G;
dest[q + 2] = pmc.R;
dest[q + 3] = pmc.A;
}
}
}

View File

@@ -76,7 +76,8 @@ namespace OpenRA.Graphics
var allSprites = sprites.GetOrAdd(filename);
var sprite = allSprites.FirstOrDefault();
if (!unloadedFrames.TryGetValue(filename, out var unloaded))
ISpriteFrame[] unloaded;
if (!unloadedFrames.TryGetValue(filename, out unloaded))
unloaded = null;
// This is the first time that the file has been requested
@@ -84,7 +85,8 @@ namespace OpenRA.Graphics
// the loaded cache (initially empty)
if (sprite == null)
{
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out var fileMetadata);
TypeDictionary fileMetadata = null;
unloaded = FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
unloadedFrames[filename] = unloaded;
metadata[filename] = fileMetadata;
@@ -123,7 +125,8 @@ namespace OpenRA.Graphics
/// </summary>
public TypeDictionary FrameMetadata(string filename)
{
if (!metadata.TryGetValue(filename, out var fileMetadata))
TypeDictionary fileMetadata;
if (!metadata.TryGetValue(filename, out fileMetadata))
{
FrameLoader.GetFrames(fileSystem, filename, loaders, out fileMetadata);
metadata[filename] = fileMetadata;
@@ -139,7 +142,8 @@ namespace OpenRA.Graphics
public FrameCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
TypeDictionary metadata;
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out metadata));
}
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
@@ -161,10 +165,11 @@ namespace OpenRA.Graphics
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
{
ISpriteFrame[] frames;
metadata = null;
foreach (var loader in loaders)
if (loader.TryParseSprite(stream, out var frames, out metadata))
if (loader.TryParseSprite(stream, out frames, out metadata))
return frames;
return null;

View File

@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
public struct SpriteRenderable : IRenderable, IFinalizedRenderable
{
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
@@ -24,17 +24,9 @@ namespace OpenRA.Graphics
readonly int zOffset;
readonly PaletteReference palette;
readonly float scale;
readonly float3 tint;
readonly bool isDecoration;
readonly bool ignoreWorldTint;
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
{
this.sprite = sprite;
this.pos = pos;
@@ -42,9 +34,7 @@ namespace OpenRA.Graphics
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.tint = tint;
this.isDecoration = isDecoration;
this.ignoreWorldTint = ignoreWorldTint;
}
public WPos Pos { get { return pos + offset; } }
@@ -53,12 +43,10 @@ namespace OpenRA.Graphics
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return isDecoration; } }
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, isDecoration); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, isDecoration); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, isDecoration); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, true); }
float3 ScreenPosition(WorldRenderer wr)
{
@@ -71,17 +59,7 @@ namespace OpenRA.Graphics
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var wsr = Game.Renderer.WorldSpriteRenderer;
if (ignoreWorldTint)
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
else
{
var t = tint;
if (wr.TerrainLighting != null)
t *= wr.TerrainLighting.TintAt(pos);
wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
}
Game.Renderer.WorldSpriteRenderer.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
}
public void RenderDebugGeometry(WorldRenderer wr)

View File

@@ -10,21 +10,17 @@
#endregion
using System;
using System.Linq;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class SpriteRenderer : Renderer.IBatchRenderer
{
const int SheetCount = 7;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
readonly Renderer renderer;
readonly IShader shader;
readonly Vertex[] vertices;
readonly Sheet[] sheets = new Sheet[SheetCount];
readonly Sheet[] sheets = new Sheet[7];
BlendMode currentBlend = BlendMode.Alpha;
int nv = 0;
@@ -43,7 +39,7 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < ns; i++)
{
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
sheets[i] = null;
}
@@ -108,46 +104,27 @@ namespace OpenRA.Graphics
return new int2(sheetIndex, secondarySheetIndex);
}
internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size);
nv += 6;
}
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
{
DrawSprite(s, location, pal.TextureIndex, s.Size);
}
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
{
DrawSprite(s, location, pal.TextureIndex, size);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
nv += 6;
}
internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
nv += 6;
}
public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
{
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
}
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, nv);
nv += 6;
}

View File

@@ -18,8 +18,6 @@ namespace OpenRA.Graphics
{
public sealed class TerrainSpriteLayer : IDisposable
{
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
public readonly Sheet Sheet;
public readonly BlendMode BlendMode;
@@ -27,7 +25,6 @@ namespace OpenRA.Graphics
readonly IVertexBuffer<Vertex> vertexBuffer;
readonly Vertex[] vertices;
readonly bool[] ignoreTint;
readonly HashSet<int> dirtyRows = new HashSet<int>();
readonly int rowStride;
readonly bool restrictToBounds;
@@ -53,12 +50,6 @@ namespace OpenRA.Graphics
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
wr.PaletteInvalidated += UpdatePaletteIndices;
if (wr.TerrainLighting != null)
{
ignoreTint = new bool[rowStride * map.MapSize.Y];
wr.TerrainLighting.CellChanged += UpdateTint;
}
}
void UpdatePaletteIndices()
@@ -68,76 +59,22 @@ namespace OpenRA.Graphics
for (var i = 0; i < vertices.Length; i++)
{
var v = vertices[i];
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C);
}
for (var row = 0; row < map.MapSize.Y; row++)
dirtyRows.Add(row);
}
public void Clear(CPos cell)
public void Update(CPos cell, Sprite sprite)
{
Update(cell, null, true);
var xyz = sprite == null ? float3.Zero :
worldRenderer.Screen3DPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size;
Update(cell.ToMPos(map.Grid.Type), sprite, xyz);
}
public void Update(CPos cell, ISpriteSequence sequence, int frame)
{
Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
}
public void Update(CPos cell, Sprite sprite, bool ignoreTint)
{
var xyz = float3.Zero;
if (sprite != null)
{
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
}
Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
}
void UpdateTint(MPos uv)
{
var offset = rowStride * uv.V + 6 * uv.U;
if (ignoreTint[offset])
{
var noTint = float3.Ones;
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
}
return;
}
// Allow the terrain tint to vary linearly across the cell to smooth out the staircase effect
// This is done by sampling the lighting the corners of the sprite, even though those pixels are
// transparent for isometric tiles
var tl = worldRenderer.TerrainLighting;
var pos = map.CenterOfCell(uv.ToCPos(map));
var step = map.Grid.Type == MapGridType.RectangularIsometric ? 724 : 512;
var weights = new[]
{
tl.TintAt(pos + new WVec(-step, -step, 0)),
tl.TintAt(pos + new WVec(step, -step, 0)),
tl.TintAt(pos + new WVec(step, step, 0)),
tl.TintAt(pos + new WVec(-step, step, 0))
};
// Apply tint directly to the underlying vertices
// This saves us from having to re-query the sprite information, which has not changed
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
}
dirtyRows.Add(uv.V);
}
public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
public void Update(MPos uv, Sprite sprite, float3 pos)
{
if (sprite != null)
{
@@ -155,13 +92,7 @@ namespace OpenRA.Graphics
return;
var offset = rowStride * uv.V + 6 * uv.U;
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
if (worldRenderer.TerrainLighting != null)
{
this.ignoreTint[offset] = ignoreTint;
UpdateTint(uv);
}
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size);
dirtyRows.Add(uv.V);
}
@@ -183,7 +114,15 @@ namespace OpenRA.Graphics
continue;
var rowOffset = rowStride * row;
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
unsafe
{
// The compiler / language spec won't let us calculate a pointer to
// an offset inside a generic array T[], and so we are forced to
// calculate the start-of-row pointer here to pass in to SetData.
fixed (Vertex* vPtr = &vertices[0])
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
}
}
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
@@ -196,9 +135,6 @@ namespace OpenRA.Graphics
public void Dispose()
{
worldRenderer.PaletteInvalidated -= UpdatePaletteIndices;
if (worldRenderer.TerrainLighting != null)
worldRenderer.TerrainLighting.CellChanged -= UpdateTint;
vertexBuffer.Dispose();
}
}

View File

@@ -40,7 +40,7 @@ namespace OpenRA.Graphics
readonly MersenneTwister random;
TileSet tileset;
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
public Theater(TileSet tileset)
{
this.tileset = tileset;
var allocated = false;
@@ -63,31 +63,9 @@ namespace OpenRA.Graphics
foreach (var i in t.Value.Images)
{
ISpriteFrame[] allFrames;
if (onMissingImage != null)
{
try
{
allFrames = frameCache[i];
}
catch (FileNotFoundException)
{
onMissingImage(t.Key, i);
continue;
}
}
else
allFrames = frameCache[i];
var allFrames = frameCache[i];
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j);
var start = indices.Min();
var end = indices.Max();
if (start < 0 || end >= frameCount)
throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist"
.F(t.Key, start, end, i, frameCount - 1));
var indices = t.Value.Frames != null ? t.Value.Frames : Enumerable.Range(0, frameCount);
variants.Add(indices.Select(j =>
{
var f = allFrames[j];
@@ -104,7 +82,7 @@ namespace OpenRA.Graphics
if (sheetBuilder == null)
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
else if (type != sheetBuilder.Type)
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
throw new InvalidDataException("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);
@@ -129,9 +107,6 @@ namespace OpenRA.Graphics
if (tileset.IgnoreTileSpriteOffsets)
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
if (onMissingImage != null && !variants.Any())
continue;
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
}
@@ -141,14 +116,10 @@ namespace OpenRA.Graphics
Sheet.ReleaseBuffer();
}
public bool HasTileSprite(TerrainTile r, int? variant = null)
{
return TileSprite(r, variant) != missingTile;
}
public Sprite TileSprite(TerrainTile r, int? variant = null)
{
if (!templates.TryGetValue(r.Type, out var template))
TheaterTemplate template;
if (!templates.TryGetValue(r.Type, out template))
return missingTile;
if (r.Index >= template.Stride)

View File

@@ -20,18 +20,15 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint)
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size)
{
var b = new float3(o.X + size.X, o.Y, o.Z);
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, nv);
}
public static void FastCreateQuad(Vertex[] vertices,
in float3 a, in float3 b, in float3 c, in float3 d,
Sprite r, int2 samplers, float paletteTextureIndex,
in float3 tint, int nv)
public static void FastCreateQuad(Vertex[] vertices, float3 a, float3 b, float3 c, float3 d, Sprite r, int2 samplers, float paletteTextureIndex, int nv)
{
float sl = 0;
float st = 0;
@@ -54,12 +51,12 @@ namespace OpenRA.Graphics
}
var fAttribC = (float)attribC;
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC);
}
public static void FastCopyIntoChannel(Sprite dest, byte[] src)

View File

@@ -16,34 +16,17 @@ namespace OpenRA.Graphics
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
// 3d position
public readonly float X, Y, Z;
public readonly float X, Y, Z, S, T, U, V, P, C;
// Primary and secondary texture coordinates or RGBA color
public readonly float S, T, U, V;
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c) { }
// Palette and channel flags
public readonly float P, C;
// Color tint
public readonly float R, G, B;
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b)
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c)
{
X = x; Y = y; Z = z;
S = s; T = t;
U = u; V = v;
P = p; C = c;
R = r; G = g; B = b;
}
}
}

View File

@@ -89,22 +89,12 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get { return minZoom; } }
public void AdjustZoom(float dz)
{
// Exponential ensures that equal positive and negative steps have the same effect
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
}
public void AdjustZoom(float dz, int2 center)
{
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
AdjustZoom(dz);
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
CenterLocation += oldCenter - newCenter;
}
public void ToggleZoom()
{
// Unlocked zooms always reset to the default zoom
@@ -262,6 +252,7 @@ namespace OpenRA.Graphics
var world = worldRenderer.Viewport.ViewToWorldPx(view);
var map = worldRenderer.World.Map;
var candidates = CandidateMouseoverCells(world).ToList();
var tileSet = worldRenderer.World.Map.Rules.TileSet;
foreach (var uv in candidates)
{
@@ -270,9 +261,18 @@ namespace OpenRA.Graphics
var s = worldRenderer.ScreenPxPosition(p);
if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height)
{
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
var ramp = 0;
if (map.Contains(uv))
{
var ti = tileSet.GetTileInfo(map.Tiles[uv]);
if (ti != null)
ramp = ti.RampType;
}
var corners = map.Grid.CellCorners[ramp];
var pos = map.CenterOfCell(uv.ToCPos(map));
var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
if (screen.PolygonContains(world))
return uv.ToCPos(map);
}
@@ -282,7 +282,7 @@ namespace OpenRA.Graphics
// Try and find the closest cell
if (candidates.Count > 0)
{
return candidates.MinBy(uv =>
return candidates.OrderBy(uv =>
{
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
var s = worldRenderer.ScreenPxPosition(p);
@@ -290,7 +290,7 @@ namespace OpenRA.Graphics
var dy = Math.Abs(s.Y - world.Y);
return dx * dx + dy * dy;
}).ToCPos(map);
}).First().ToCPos(map);
}
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
@@ -314,7 +314,7 @@ namespace OpenRA.Graphics
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
public int2 WorldToViewPx(float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
public void Center(IEnumerable<Actor> actors)
{

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Graphics
{
public sealed class WorldRenderer : IDisposable
{
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
r => ZPosition(r.Pos, r.ZOffset);
public readonly Size TileSize;
@@ -28,7 +28,6 @@ namespace OpenRA.Graphics
public readonly World World;
public readonly Theater Theater;
public Viewport Viewport { get; private set; }
public readonly ITerrainLighting TerrainLighting;
public event Action PaletteInvalidated = null;
@@ -44,8 +43,6 @@ namespace OpenRA.Graphics
readonly List<IFinalizedRenderable> preparedOverlayRenderables = new List<IFinalizedRenderable>();
readonly List<IFinalizedRenderable> preparedAnnotationRenderables = new List<IFinalizedRenderable>();
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
bool lastDepthPreviewEnabled;
internal WorldRenderer(ModData modData, World world)
@@ -69,7 +66,6 @@ namespace OpenRA.Graphics
palette.Initialize();
Theater = new Theater(world.Map.Rules.TileSet);
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
debugVis = Exts.Lazy(() => world.WorldActor.TraitOrDefault<DebugVisualizations>());
@@ -97,8 +93,8 @@ namespace OpenRA.Graphics
var oldHeight = palette.Height;
palette.AddPalette(name, pal, allowModifiers);
if (oldHeight != palette.Height)
PaletteInvalidated?.Invoke();
if (oldHeight != palette.Height && PaletteInvalidated != null)
PaletteInvalidated();
}
}
@@ -111,118 +107,78 @@ namespace OpenRA.Graphics
palettes[name].Palette = pal;
}
// PERF: Avoid LINQ.
void GenerateRenderables()
IEnumerable<IFinalizedRenderable> GenerateRenderables()
{
foreach (var actor in onScreenActors)
renderablesBuffer.AddRange(actor.Render(this));
renderablesBuffer.AddRange(World.WorldActor.Render(this));
var actors = onScreenActors.Append(World.WorldActor);
if (World.RenderPlayer != null)
renderablesBuffer.AddRange(World.RenderPlayer.PlayerActor.Render(this));
actors = actors.Append(World.RenderPlayer.PlayerActor);
var worldRenderables = actors.SelectMany(a => a.Render(this));
if (World.OrderGenerator != null)
renderablesBuffer.AddRange(World.OrderGenerator.Render(this, World));
worldRenderables = worldRenderables.Concat(World.OrderGenerator.Render(this, World));
// Unpartitioned effects
foreach (var e in World.UnpartitionedEffects)
renderablesBuffer.AddRange(e.Render(this));
worldRenderables = worldRenderables.Concat(World.UnpartitionedEffects.SelectMany(e => e.Render(this)));
// Partitioned, currently on-screen effects
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
renderablesBuffer.AddRange(e.Render(this));
var effectRenderables = World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight);
worldRenderables = worldRenderables.Concat(effectRenderables.SelectMany(e => e.Render(this)));
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
preparedRenderables.Add(renderable.PrepareRender(this));
worldRenderables = worldRenderables.OrderBy(RenderableScreenZPositionComparisonKey);
// PERF: Reuse collection to avoid allocations.
renderablesBuffer.Clear();
return worldRenderables.Select(r => r.PrepareRender(this));
}
// PERF: Avoid LINQ.
void GenerateOverlayRenderables()
IEnumerable<IFinalizedRenderable> GenerateOverlayRenderables()
{
foreach (var a in World.ActorsWithTrait<IRenderAboveShroud>())
{
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
continue;
var actors = World.ActorsWithTrait<IRenderAboveShroud>()
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
.SelectMany(a => a.Trait.RenderAboveShroud(a.Actor, this));
foreach (var renderable in a.Trait.RenderAboveShroud(a.Actor, this))
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
}
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
.SelectMany(a => a.TraitsImplementing<IRenderAboveShroudWhenSelected>()
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
.SelectMany(t => t.RenderAboveShroud(a, this)));
foreach (var a in World.Selection.Actors)
{
if (!a.IsInWorld || a.Disposed)
continue;
foreach (var t in a.TraitsImplementing<IRenderAboveShroudWhenSelected>())
{
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
continue;
foreach (var renderable in t.RenderAboveShroud(a, this))
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
}
}
foreach (var e in World.Effects)
{
var ea = e as IEffectAboveShroud;
if (ea == null)
continue;
foreach (var renderable in ea.RenderAboveShroud(this))
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
}
var effects = World.Effects.Select(e => e as IEffectAboveShroud)
.Where(e => e != null)
.SelectMany(e => e.RenderAboveShroud(this));
var orderGenerator = SpriteRenderable.None;
if (World.OrderGenerator != null)
foreach (var renderable in World.OrderGenerator.RenderAboveShroud(this, World))
preparedOverlayRenderables.Add(renderable.PrepareRender(this));
orderGenerator = World.OrderGenerator.RenderAboveShroud(this, World);
return actors
.Concat(selected)
.Concat(effects)
.Concat(orderGenerator)
.Select(r => r.PrepareRender(this));
}
// PERF: Avoid LINQ.
void GenerateAnnotationRenderables()
IEnumerable<IFinalizedRenderable> GenerateAnnotationRenderables()
{
foreach (var a in World.ActorsWithTrait<IRenderAnnotations>())
{
if (!a.Actor.IsInWorld || a.Actor.Disposed || (a.Trait.SpatiallyPartitionable && !onScreenActors.Contains(a.Actor)))
continue;
var actors = World.ActorsWithTrait<IRenderAnnotations>()
.Where(a => a.Actor.IsInWorld && !a.Actor.Disposed && (!a.Trait.SpatiallyPartitionable || onScreenActors.Contains(a.Actor)))
.SelectMany(a => a.Trait.RenderAnnotations(a.Actor, this));
foreach (var renderAnnotation in a.Trait.RenderAnnotations(a.Actor, this))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
}
var selected = World.Selection.Actors.Where(a => a.IsInWorld && !a.Disposed)
.SelectMany(a => a.TraitsImplementing<IRenderAnnotationsWhenSelected>()
.Where(t => !t.SpatiallyPartitionable || onScreenActors.Contains(a))
.SelectMany(t => t.RenderAnnotations(a, this)));
foreach (var a in World.Selection.Actors)
{
if (!a.IsInWorld || a.Disposed)
continue;
foreach (var t in a.TraitsImplementing<IRenderAnnotationsWhenSelected>())
{
if (t.SpatiallyPartitionable && !onScreenActors.Contains(a))
continue;
foreach (var renderAnnotation in t.RenderAnnotations(a, this))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
}
}
foreach (var e in World.Effects)
{
var ea = e as IEffectAnnotation;
if (ea == null)
continue;
foreach (var renderAnnotation in ea.RenderAnnotation(this))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
}
var effects = World.Effects.Select(e => e as IEffectAnnotation)
.Where(e => e != null)
.SelectMany(e => e.RenderAnnotation(this));
var orderGenerator = SpriteRenderable.None;
if (World.OrderGenerator != null)
foreach (var renderAnnotation in World.OrderGenerator.RenderAnnotations(this, World))
preparedAnnotationRenderables.Add(renderAnnotation.PrepareRender(this));
orderGenerator = World.OrderGenerator.RenderAnnotations(this, World);
return actors
.Concat(selected)
.Concat(effects)
.Concat(orderGenerator)
.Select(r => r.PrepareRender(this));
}
public void PrepareRenderables()
@@ -234,11 +190,9 @@ namespace OpenRA.Graphics
// PERF: Reuse collection to avoid allocations.
onScreenActors.UnionWith(World.ScreenMap.RenderableActorsInBox(Viewport.TopLeft, Viewport.BottomRight));
GenerateRenderables();
GenerateOverlayRenderables();
GenerateAnnotationRenderables();
preparedRenderables.AddRange(GenerateRenderables());
preparedOverlayRenderables.AddRange(GenerateOverlayRenderables());
preparedAnnotationRenderables.AddRange(GenerateAnnotationRenderables());
onScreenActors.Clear();
}
@@ -259,7 +213,8 @@ namespace OpenRA.Graphics
if (enableDepthBuffer)
Game.Renderer.Context.EnableDepthBuffer();
terrainRenderer?.RenderTerrain(this, Viewport);
if (terrainRenderer != null)
terrainRenderer.RenderTerrain(this, Viewport);
Game.Renderer.Flush();
@@ -322,16 +277,11 @@ namespace OpenRA.Graphics
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
}
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
{
var points = new float2[b.Vertices.Length];
for (var index = 0; index < b.Vertices.Length; index++)
{
var vertex = b.Vertices[index];
points[index] = Viewport.WorldToViewPx(vertex).ToFloat2();
}
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.OrangeRed);
}
}

View File

@@ -50,7 +50,8 @@ namespace OpenRA
return () => keys[name];
// Try and parse as a hardcoded definition
if (!Hotkey.TryParse(name, out var key))
Hotkey key;
if (!Hotkey.TryParse(name, out key))
key = Hotkey.Invalid;
return () => key;
@@ -58,7 +59,8 @@ namespace OpenRA
public void Set(string name, Hotkey value)
{
if (!definitions.TryGetValue(name, out var definition))
HotkeyDefinition definition;
if (!definitions.TryGetValue(name, out definition))
return;
keys[name] = value;

View File

@@ -32,9 +32,11 @@ namespace OpenRA
var parts = s.Split(' ');
if (!Enum<Keycode>.TryParse(parts[0], true, out var key))
Keycode key;
if (!Enum<Keycode>.TryParse(parts[0], true, out key))
{
if (!int.TryParse(parts[0], out var c))
int c;
if (!int.TryParse(parts[0], out c))
return false;
key = (Keycode)c;
}

View File

@@ -498,7 +498,8 @@ namespace OpenRA
public static string DisplayString(Keycode k)
{
if (!KeyNames.TryGetValue(k, out var ret))
string ret;
if (!KeyNames.TryGetValue(k, out ret))
return k.ToString();
return ret;

View File

@@ -14,6 +14,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
@@ -34,9 +35,9 @@ namespace OpenRA
mods = GetInstalledMods(searchPaths, explicitPaths);
}
static IEnumerable<(string Id, string Path)> GetCandidateMods(IEnumerable<string> searchPaths)
static IEnumerable<Pair<string, string>> GetCandidateMods(IEnumerable<string> searchPaths)
{
var mods = new List<(string, string)>();
var mods = new List<Pair<string, string>>();
foreach (var path in searchPaths)
{
try
@@ -47,7 +48,7 @@ namespace OpenRA
var directory = new DirectoryInfo(resolved);
foreach (var subdir in directory.EnumerateDirectories())
mods.Add((subdir.Name, subdir.FullName));
mods.Add(Pair.New(subdir.Name, subdir.FullName));
}
catch (Exception e)
{
@@ -78,7 +79,8 @@ namespace OpenRA
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
}
package?.Dispose();
if (package != null)
package.Dispose();
return null;
}
@@ -87,13 +89,13 @@ namespace OpenRA
{
var ret = new Dictionary<string, Manifest>();
var candidates = GetCandidateMods(searchPaths)
.Concat(explicitPaths.Select(p => (Id: Path.GetFileNameWithoutExtension(p), Path: p)));
.Concat(explicitPaths.Select(p => Pair.New(Path.GetFileNameWithoutExtension(p), p)));
foreach (var pair in candidates)
{
var mod = LoadMod(pair.Id, pair.Path);
var mod = LoadMod(pair.First, pair.Second);
if (mod != null)
ret[pair.Id] = mod;
ret[pair.First] = mod;
}
return ret;

View File

@@ -80,6 +80,8 @@ namespace OpenRA
{
try
{
innerState = LinkState.Unlinked;
if (i.Error != null)
{
innerState = LinkState.ConnectionFailed;
@@ -98,8 +100,6 @@ namespace OpenRA
else
innerState = LinkState.Linked;
}
else
innerState = LinkState.Unlinked;
}
catch (Exception e)
{
@@ -108,7 +108,8 @@ namespace OpenRA
}
finally
{
onComplete?.Invoke();
if (onComplete != null)
onComplete();
}
};

View File

@@ -52,7 +52,7 @@ namespace OpenRA
}
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
public class Manifest : IDisposable
public class Manifest
{
public readonly string Id;
public readonly IReadOnlyPackage Package;
@@ -73,7 +73,7 @@ namespace OpenRA
readonly string[] reservedModuleNames =
{
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
@@ -89,32 +89,15 @@ namespace OpenRA
{
Id = modId;
Package = package;
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
for (var i = nodes.Count - 1; i >= 0; i--)
{
if (nodes[i].Key != "Include")
continue;
// Replace `Includes: filename.yaml` with the contents of filename.yaml
var filename = nodes[i].Value.Value;
var contents = package.GetStream(filename);
if (contents == null)
throw new YamlException("{0}: File `{1}` not found.".F(nodes[i].Location, filename));
nodes.RemoveAt(i);
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
}
// Merge inherited overrides
yaml = new MiniYaml(null, MiniYaml.Merge(new[] { nodes })).ToDictionary();
yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml")).ToDictionary();
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);
// TODO: Use fieldloader
MapFolders = YamlDictionary(yaml, "MapFolders");
if (yaml.TryGetValue("Packages", out var packages))
MiniYaml packages;
if (yaml.TryGetValue("Packages", out packages))
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
Rules = YamlList(yaml, "Rules");
@@ -234,8 +217,9 @@ namespace OpenRA
/// </summary>
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
{
MiniYaml data;
var t = typeof(T);
if (!yaml.TryGetValue(t.Name, out var data))
if (!yaml.TryGetValue(t.Name, out data))
{
// Lazily create the default values if not explicitly defined.
return (T)oc.CreateBasic(t);
@@ -257,14 +241,5 @@ namespace OpenRA
return (T)module;
}
public void Dispose()
{
foreach (var module in modules)
{
var disposableModule = module as IDisposable;
disposableModule?.Dispose();
}
}
}
}

View File

@@ -9,29 +9,17 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
public interface IActorInitializer
{
World World { get; }
T GetOrDefault<T>(TraitInfo info) where T : ActorInit;
T Get<T>(TraitInfo info) where T : ActorInit;
U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>;
U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>;
bool Contains<T>(TraitInfo info) where T : ActorInit;
T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit;
T Get<T>() where T : ActorInit, ISingleInstanceInit;
U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit;
U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit;
bool Contains<T>() where T : ActorInit, ISingleInstanceInit;
T Get<T>() where T : IActorInit;
U Get<T, U>() where T : IActorInit<U>;
bool Contains<T>() where T : IActorInit;
}
public class ActorInitializer : IActorInitializer
@@ -47,228 +35,50 @@ namespace OpenRA
Dict = dict;
}
public T GetOrDefault<T>(TraitInfo info) where T : ActorInit
{
var inits = Dict.WithInterface<T>();
// Traits tagged with an instance name prefer inits with the same name.
// If a more specific init is not available, fall back to an unnamed init.
// If duplicate inits are defined, take the last to match standard yaml override expectations
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
// Untagged traits will only use untagged inits
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
}
public T Get<T>(TraitInfo info) where T : ActorInit
{
var init = GetOrDefault<T>(info);
if (init == null)
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
return init;
}
public U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>
{
return Get<T>(info).Value;
}
public U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>
{
var init = GetOrDefault<T>(info);
return init != null ? init.Value : fallback;
}
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
{
return Dict.GetOrDefault<T>();
}
public T Get<T>() where T : ActorInit, ISingleInstanceInit
{
return Dict.Get<T>();
}
public U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit
{
return Get<T>().Value;
}
public U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit
{
var init = GetOrDefault<T>();
return init != null ? init.Value : fallback;
}
public bool Contains<T>() where T : ActorInit, ISingleInstanceInit { return GetOrDefault<T>() != null; }
public T Get<T>() where T : IActorInit { return Dict.Get<T>(); }
public U Get<T, U>() where T : IActorInit<U> { return Dict.Get<T>().Value(World); }
public bool Contains<T>() where T : IActorInit { return Dict.Contains<T>(); }
}
/*
* Things to be aware of when writing ActorInits:
*
* - ActorReference and ActorGlobal can dynamically create objects without calling a constructor.
* The object will be allocated directly then the best matching Initialize() method will be called to set valid state.
* - ActorReference will always attempt to call Initialize(MiniYaml). ActorGlobal will use whichever one it first
* finds with an argument type that matches the given LuaValue.
* - Most ActorInits will want to inherit either ValueActorInit<T> or CompositeActorInit which hide the low-level plumbing.
* - Inits that reference actors should use ActorInitActorReference which allows actors to be referenced by name in map.yaml
* - Inits that should only have a single instance defined on an actor should implement ISingleInstanceInit to allow
* direct queries and runtime enforcement.
* - Inits that aren't ISingleInstanceInit should expose a ctor that accepts a TraitInfo to allow per-trait targeting.
*/
public abstract class ActorInit
public interface IActorInit { }
public interface IActorInit<T> : IActorInit
{
[FieldLoader.Ignore]
public readonly string InstanceName;
protected ActorInit(string instanceName)
{
InstanceName = instanceName;
}
protected ActorInit() { }
public abstract MiniYaml Save();
T Value(World world);
}
public interface ISingleInstanceInit { }
public abstract class ValueActorInit<T> : ActorInit
public class LocationInit : IActorInit<CPos>
{
protected readonly T value;
[FieldFromYamlKey]
readonly CPos value = CPos.Zero;
protected ValueActorInit(TraitInfo info, T value)
: base(info.InstanceName) { this.value = value; }
protected ValueActorInit(string instanceName, T value)
: base(instanceName) { this.value = value; }
protected ValueActorInit(T value) { this.value = value; }
public virtual T Value { get { return value; } }
public virtual void Initialize(MiniYaml yaml)
{
Initialize((T)FieldLoader.GetValue("value", typeof(T), yaml.Value));
}
public virtual void Initialize(T value)
{
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, value);
}
public override MiniYaml Save()
{
return new MiniYaml(FieldSaver.FormatValue(value));
}
public LocationInit() { }
public LocationInit(CPos init) { value = init; }
public CPos Value(World world) { return value; }
}
public abstract class CompositeActorInit : ActorInit
public class OwnerInit : IActorInit<Player>
{
protected CompositeActorInit(TraitInfo info)
: base(info.InstanceName) { }
[FieldFromYamlKey]
public readonly string PlayerName = "Neutral";
protected CompositeActorInit()
: base() { }
Player player;
public virtual void Initialize(MiniYaml yaml)
public OwnerInit() { }
public OwnerInit(string playerName) { PlayerName = playerName; }
public OwnerInit(Player player)
{
FieldLoader.Load(this, yaml);
}
public virtual void Initialize(Dictionary<string, object> values)
{
foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var sa = field.GetCustomAttributes<FieldLoader.SerializeAttribute>(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First();
if (!sa.Serialize)
continue;
if (values.TryGetValue(field.Name, out var value))
field.SetValue(this, value);
}
}
public virtual Dictionary<string, Type> InitializeArgs()
{
var dict = new Dictionary<string, Type>();
foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var sa = field.GetCustomAttributes<FieldLoader.SerializeAttribute>(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First();
if (!sa.Serialize)
continue;
dict[field.Name] = field.FieldType;
}
return dict;
}
public override MiniYaml Save()
{
return FieldSaver.Save(this);
}
}
public class LocationInit : ValueActorInit<CPos>, ISingleInstanceInit
{
public LocationInit(CPos value)
: base(value) { }
}
public class OwnerInit : ActorInit, ISingleInstanceInit
{
public readonly string InternalName;
protected readonly Player value;
public OwnerInit(Player value)
{
this.value = value;
InternalName = value.InternalName;
}
public OwnerInit(string value)
{
InternalName = value;
this.player = player;
PlayerName = player.InternalName;
}
public Player Value(World world)
{
return value ?? world.Players.First(x => x.InternalName == InternalName);
}
if (player != null)
return player;
public void Initialize(MiniYaml yaml)
{
var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
if (field != null)
field.SetValue(this, yaml.Value);
}
public void Initialize(Player player)
{
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, player);
}
public override MiniYaml Save()
{
return new MiniYaml(InternalName);
}
}
public abstract class RuntimeFlagInit : ActorInit, ISuppressInitExport
{
public override MiniYaml Save()
{
throw new NotImplementedException("RuntimeFlagInit cannot be saved");
return world.Players.First(x => x.InternalName == PlayerName);
}
}
}

View File

@@ -12,11 +12,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
@@ -25,9 +21,12 @@ namespace OpenRA
public class ActorReference : IEnumerable
{
public string Type;
Lazy<TypeDictionary> initDict;
public TypeDictionary InitDict
{
get { return initDict.Value; }
}
internal TypeDictionary InitDict { get { return initDict.Value; } }
Lazy<TypeDictionary> initDict;
public ActorReference(string type)
: this(type, new Dictionary<string, MiniYaml>()) { }
@@ -39,168 +38,47 @@ namespace OpenRA
{
var dict = new TypeDictionary();
foreach (var i in inits)
{
var init = LoadInit(i.Key, i.Value);
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
dict.Add(init);
}
dict.Add(LoadInit(i.Key, i.Value));
return dict;
});
}
public ActorReference(string type, TypeDictionary inits)
static IActorInit LoadInit(string traitName, MiniYaml my)
{
Type = type;
initDict = new Lazy<TypeDictionary>(() =>
{
var dict = new TypeDictionary();
foreach (var i in inits)
dict.Add(i);
return dict;
});
var info = Game.CreateObject<IActorInit>(traitName + "Init");
FieldLoader.Load(info, my);
return info;
}
static ActorInit LoadInit(string initName, MiniYaml initYaml)
{
var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator);
var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init");
if (type == null)
throw new InvalidDataException("Unknown initializer type '{0}Init'".F(initInstance[0]));
var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
if (initInstance.Length > 1)
type.GetField("InstanceName").SetValue(init, initInstance[1]);
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
if (loader == null)
throw new InvalidDataException("{0}Init does not define a yaml-assignable type.".F(initInstance[0]));
loader.Invoke(init, new[] { initYaml });
return init;
}
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
public MiniYaml Save(Func<object, bool> initFilter = null)
{
var ret = new MiniYaml(Type);
foreach (var o in initDict.Value)
foreach (var init in InitDict)
{
var init = o as ActorInit;
if (init == null || o is ISuppressInitExport)
if (init is ISuppressInitExport)
continue;
if (initFilter != null && !initFilter(init))
continue;
var initTypeName = init.GetType().Name;
var initName = initTypeName.Substring(0, initTypeName.Length - 4);
if (!string.IsNullOrEmpty(init.InstanceName))
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
var initName = init.GetType().Name;
ret.Nodes.Add(new MiniYamlNode(initName.Substring(0, initName.Length - 4), FieldSaver.Save(init)));
}
return ret;
}
public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); }
// for initialization syntax
public void Add(object o) { InitDict.Add(o); }
public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); }
public ActorReference Clone()
{
var clone = new ActorReference(Type);
foreach (var init in initDict.Value)
clone.initDict.Value.Add(init);
foreach (var init in InitDict)
clone.InitDict.Add(init);
return clone;
}
public void Add(ActorInit init)
{
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
InitDict.Add(init);
}
public void Remove(ActorInit o) { initDict.Value.Remove(o); }
public int RemoveAll<T>() where T : ActorInit
{
var removed = 0;
foreach (var o in initDict.Value.WithInterface<T>().ToList())
{
removed++;
initDict.Value.Remove(o);
}
return removed;
}
public IEnumerable<T> GetAll<T>() where T : ActorInit
{
return initDict.Value.WithInterface<T>();
}
public T GetOrDefault<T>(TraitInfo info) where T : ActorInit
{
var inits = initDict.Value.WithInterface<T>();
// Traits tagged with an instance name prefer inits with the same name.
// If a more specific init is not available, fall back to an unnamed init.
// If duplicate inits are defined, take the last to match standard yaml override expectations
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
// Untagged traits will only use untagged inits
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
}
public T Get<T>(TraitInfo info) where T : ActorInit
{
var init = GetOrDefault<T>(info);
if (init == null)
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
return init;
}
public U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>
{
return Get<T>(info).Value;
}
public U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>
{
var init = GetOrDefault<T>(info);
return init != null ? init.Value : fallback;
}
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
{
return initDict.Value.GetOrDefault<T>();
}
public T Get<T>() where T : ActorInit, ISingleInstanceInit
{
return initDict.Value.Get<T>();
}
public U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit
{
return Get<T>().Value;
}
public U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit
{
var init = GetOrDefault<T>();
return init != null ? init.Value : fallback;
}
public bool Contains<T>() where T : ActorInit, ISingleInstanceInit { return GetOrDefault<T>() != null; }
}
}

View File

@@ -10,28 +10,42 @@
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA
{
// Represents a layer of "something" that covers the map
public sealed class CellLayer<T> : CellLayerBase<T>
public class CellLayer<T> : IEnumerable<T>
{
public readonly Size Size;
readonly Rectangle bounds;
public readonly MapGridType GridType;
public event Action<CPos> CellEntryChanged = null;
readonly T[] entries;
public CellLayer(Map map)
: base(map) { }
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
public CellLayer(MapGridType gridType, Size size)
: base(gridType, size) { }
public override void CopyValuesFrom(CellLayerBase<T> anotherLayer)
{
Size = size;
bounds = new Rectangle(0, 0, Size.Width, Size.Height);
GridType = gridType;
entries = new T[size.Width * size.Height];
}
public void CopyValuesFrom(CellLayer<T> anotherLayer)
{
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
throw new ArgumentException(
"layers must have a matching size and shape (grid type).", "anotherLayer");
if (CellEntryChanged != null)
throw new InvalidOperationException(
"Cannot copy values when there are listeners attached to the CellEntryChanged event.");
base.CopyValuesFrom(anotherLayer);
Array.Copy(anotherLayer.entries, entries, entries.Length);
}
public static CellLayer<T> CreateInstance(Func<MPos, T> initialCellValueFactory, Size size, MapGridType mapGridType)
@@ -73,7 +87,8 @@ namespace OpenRA
{
entries[Index(cell)] = value;
CellEntryChanged?.Invoke(cell);
if (CellEntryChanged != null)
CellEntryChanged(cell);
}
}
@@ -89,10 +104,28 @@ namespace OpenRA
{
entries[Index(uv)] = value;
CellEntryChanged?.Invoke(uv.ToCPos(GridType));
if (CellEntryChanged != null)
CellEntryChanged(uv.ToCPos(GridType));
}
}
/// <summary>Clears the layer contents with a known value</summary>
public void Clear(T clearValue)
{
for (var i = 0; i < entries.Length; i++)
entries[i] = clearValue;
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)entries).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Contains(CPos cell)
{
// .ToMPos() returns the same result if the X and Y coordinates

View File

@@ -1,63 +0,0 @@
#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;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA
{
public abstract class CellLayerBase<T> : IEnumerable<T>
{
public readonly Size Size;
public readonly MapGridType GridType;
protected readonly T[] entries;
protected readonly Rectangle bounds;
public CellLayerBase(Map map)
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
public CellLayerBase(MapGridType gridType, Size size)
{
Size = size;
bounds = new Rectangle(0, 0, Size.Width, Size.Height);
GridType = gridType;
entries = new T[size.Width * size.Height];
}
public virtual void CopyValuesFrom(CellLayerBase<T> anotherLayer)
{
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
throw new ArgumentException("Layers must have a matching size and shape (grid type).", "anotherLayer");
Array.Copy(anotherLayer.entries, entries, entries.Length);
}
/// <summary>Clears the layer contents with a known value</summary>
public void Clear(T clearValue)
{
for (var i = 0; i < entries.Length; i++)
entries[i] = clearValue;
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)entries).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -117,7 +117,7 @@ namespace OpenRA
return GetEnumerator();
}
public struct CellRegionEnumerator : IEnumerator<CPos>
public sealed class CellRegionEnumerator : IEnumerator<CPos>
{
readonly CellRegion r;
@@ -128,11 +128,9 @@ namespace OpenRA
CPos current;
public CellRegionEnumerator(CellRegion region)
: this()
{
r = region;
Reset();
current = new MPos(u, v).ToCPos(r.gridType);
}
public bool MoveNext()

View File

@@ -230,10 +230,9 @@ namespace OpenRA
public CellLayer<TerrainTile> Tiles { get; private set; }
public CellLayer<ResourceTile> Resources { get; private set; }
public CellLayer<byte> Height { get; private set; }
public CellLayer<byte> Ramp { get; private set; }
public CellLayer<byte> CustomTerrain { get; private set; }
public PPos[] ProjectedCells { get; private set; }
public ProjectedCellRegion ProjectedCellBounds { get; private set; }
public CellRegion AllCells { get; private set; }
public List<CPos> AllEdgeCells { get; private set; }
@@ -302,12 +301,10 @@ namespace OpenRA
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
Height = new CellLayer<byte>(Grid.Type, size);
Ramp = new CellLayer<byte>(Grid.Type, size);
if (Grid.MaximumTerrainHeight > 0)
{
Height.CellEntryChanged += UpdateProjection;
Tiles.CellEntryChanged += UpdateProjection;
Tiles.CellEntryChanged += UpdateRamp;
}
Tiles.Clear(tileRef);
@@ -331,9 +328,6 @@ namespace OpenRA
throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, package.Name));
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
if (PlayerDefinitions.Count > 64)
throw new InvalidDataException("Maps must not define more than 64 players.\n File: {0}".F(package.Name));
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
Grid = modData.Manifest.Get<MapGrid>();
@@ -342,7 +336,6 @@ namespace OpenRA
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
Height = new CellLayer<byte>(Grid.Type, size);
Ramp = new CellLayer<byte>(Grid.Type, size);
using (var s = Package.GetStream("map.bin"))
{
@@ -391,7 +384,6 @@ namespace OpenRA
if (Grid.MaximumTerrainHeight > 0)
{
Tiles.CellEntryChanged += UpdateRamp;
Tiles.CellEntryChanged += UpdateProjection;
Height.CellEntryChanged += UpdateProjection;
}
@@ -430,23 +422,9 @@ namespace OpenRA
foreach (var uv in AllCells.MapCoords)
CustomTerrain[uv] = byte.MaxValue;
// Cache initial ramp state
var tileset = Rules.TileSet;
foreach (var uv in AllCells)
{
var tile = tileset.GetTileInfo(Tiles[uv]);
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
}
AllEdgeCells = UpdateEdgeCells();
}
void UpdateRamp(CPos cell)
{
var tile = Rules.TileSet.GetTileInfo(Tiles[cell]);
Ramp[cell] = tile != null ? tile.RampType : (byte)0;
}
void InitializeCellProjection()
{
if (initializedCellProjection)
@@ -553,8 +531,12 @@ namespace OpenRA
return new[] { (PPos)uv };
// Odd-height ramps get bumped up a level to the next even height layer
if ((height & 1) == 1 && Ramp[uv] != 0)
height += 1;
if ((height & 1) == 1)
{
var ti = Rules.TileSet.GetTileInfo(Tiles[uv]);
if (ti != null && ti.RampType != 0)
height += 1;
}
var candidates = new List<PPos>();
@@ -665,40 +647,12 @@ namespace OpenRA
return dataStream.ToArray();
}
public (Color Left, Color Right) GetTerrainColorPair(MPos uv)
{
Color left, right;
var tileset = Rules.TileSet;
var type = tileset.GetTileInfo(Tiles[uv]);
if (type != null)
{
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));
}
}
else
left = right = Color.Black;
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)>();
var positions = new List<Pair<MPos, Color>>();
foreach (var actor in actors)
{
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
@@ -732,18 +686,24 @@ namespace OpenRA
var stride = bitmapWidth * 4;
var pxStride = 4;
var minimapData = new byte[stride * height];
(Color Left, Color Right) terrainColor = default((Color, Color));
Color leftColor, rightColor;
for (var y = 0; y < height; y++)
{
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);
var actorsThere = positions.Where(ap => ap.First == uv);
if (actorsThere.Any())
{
leftColor = rightColor = actorsThere.First().Second;
}
else
{
// Cell contains terrain
var type = tileset.GetTileInfo(Tiles[uv]);
leftColor = type != null ? type.LeftColor : Color.Black;
rightColor = type != null ? type.RightColor : Color.Black;
}
if (isRectangularIsometric)
{
@@ -753,31 +713,28 @@ namespace OpenRA
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;
minimapData[z++] = leftColor.R;
minimapData[z++] = leftColor.G;
minimapData[z++] = leftColor.B;
minimapData[z++] = leftColor.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;
minimapData[z++] = rightColor.R;
minimapData[z++] = rightColor.G;
minimapData[z++] = rightColor.B;
minimapData[z++] = rightColor.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;
minimapData[z++] = leftColor.R;
minimapData[z++] = leftColor.G;
minimapData[z++] = leftColor.B;
minimapData[z++] = leftColor.A;
}
}
}
@@ -813,9 +770,8 @@ namespace OpenRA
bool ContainsAllProjectedCellsCovering(MPos uv)
{
// PERF: Checking the bounds directly here is the same as calling Contains((PPos)uv) but saves an allocation
if (Grid.MaximumTerrainHeight == 0)
return Bounds.Contains(uv.U, uv.V);
return Contains((PPos)uv);
// If the cell has no valid projection, then we're off the map.
var projectedCells = ProjectedCellsCovering(uv);
@@ -825,7 +781,6 @@ namespace OpenRA
foreach (var puv in projectedCells)
if (!Contains(puv))
return false;
return true;
}
@@ -850,7 +805,7 @@ namespace OpenRA
// (c) u, v coordinates run diagonally to the cell axes, and we define
// 1024 as the length projected onto the primary cell axis
// - 512 * sqrt(2) = 724
var z = Height.Contains(cell) ? 724 * Height[cell] + Grid.Ramps[Ramp[cell]].CenterHeightOffset : 0;
var z = Height.Contains(cell) ? 724 * Height[cell] : 0;
return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z);
}
@@ -858,42 +813,15 @@ namespace OpenRA
{
var index = (int)subCell;
if (index >= 0 && index < Grid.SubCellOffsets.Length)
{
var center = CenterOfCell(cell);
var offset = Grid.SubCellOffsets[index];
var ramp = Ramp.Contains(cell) ? Ramp[cell] : 0;
if (ramp != 0)
{
var r = Grid.Ramps[ramp];
offset += new WVec(0, 0, r.HeightOffset(offset.X, offset.Y) - r.CenterHeightOffset);
}
return center + offset;
}
return CenterOfCell(cell) + Grid.SubCellOffsets[index];
return CenterOfCell(cell);
}
public WDist DistanceAboveTerrain(WPos pos)
{
if (Grid.Type == MapGridType.Rectangular)
return new WDist(pos.Z);
// Apply ramp offset
var cell = CellContaining(pos);
var offset = pos - CenterOfCell(cell);
if (!Ramp.Contains(cell))
return new WDist(offset.Z);
var ramp = Ramp[cell];
if (ramp != 0)
{
var r = Grid.Ramps[ramp];
return new WDist(offset.Z + r.CenterHeightOffset - r.HeightOffset(offset.X, offset.Y));
}
return new WDist(offset.Z);
var delta = pos - CenterOfCell(cell);
return new WDist(delta.Z);
}
public WVec Offset(CVec delta, int dz)
@@ -971,13 +899,13 @@ namespace OpenRA
return projectedHeight[(MPos)puv];
}
public WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing)
public int FacingBetween(CPos cell, CPos towards, int fallbackfacing)
{
var delta = CenterOfCell(towards) - CenterOfCell(cell);
if (delta.HorizontalLengthSquared == 0)
return fallbackfacing;
return delta.Yaw;
return delta.Yaw.Facing;
}
public void Resize(int width, int height)
@@ -985,13 +913,11 @@ namespace OpenRA
var oldMapTiles = Tiles;
var oldMapResources = Resources;
var oldMapHeight = Height;
var oldMapRamp = Ramp;
var newSize = new Size(width, height);
Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]);
Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]);
Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]);
Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]);
MapSize = new int2(newSize);
var tl = new MPos(0, 0);
@@ -1021,8 +947,7 @@ namespace OpenRA
ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0);
}
// PERF: This enumeration isn't going to change during the game
ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray();
ProjectedCellBounds = new ProjectedCellRegion(this, tl, br);
}
public void FixOpenAreas()
@@ -1285,12 +1210,16 @@ namespace OpenRA
throw new ArgumentOutOfRangeException("maxRange",
"The requested range ({0}) cannot exceed the value of MaximumTileSearchRange ({1})".F(maxRange, Grid.MaximumTileSearchRange));
Func<CPos, bool> valid = Contains;
if (allowOutsideBounds)
valid = Tiles.Contains;
for (var i = minRange; i <= maxRange; i++)
{
foreach (var offset in Grid.TilesByDistance[i])
{
var t = offset + center;
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
if (valid(t))
yield return t;
}
}

View File

@@ -38,8 +38,6 @@ namespace OpenRA
object syncRoot = new object();
Queue<MapPreview> generateMinimap = new Queue<MapPreview>();
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
public MapCache(ModData modData)
{
this.modData = modData;
@@ -72,10 +70,13 @@ namespace OpenRA
try
{
// HACK: If the path is inside the the support directory then we may need to create it
// Assume that the path is a directory if there is not an existing file with the same name
var resolved = Platform.ResolvePath(name);
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
Directory.CreateDirectory(resolved);
if (Platform.IsPathRelativeToSupportDirectory(name))
{
// Assume that the path is a directory if there is not an existing file with the same name
var resolved = Platform.ResolvePath(name);
if (!File.Exists(resolved))
Directory.CreateDirectory(resolved);
}
package = modData.ModFiles.OpenPackage(name);
}
@@ -110,7 +111,8 @@ namespace OpenRA
}
catch (Exception e)
{
mapPackage?.Dispose();
if (mapPackage != null)
mapPackage.Dispose();
Console.WriteLine("Failed to load map: {0}", map);
Console.WriteLine("Details: {0}", e);
Log.Write("debug", "Failed to load map: {0}", map);
@@ -129,7 +131,8 @@ namespace OpenRA
// Enumerate map directories
foreach (var kv in modData.Manifest.MapFolders)
{
if (!Enum.TryParse(kv.Value, out MapClassification packageClassification))
MapClassification packageClassification;
if (!Enum.TryParse(kv.Value, out packageClassification))
continue;
if (!classification.HasFlag(packageClassification))
@@ -141,9 +144,12 @@ namespace OpenRA
name = name.Substring(1);
// Don't try to open the map directory in the support directory if it doesn't exist
var resolved = Platform.ResolvePath(name);
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
continue;
if (Platform.IsPathRelativeToSupportDirectory(name))
{
var resolved = Platform.ResolvePath(name);
if (!Directory.Exists(resolved) || !File.Exists(resolved))
continue;
}
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
{
@@ -166,7 +172,6 @@ namespace OpenRA
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
{
var maps = uids.Distinct()
.Where(uid => uid != null)
.Select(uid => previews[uid])
.Where(p => p.Status == MapStatus.Unavailable)
.ToDictionary(p => p.Uid, p => p);
@@ -188,7 +193,8 @@ namespace OpenRA
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
queryFailed?.Invoke();
if (queryFailed != null)
queryFailed();
return;
}
@@ -208,7 +214,8 @@ namespace OpenRA
{
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
Log.Write("debug", "Exception: {0}", e);
queryFailed?.Invoke();
if (queryFailed != null)
queryFailed();
}
};
@@ -292,7 +299,8 @@ namespace OpenRA
Game.RunAfterTick(() =>
{
// Wait for any existing thread to exit before starting a new one.
previewLoaderThread?.Join();
if (previewLoaderThread != null)
previewLoaderThread.Join();
previewLoaderThread = new Thread(LoadAsyncInternal)
{

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -19,85 +20,6 @@ namespace OpenRA
{
public enum MapGridType { Rectangular, RectangularIsometric }
public enum RampSplit { Flat, X, Y }
public enum RampCornerHeight { Low = 0, Half = 1, Full = 2 }
public struct CellRamp
{
public readonly int CenterHeightOffset;
public readonly WVec[] Corners;
public readonly WVec[][] Polygons;
public CellRamp(MapGridType type, RampCornerHeight tl = RampCornerHeight.Low, RampCornerHeight tr = RampCornerHeight.Low, RampCornerHeight br = RampCornerHeight.Low, RampCornerHeight bl = RampCornerHeight.Low, RampSplit split = RampSplit.Flat)
{
if (type == MapGridType.RectangularIsometric)
{
Corners = new[]
{
new WVec(0, -724, 724 * (int)tl),
new WVec(724, 0, 724 * (int)tr),
new WVec(0, 724, 724 * (int)br),
new WVec(-724, 0, 724 * (int)bl),
};
}
else
{
Corners = new[]
{
new WVec(-512, -512, 512 * (int)tl),
new WVec(512, -512, 512 * (int)tr),
new WVec(512, 512, 512 * (int)br),
new WVec(-512, 512, 512 * (int)bl)
};
}
if (split == RampSplit.X)
{
Polygons = new[]
{
new[] { Corners[0], Corners[1], Corners[3] },
new[] { Corners[1], Corners[2], Corners[3] }
};
}
else if (split == RampSplit.Y)
{
Polygons = new[]
{
new[] { Corners[0], Corners[1], Corners[2] },
new[] { Corners[0], Corners[2], Corners[3] }
};
}
else
Polygons = new[] { Corners };
// Initial value must be asigned before HeightOffset can be called
CenterHeightOffset = 0;
CenterHeightOffset = HeightOffset(0, 0);
}
public int HeightOffset(int dX, int dY)
{
// Enumerate over the polygons, assuming that they are triangles
// If the ramp is not split we will take the first three vertices of the corners as a valid triangle
WVec[] p = null;
var u = 0;
var v = 0;
for (var i = 0; i < Polygons.Length; i++)
{
p = Polygons[i];
u = ((p[1].Y - p[2].Y) * (dX - p[2].X) - (p[1].X - p[2].X) * (dY - p[2].Y)) / 1024;
v = ((p[0].X - p[2].X) * (dY - p[2].Y) - (p[0].Y - p[2].Y) * (dX - p[2].X)) / 1024;
// Point is within the triangle if 0 <= u,v <= 1024
if (u >= 0 && u <= 1024 && v >= 0 && v <= 1024)
break;
}
// Calculate w from u,v and interpolate height
return (u * p[0].Z + v * p[1].Z + (1024 - u - v) * p[2].Z) / 1024;
}
}
public class MapGrid : IGlobalModData
{
public readonly MapGridType Type = MapGridType.Rectangular;
@@ -119,7 +41,43 @@ namespace OpenRA
new WVec(256, 256, 0), // bottom right - index 5
};
public CellRamp[] Ramps { get; private set; }
public WVec[][] CellCorners { get; private set; }
readonly int[][] cellCornerHalfHeights = new int[][]
{
// Flat
new[] { 0, 0, 0, 0 },
// Slopes (two corners high)
new[] { 0, 0, 1, 1 },
new[] { 1, 0, 0, 1 },
new[] { 1, 1, 0, 0 },
new[] { 0, 1, 1, 0 },
// Slopes (one corner high)
new[] { 0, 0, 0, 1 },
new[] { 1, 0, 0, 0 },
new[] { 0, 1, 0, 0 },
new[] { 0, 0, 1, 0 },
// Slopes (three corners high)
new[] { 1, 0, 1, 1 },
new[] { 1, 1, 0, 1 },
new[] { 1, 1, 1, 0 },
new[] { 0, 1, 1, 1 },
// Slopes (two corners high, one corner double high)
new[] { 1, 0, 1, 2 },
new[] { 2, 1, 0, 1 },
new[] { 1, 2, 1, 0 },
new[] { 0, 1, 2, 1 },
// Slopes (two corners high, alternating)
new[] { 1, 0, 1, 0 },
new[] { 0, 1, 0, 1 },
new[] { 1, 0, 1, 0 },
new[] { 0, 1, 0, 1 }
};
internal readonly CVec[][] TilesByDistance;
@@ -138,46 +96,34 @@ namespace OpenRA
throw new InvalidDataException("Subcell default index must be a valid index into the offset triples and must be greater than 0 for mods with subcells");
}
// Slope types are hardcoded following the convention from the TS and RA2 map format
Ramps = new[]
{
// Flat
new CellRamp(Type),
// Two adjacent corners raised by half a cell
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half),
new CellRamp(Type, br: RampCornerHeight.Half, bl: RampCornerHeight.Half),
new CellRamp(Type, tl: RampCornerHeight.Half, bl: RampCornerHeight.Half),
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half),
// One corner raised by half a cell
new CellRamp(Type, br: RampCornerHeight.Half, split: RampSplit.X),
new CellRamp(Type, bl: RampCornerHeight.Half, split: RampSplit.Y),
new CellRamp(Type, tl: RampCornerHeight.Half, split: RampSplit.X),
new CellRamp(Type, tr: RampCornerHeight.Half, split: RampSplit.Y),
// Three corners raised by half a cell
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y),
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
// Full tile sloped (mid corners raised by half cell, far corner by full cell)
new CellRamp(Type, tr: RampCornerHeight.Half, br: RampCornerHeight.Full, bl: RampCornerHeight.Half),
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, bl: RampCornerHeight.Full),
new CellRamp(Type, tl: RampCornerHeight.Full, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half),
new CellRamp(Type, tl: RampCornerHeight.Half, tr: RampCornerHeight.Full, br: RampCornerHeight.Half),
// Two opposite corners raised by half a cell
new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.Y),
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.Y),
new CellRamp(Type, tr: RampCornerHeight.Half, bl: RampCornerHeight.Half, split: RampSplit.X),
new CellRamp(Type, tl: RampCornerHeight.Half, br: RampCornerHeight.Half, split: RampSplit.X),
};
var makeCorners = Type == MapGridType.RectangularIsometric ?
(Func<int[], WVec[]>)IsometricCellCorners : RectangularCellCorners;
CellCorners = cellCornerHalfHeights.Select(makeCorners).ToArray();
TilesByDistance = CreateTilesByDistance();
}
static WVec[] IsometricCellCorners(int[] cornerHeight)
{
return new WVec[]
{
new WVec(-724, 0, 724 * cornerHeight[0]),
new WVec(0, -724, 724 * cornerHeight[1]),
new WVec(724, 0, 724 * cornerHeight[2]),
new WVec(0, 724, 724 * cornerHeight[3])
};
}
static WVec[] RectangularCellCorners(int[] cornerHeight)
{
return new WVec[]
{
new WVec(-512, -512, 512 * cornerHeight[0]),
new WVec(512, -512, 512 * cornerHeight[1]),
new WVec(512, 512, 512 * cornerHeight[2]),
new WVec(-512, 512, 512 * cornerHeight[3])
};
}
CVec[][] CreateTilesByDistance()
{
var ts = new List<CVec>[MaximumTileSearchRange + 1];

View File

@@ -89,7 +89,7 @@ namespace OpenRA
public bool DefinesUnsafeCustomRules { get; private set; }
public bool RulesLoaded { get; private set; }
public void SetRulesetGenerator(ModData modData, Func<(Ruleset Ruleset, bool DefinesUnsafeCustomRules)> generator)
public void SetRulesetGenerator(ModData modData, Func<Pair<Ruleset, bool>> generator)
{
InvalidCustomRules = false;
RulesLoaded = false;
@@ -106,8 +106,8 @@ namespace OpenRA
try
{
var ret = generator();
DefinesUnsafeCustomRules = ret.DefinesUnsafeCustomRules;
return ret.Ruleset;
DefinesUnsafeCustomRules = ret.Second;
return ret.First;
}
catch (Exception e)
{
@@ -223,7 +223,7 @@ namespace OpenRA
if (yamlStream == null)
throw new FileNotFoundException("Required file map.yaml not present in this map");
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml")).ToDictionary();
}
Package = p;
@@ -233,7 +233,8 @@ namespace OpenRA
newData.GridType = gridType;
newData.Class = classification;
if (yaml.TryGetValue("MapFormat", out var temp))
MiniYaml temp;
if (yaml.TryGetValue("MapFormat", out temp))
{
var format = FieldLoader.GetValue<int>("MapFormat", temp.Value);
if (format != Map.SupportedMapFormat)
@@ -268,13 +269,14 @@ namespace OpenRA
try
{
// Actor definitions may change if the map format changes
if (yaml.TryGetValue("Actors", out var actorDefinitions))
MiniYaml actorDefinitions;
if (yaml.TryGetValue("Actors", out actorDefinitions))
{
var spawns = new List<CPos>();
foreach (var kv in actorDefinitions.Nodes.Where(d => d.Value.Value == "mpspawn"))
{
var s = new ActorReference(kv.Value.Value, kv.Value.ToDictionary());
spawns.Add(s.Get<LocationInit>().Value);
spawns.Add(s.InitDict.Get<LocationInit>().Value(null));
}
newData.SpawnPoints = spawns.ToArray();
@@ -291,7 +293,8 @@ namespace OpenRA
try
{
// Player definitions may change if the map format changes
if (yaml.TryGetValue("Players", out var playerDefinitions))
MiniYaml playerDefinitions;
if (yaml.TryGetValue("Players", out playerDefinitions))
{
newData.Players = new MapPlayers(playerDefinitions.Nodes);
newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable);
@@ -315,7 +318,7 @@ namespace OpenRA
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
return (rules, flagged);
return Pair.New(rules, flagged);
});
if (p.Contains("map.png"))
@@ -328,7 +331,8 @@ namespace OpenRA
MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
{
if (!yaml.TryGetValue(section, out var node))
MiniYaml node;
if (!yaml.TryGetValue(section, out node))
return null;
return node;
@@ -398,7 +402,7 @@ namespace OpenRA
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
return (rules, flagged);
return Pair.New(rules, flagged);
});
}
catch (Exception e)
@@ -412,7 +416,8 @@ namespace OpenRA
if (innerData.Preview != null)
cache.CacheMinimap(this);
parseMetadata?.Invoke(this);
if (parseMetadata != null)
parseMetadata(this);
}
// Update the status and class unconditionally
@@ -521,7 +526,9 @@ namespace OpenRA
public void Delete()
{
Invalidate();
(parentPackage as IReadWritePackage)?.Delete(Package.Name);
var deleteFromPackage = parentPackage as IReadWritePackage;
if (deleteFromPackage != null)
deleteFromPackage.Delete(Package.Name);
}
Stream IReadOnlyFileSystem.Open(string filename)

View File

@@ -30,29 +30,14 @@ namespace OpenRA
public string Faction;
public bool LockColor = false;
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
/// <summary>
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
/// location or choose the map edge for reinforcements.
/// This will usually be overridden for client (lobby slot) players with a location based on the Spawn index
/// </summary>
public CPos HomeLocation = CPos.Zero;
public Color Color = Color.FromAhsl(0, 0, 238);
public bool LockSpawn = false;
/// <summary>
/// Sets the initial spawn point index that is used to override the "Home" location for client (lobby slot) players.
/// Map players always ignore this and use HomeLocation directly.
/// </summary>
public int Spawn = 0;
public bool LockTeam = false;
public int Team = 0;
public bool LockHandicap = false;
public int Handicap = 0;
public string[] Allies = { };
public string[] Enemies = { };

View File

@@ -1,62 +0,0 @@
#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 OpenRA.Primitives;
namespace OpenRA
{
public sealed class ProjectedCellLayer<T> : CellLayerBase<T>
{
public ProjectedCellLayer(Map map)
: base(map) { }
public ProjectedCellLayer(MapGridType gridType, Size size)
: base(gridType, size) { }
// Resolve an array index from map coordinates.
public int Index(PPos uv)
{
return uv.V * Size.Width + uv.U;
}
public T this[int index]
{
get
{
return entries[index];
}
set
{
entries[index] = value;
}
}
/// <summary>Gets or sets the layer contents using projected map coordinates.</summary>
public T this[PPos uv]
{
get
{
return entries[Index(uv)];
}
set
{
entries[Index(uv)] = value;
}
}
public bool Contains(PPos uv)
{
return bounds.Contains(uv.U, uv.V);
}
}
}

View File

@@ -76,7 +76,7 @@ namespace OpenRA
return GetEnumerator();
}
public struct ProjectedCellRegionEnumerator : IEnumerator<PPos>
public sealed class ProjectedCellRegionEnumerator : IEnumerator<PPos>
{
readonly ProjectedCellRegion r;
@@ -86,11 +86,9 @@ namespace OpenRA
PPos current;
public ProjectedCellRegionEnumerator(ProjectedCellRegion region)
: this()
{
r = region;
Reset();
current = new PPos(u, v);
}
public bool MoveNext()

View File

@@ -24,8 +24,9 @@ namespace OpenRA
public readonly byte TerrainType = byte.MaxValue;
public readonly byte Height;
public readonly byte RampType;
public readonly Color MinColor;
public readonly Color MaxColor;
public readonly Color LeftColor;
public readonly Color RightColor;
public readonly float ZOffset = 0.0f;
public readonly float ZRamp = 1.0f;
}
@@ -54,6 +55,13 @@ namespace OpenRA
readonly TerrainTileInfo[] tileInfo;
public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles)
{
Id = id;
Images = images;
Size = size;
}
public TerrainTemplateInfo(TileSet tileSet, MiniYaml my)
{
FieldLoader.Load(this, my);
@@ -65,11 +73,9 @@ namespace OpenRA
tileInfo = new TerrainTileInfo[Size.X * Size.Y];
foreach (var node in nodes)
{
if (!int.TryParse(node.Key, out var key))
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
if (key < 0 || key >= tileInfo.Length)
throw new YamlException("Tileset `{0}` template `{1}` references frame {2}, but only [0..{3}] are valid for a {4}x{5} Size template.".F(tileSet.Id, Id, key, tileInfo.Length - 1, Size.X, Size.Y));
int key;
if (!int.TryParse(node.Key, out key) || key < 0 || key >= tileInfo.Length)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
}
@@ -81,11 +87,9 @@ namespace OpenRA
var i = 0;
foreach (var node in nodes)
{
if (!int.TryParse(node.Key, out var key))
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
if (key != i++)
throw new YamlException("Tileset `{0}` template `{1}` is missing a definition for frame {2}.".F(tileSet.Id, Id, i - 1));
int key;
if (!int.TryParse(node.Key, out key) || key != i++)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
}
@@ -102,11 +106,11 @@ namespace OpenRA
// Fall back to the terrain-type color if necessary
var overrideColor = tileSet.TerrainInfo[tile.TerrainType].Color;
if (tile.MinColor == default(Color))
tile.GetType().GetField("MinColor").SetValue(tile, overrideColor);
if (tile.LeftColor == default(Color))
tile.GetType().GetField("LeftColor").SetValue(tile, overrideColor);
if (tile.MaxColor == default(Color))
tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor);
if (tile.RightColor == default(Color))
tile.GetType().GetField("RightColor").SetValue(tile, overrideColor);
return tile;
}
@@ -135,8 +139,6 @@ namespace OpenRA
public readonly string[] EditorTemplateOrder;
public readonly bool IgnoreTileSpriteOffsets;
public readonly bool EnableDepth = false;
public readonly float MinHeightColorBrightness = 1.0f;
public readonly float MaxHeightColorBrightness = 1.0f;
[FieldLoader.Ignore]
public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
@@ -161,14 +163,14 @@ namespace OpenRA
.ToArray();
if (TerrainInfo.Length >= byte.MaxValue)
throw new YamlException("Too many terrain types.");
throw new InvalidDataException("Too many terrain types.");
for (byte i = 0; i < TerrainInfo.Length; i++)
{
var tt = TerrainInfo[i].Type;
if (terrainIndexByType.ContainsKey(tt))
throw new YamlException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
throw new InvalidDataException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
terrainIndexByType.Add(tt, i);
}
@@ -213,7 +215,8 @@ namespace OpenRA
public byte GetTerrainIndex(string type)
{
if (terrainIndexByType.TryGetValue(type, out var index))
byte index;
if (terrainIndexByType.TryGetValue(type, out index))
return index;
throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type));
@@ -221,7 +224,8 @@ namespace OpenRA
public byte GetTerrainIndex(TerrainTile r)
{
if (!Templates.TryGetValue(r.Type, out var tpl))
TerrainTemplateInfo tpl;
if (!Templates.TryGetValue(r.Type, out tpl))
return defaultWalkableTerrainIndex;
if (tpl.Contains(r.Index))
@@ -236,7 +240,8 @@ namespace OpenRA
public TerrainTileInfo GetTileInfo(TerrainTile r)
{
if (!Templates.TryGetValue(r.Type, out var tpl))
TerrainTemplateInfo tpl;
if (!Templates.TryGetValue(r.Type, out tpl))
return null;
return tpl.Contains(r.Index) ? tpl[r.Index] : null;

View File

@@ -99,10 +99,7 @@ namespace OpenRA
public MiniYaml Clone()
{
var clonedNodes = new MiniYamlNodes(Nodes.Count);
foreach (var node in Nodes)
clonedNodes.Add(node.Clone());
return new MiniYaml(Value, clonedNodes);
return new MiniYaml(Value, Nodes.Select(n => n.Clone()).ToList());
}
public Dictionary<string, MiniYaml> ToDictionary()
@@ -151,11 +148,8 @@ namespace OpenRA
return nd.ContainsKey(s) ? nd[s].Nodes : new List<MiniYamlNode>();
}
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace)
{
if (stringPool == null)
stringPool = new Dictionary<string, string>();
var levels = new List<List<MiniYamlNode>>();
levels.Add(new List<MiniYamlNode>());
@@ -269,10 +263,6 @@ namespace OpenRA
if (key != null || !discardCommentsAndWhitespace)
{
key = key == null ? null : stringPool.GetOrAdd(key, key);
value = value == null ? null : stringPool.GetOrAdd(value, value);
comment = comment == null ? null : stringPool.GetOrAdd(comment, comment);
var nodes = new List<MiniYamlNode>();
levels[level].Add(new MiniYamlNode(key, value, comment, nodes, location));
@@ -280,33 +270,23 @@ namespace OpenRA
}
}
foreach (var nodes in levels)
nodes.TrimExcess();
return levels[0];
}
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true)
{
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace, stringPool);
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace);
}
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
{
IEnumerable<string> Lines(StreamReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
}
using (var reader = new StreamReader(s))
return FromLines(Lines(reader), fileName, discardCommentsAndWhitespace, stringPool);
return FromString(reader.ReadToEnd(), fileName, discardCommentsAndWhitespace);
}
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
{
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace, stringPool);
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace);
}
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
@@ -350,7 +330,7 @@ namespace OpenRA
static List<MiniYamlNode> ResolveInherits(string key, MiniYaml node, Dictionary<string, MiniYaml> tree, Dictionary<string, MiniYamlNode.SourceLocation> inherited)
{
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
var resolved = new List<MiniYamlNode>();
// Inheritance is tracked from parent->child, but not from child->parentsiblings.
inherited = new Dictionary<string, MiniYamlNode.SourceLocation>(inherited);
@@ -359,7 +339,8 @@ namespace OpenRA
{
if (n.Key == "Inherits" || n.Key.StartsWith("Inherits@", StringComparison.Ordinal))
{
if (!tree.TryGetValue(n.Value.Value, out var parent))
MiniYaml parent;
if (!tree.TryGetValue(n.Value.Value, out parent))
throw new YamlException(
"{0}: Parent type `{1}` not found".F(n.Location, n.Value.Value));
@@ -381,7 +362,6 @@ namespace OpenRA
MergeIntoResolved(n, resolved, tree, inherited);
}
resolved.TrimExcess();
return resolved;
}
@@ -418,7 +398,6 @@ namespace OpenRA
}
}
ret.TrimExcess();
return ret;
}
@@ -449,8 +428,9 @@ namespace OpenRA
foreach (var key in allKeys)
{
existingDict.TryGetValue(key, out var existingNode);
overrideDict.TryGetValue(key, out var overrideNode);
MiniYamlNode existingNode, overrideNode;
existingDict.TryGetValue(key, out existingNode);
overrideDict.TryGetValue(key, out overrideNode);
var loc = overrideNode == null ? default(MiniYamlNode.SourceLocation) : overrideNode.Location;
var comment = (overrideNode ?? existingNode).Comment;
@@ -459,7 +439,6 @@ namespace OpenRA
ret.Add(merged);
}
ret.TrimExcess();
return ret;
}

View File

@@ -79,6 +79,7 @@ namespace OpenRA
throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type));
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s);
var modelFormat = Manifest.Get<ModelSequenceFormat>();
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
@@ -107,7 +108,7 @@ namespace OpenRA
defaultSequences = Exts.Lazy(() =>
{
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Key, null));
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Value, null));
return (IReadOnlyDictionary<string, SequenceProvider>)(new ReadOnlyDictionary<string, SequenceProvider>(items));
});
@@ -176,7 +177,8 @@ namespace OpenRA
public Map PrepareMap(string uid)
{
LoadScreen?.Display();
if (LoadScreen != null)
LoadScreen.Display();
if (MapCache[uid].Status != MapStatus.Available)
throw new InvalidDataException("Invalid map uid: {0}".F(uid));
@@ -200,12 +202,12 @@ namespace OpenRA
public void Dispose()
{
LoadScreen?.Dispose();
if (LoadScreen != null)
LoadScreen.Dispose();
MapCache.Dispose();
ObjectCreator?.Dispose();
Manifest.Dispose();
if (ObjectCreator != null)
ObjectCreator.Dispose();
}
}

View File

@@ -10,11 +10,8 @@
#endregion
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using OpenRA.Server;
@@ -33,63 +30,12 @@ namespace OpenRA.Network
{
int LocalClientId { get; }
ConnectionState ConnectionState { get; }
IPEndPoint EndPoint { get; }
string ErrorMessage { get; }
void Send(int frame, List<byte[]> orders);
void SendImmediate(IEnumerable<byte[]> orders);
void SendSync(int frame, byte[] syncData);
void Receive(Action<int, byte[]> packetFn);
}
public class ConnectionTarget
{
readonly DnsEndPoint[] endpoints;
public ConnectionTarget()
{
endpoints = new[] { new DnsEndPoint("invalid", 0) };
}
public ConnectionTarget(string host, int port)
{
endpoints = new[] { new DnsEndPoint(host, port) };
}
public ConnectionTarget(IEnumerable<DnsEndPoint> endpoints)
{
this.endpoints = endpoints.ToArray();
if (this.endpoints.Length == 0)
{
throw new ArgumentException("ConnectionTarget must have at least one address.");
}
}
public IEnumerable<IPEndPoint> GetConnectEndPoints()
{
return endpoints
.SelectMany(e =>
{
try
{
return Dns.GetHostAddresses(e.Host)
.Select(a => new IPEndPoint(a, e.Port));
}
catch (Exception)
{
return Enumerable.Empty<IPEndPoint>();
}
})
.ToList();
}
public override string ToString()
{
return endpoints
.Select(e => "{0}:{1}".F(e.Host, e.Port))
.JoinWith("/");
}
}
class EchoConnection : IConnection
{
protected struct ReceivedPacket
@@ -111,16 +57,6 @@ namespace OpenRA.Network
get { return ConnectionState.PreConnecting; }
}
public virtual IPEndPoint EndPoint
{
get { throw new NotSupportedException("An echo connection doesn't have an endpoint"); }
}
public virtual string ErrorMessage
{
get { return null; }
}
public virtual void Send(int frame, List<byte[]> orders)
{
var ms = new MemoryStream();
@@ -174,21 +110,23 @@ namespace OpenRA.Network
foreach (var p in packets)
{
packetFn(p.FromClient, p.Data);
Recorder?.Receive(p.FromClient, p.Data);
if (Recorder != null)
Recorder.Receive(p.FromClient, p.Data);
}
}
public void StartRecording(Func<string> chooseFilename)
{
// If we have a previous recording then save/dispose it and start a new one.
Recorder?.Dispose();
if (Recorder != null)
Recorder.Dispose();
Recorder = new ReplayRecorder(chooseFilename);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
Recorder?.Dispose();
if (disposing && Recorder != null)
Recorder.Dispose();
}
public void Dispose()
@@ -200,100 +138,35 @@ namespace OpenRA.Network
sealed class NetworkConnection : EchoConnection
{
readonly ConnectionTarget target;
TcpClient tcp;
IPEndPoint endpoint;
readonly TcpClient tcp;
readonly List<byte[]> queuedSyncPackets = new List<byte[]>();
volatile ConnectionState connectionState = ConnectionState.Connecting;
volatile int clientId;
bool disposed;
string errorMessage;
public override IPEndPoint EndPoint { get { return endpoint; } }
public override string ErrorMessage { get { return errorMessage; } }
public NetworkConnection(ConnectionTarget target)
{
this.target = target;
new Thread(NetworkConnectionConnect)
{
Name = "{0} (connect to {1})".F(GetType().Name, target),
IsBackground = true
}.Start();
}
void NetworkConnectionConnect()
{
var queue = new BlockingCollection<TcpClient>();
var atLeastOneEndpoint = false;
foreach (var endpoint in target.GetConnectEndPoints())
{
atLeastOneEndpoint = true;
new Thread(() =>
{
try
{
var client = new TcpClient(endpoint.AddressFamily) { NoDelay = true };
client.Connect(endpoint.Address, endpoint.Port);
try
{
queue.Add(client);
}
catch (InvalidOperationException)
{
// Another connection was faster, close this one.
client.Close();
}
}
catch (Exception ex)
{
errorMessage = "Failed to connect";
Log.Write("client", "Failed to connect to {0}: {1}".F(endpoint, ex.Message));
}
})
{
Name = "{0} (connect to {1})".F(GetType().Name, endpoint),
IsBackground = true
}.Start();
}
if (!atLeastOneEndpoint)
{
errorMessage = "Failed to resolve address";
connectionState = ConnectionState.NotConnected;
}
// Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway.
else if (queue.TryTake(out tcp, 5000))
{
// Copy endpoint here to have it even after getting disconnected.
endpoint = (IPEndPoint)tcp.Client.RemoteEndPoint;
new Thread(NetworkConnectionReceive)
{
Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint),
IsBackground = true
}.Start();
}
else
{
connectionState = ConnectionState.NotConnected;
}
// Close all unneeded connections in the queue and make sure new ones are closed on the connect thread.
queue.CompleteAdding();
foreach (var client in queue)
client.Close();
}
void NetworkConnectionReceive()
public NetworkConnection(string host, int port)
{
try
{
var reader = new BinaryReader(tcp.GetStream());
tcp = new TcpClient(host, port) { NoDelay = true };
new Thread(NetworkConnectionReceive)
{
Name = GetType().Name + " " + host + ":" + port,
IsBackground = true
}.Start(tcp.GetStream());
}
catch
{
connectionState = ConnectionState.NotConnected;
}
}
void NetworkConnectionReceive(object networkStreamObject)
{
try
{
var networkStream = (NetworkStream)networkStreamObject;
var reader = new BinaryReader(networkStream);
var handshakeProtocol = reader.ReadInt32();
if (handshakeProtocol != ProtocolVersion.Handshake)
@@ -314,11 +187,7 @@ namespace OpenRA.Network
AddPacket(new ReceivedPacket { FromClient = client, Data = buf });
}
}
catch (Exception ex)
{
errorMessage = "Connection failed";
Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message));
}
catch { }
finally
{
connectionState = ConnectionState.NotConnected;
@@ -370,7 +239,8 @@ namespace OpenRA.Network
// Closing the stream will cause any reads on the receiving thread to throw.
// This will mark the connection as no longer connected and the thread will terminate cleanly.
tcp?.Close();
if (tcp != null)
tcp.Close();
base.Dispose(disposing);
}

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Network
if (lastClientFrame == -1)
lastClientFrame = framePackets
.Where(x => x.Value.ContainsKey(clientId))
.Select(x => x.Key).MaxByOrDefault(x => x);
.Select(x => x.Key).OrderBy(x => x).LastOrDefault();
clientQuitTimes[clientId] = lastClientFrame;
}

View File

@@ -25,7 +25,6 @@ namespace OpenRA.Network
public readonly string Faction;
public readonly int SpawnPoint;
public readonly int Team;
public readonly int Handicap;
public readonly string Slot;
public readonly string Bot;
public readonly bool IsAdmin;
@@ -40,7 +39,6 @@ namespace OpenRA.Network
Faction = client.Faction;
SpawnPoint = client.SpawnPoint;
Team = client.Team;
Handicap = client.Handicap;
Slot = client.Slot;
Bot = client.Bot;
IsAdmin = client.IsAdmin;
@@ -55,7 +53,6 @@ namespace OpenRA.Network
client.Faction = Faction;
client.SpawnPoint = SpawnPoint;
client.Team = Team;
client.Handicap = Handicap;
client.Slot = Slot;
client.Bot = Bot;
client.IsAdmin = IsAdmin;
@@ -120,7 +117,7 @@ namespace OpenRA.Network
LastOrdersFrame = rs.ReadInt32();
LastSyncFrame = rs.ReadInt32();
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
lastSyncPacket = rs.ReadBytes(5);
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
@@ -193,12 +190,6 @@ namespace OpenRA.Network
// Sync packet - we only care about the last value
if (data.Length > 0 && data[0] == (byte)OrderType.SyncHash && frame > LastSyncFrame)
{
if (data.Length != Order.SyncHashOrderLength)
{
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(data.Length, Order.SyncHashOrderLength));
return;
}
LastSyncFrame = frame;
lastSyncPacket = data;
}
@@ -291,7 +282,7 @@ namespace OpenRA.Network
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
file.Write(lastSyncPacket, 0, 5);
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());

View File

@@ -56,7 +56,7 @@ namespace OpenRA.Network
"Mod", "Version", "ModTitle", "ModWebsite", "ModIcon32",
// Current server state
"Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints"
"Map", "State", "MaxPlayers", "Protected", "Authentication"
};
public const int ProtocolVersion = 2;
@@ -132,9 +132,6 @@ namespace OpenRA.Network
[FieldLoader.LoadUsing("LoadClients")]
public readonly GameClient[] Clients;
/// <summary>The list of spawnpoints that are disabled for this game</summary>
public readonly int[] DisabledSpawnPoints = { };
public string ModLabel { get { return "{0} ({1})".F(ModTitle, Version); } }
static object LoadClients(MiniYaml yaml)
@@ -170,22 +167,28 @@ namespace OpenRA.Network
// Games advertised using the old API calculated the play time locally
if (State == 2 && PlayTime < 0)
if (DateTime.TryParse(Started, out var startTime))
{
DateTime startTime;
if (DateTime.TryParse(Started, out startTime))
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
}
ExternalMod external;
var externalKey = ExternalMod.MakeKey(Mod, Version);
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
if (Game.ExternalMods.TryGetValue(externalKey, out external) && external.Version == Version)
IsCompatible = true;
// Games advertised using the old API used local mod metadata
if (string.IsNullOrEmpty(ModTitle))
{
Manifest mod;
if (external != null && external.Version == Version)
{
// Use external mod registration to populate the section header
ModTitle = external.Title;
}
else if (Game.Mods.TryGetValue(Mod, out var mod))
else if (Game.Mods.TryGetValue(Mod, out mod))
{
// Use internal mod data to populate the section header, but
// on-connect switching must use the external mod plumbing.
@@ -229,7 +232,6 @@ namespace OpenRA.Network
Protected = !string.IsNullOrEmpty(server.Settings.Password);
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Any();
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray();
DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToArray() ?? Array.Empty<int>();
}
public string ToPOSTData(bool lanGame)

View File

@@ -25,7 +25,7 @@ namespace OpenRA
}
[Flags]
enum OrderFields : short
enum OrderFields : byte
{
None = 0x0,
Target = 0x01,
@@ -35,8 +35,7 @@ namespace OpenRA
ExtraLocation = 0x10,
ExtraData = 0x20,
TargetIsCell = 0x40,
Subject = 0x80,
Grouped = 0x100
Subject = 0x80
}
static class OrderFieldsExts
@@ -49,15 +48,10 @@ namespace OpenRA
public sealed class Order
{
// Length of orders with type OrderType.SyncHash
public const int SyncHashOrderLength = 13;
public readonly string OrderString;
public readonly Actor Subject;
public readonly bool Queued;
public ref readonly Target Target => ref target;
public readonly Actor[] GroupedActors;
public readonly Target Target;
public string TargetString;
public CPos ExtraLocation;
public Actor[] ExtraActors;
@@ -66,24 +60,20 @@ namespace OpenRA
public OrderType Type = OrderType.Fields;
public bool SuppressVisualFeedback;
public ref readonly Target VisualFeedbackTarget => ref visualFeedbackTarget;
public Target VisualFeedbackTarget;
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
readonly Target target;
readonly Target visualFeedbackTarget;
Order(string orderString, Actor subject, in Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData)
{
OrderString = orderString ?? "";
Subject = subject;
this.target = target;
Target = target;
TargetString = targetString;
Queued = queued;
ExtraActors = extraActors;
ExtraLocation = extraLocation;
ExtraData = extraData;
GroupedActors = groupedActors;
}
public static Order Deserialize(World world, BinaryReader r)
@@ -96,7 +86,7 @@ namespace OpenRA
case OrderType.Fields:
{
var order = r.ReadString();
var flags = (OrderFields)r.ReadInt16();
var flags = (OrderFields)r.ReadByte();
Actor subject = null;
if (flags.HasField(OrderFields.Subject))
@@ -113,7 +103,8 @@ namespace OpenRA
{
case TargetType.Actor:
{
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out var targetActor))
Actor targetActor;
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out targetActor))
target = Target.FromActor(targetActor);
break;
}
@@ -123,7 +114,8 @@ namespace OpenRA
var playerActorID = r.ReadUInt32();
var frozenActorID = r.ReadUInt32();
if (world == null || !TryGetActorFromUInt(world, playerActorID, out var playerActor))
Actor playerActor;
if (world == null || !TryGetActorFromUInt(world, playerActorID, out playerActor))
break;
if (playerActor.Owner.FrozenActorLayer == null)
@@ -172,23 +164,13 @@ namespace OpenRA
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32()) : CPos.Zero;
var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0;
Actor[] groupedActors = null;
if (flags.HasField(OrderFields.Grouped))
{
var count = r.ReadInt32();
if (world != null)
groupedActors = Exts.MakeArray(count, _ => world.GetActorById(r.ReadUInt32()));
else
r.ReadBytes(4 * count);
}
if (world == null)
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData);
if (subject == null && flags.HasField(OrderFields.Subject))
return null;
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData);
}
case OrderType.Handshake:
@@ -249,11 +231,6 @@ namespace OpenRA
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
}
public static Order FromGroupedOrder(Order grouped, Actor subject)
{
return new Order(grouped.OrderString, subject, grouped.Target, grouped.TargetString, grouped.Queued, grouped.ExtraActors, grouped.ExtraLocation, grouped.ExtraData);
}
public static Order Command(string text)
{
return new Order("Command", null, false) { IsImmediate = true, TargetString = text };
@@ -278,17 +255,11 @@ namespace OpenRA
public Order()
: this(null, null, Target.Invalid, null, false, null, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, in Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, Target target, Target visualFeedbackTarget, bool queued)
: this(orderString, subject, target, null, queued, null, CPos.Zero, 0, null)
{
this.visualFeedbackTarget = visualFeedbackTarget;
}
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0) { }
public byte[] Serialize()
{
@@ -296,7 +267,7 @@ namespace OpenRA
if (Type == OrderType.Handshake)
minLength += TargetString.Length + 1;
else if (Type == OrderType.Fields)
minLength += 4 + 2 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
minLength += 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
if (ExtraActors != null)
minLength += ExtraActors.Length * 4;
@@ -337,9 +308,6 @@ namespace OpenRA
if (Queued)
fields |= OrderFields.Queued;
if (GroupedActors != null)
fields |= OrderFields.Grouped;
if (ExtraActors != null)
fields |= OrderFields.ExtraActors;
@@ -349,7 +317,7 @@ namespace OpenRA
if (Target.SerializableCell != null)
fields |= OrderFields.TargetIsCell;
w.Write((short)fields);
w.Write((byte)fields);
if (fields.HasField(OrderFields.Subject))
w.Write(UIntFromActor(Subject));
@@ -394,13 +362,6 @@ namespace OpenRA
if (fields.HasField(OrderFields.ExtraData))
w.Write(ExtraData);
if (fields.HasField(OrderFields.Grouped))
{
w.Write(GroupedActors.Length);
foreach (var a in GroupedActors)
w.Write(UIntFromActor(a));
}
break;
}

View File

@@ -31,14 +31,13 @@ namespace OpenRA.Network
return ret;
}
public static byte[] SerializeSync(int sync, ulong defeatState)
public static byte[] SerializeSync(int sync)
{
var ms = new MemoryStream(Order.SyncHashOrderLength);
var ms = new MemoryStream(1 + 4);
using (var writer = new BinaryWriter(ms))
{
writer.Write((byte)OrderType.SyncHash);
writer.Write(sync);
writer.Write(defeatState);
}
return ms.GetBuffer();

View File

@@ -28,10 +28,11 @@ namespace OpenRA.Network
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
public World World;
public readonly ConnectionTarget Endpoint;
public readonly string Host;
public readonly int Port;
public readonly string Password = "";
public string ServerError = null;
public string ServerError = "Server is not responding";
public bool AuthenticationFailed = false;
public ExternalMod ServerExternalMod = null;
@@ -47,10 +48,10 @@ namespace OpenRA.Network
internal int GameSaveLastFrame = -1;
internal int GameSaveLastSyncFrame = -1;
readonly List<Order> localOrders = new List<Order>();
readonly List<Order> localImmediateOrders = new List<Order>();
List<Order> localOrders = new List<Order>();
List<Order> localImmediateOrders = new List<Order>();
readonly List<ChatLine> chatCache = new List<ChatLine>();
List<ChatLine> chatCache = new List<ChatLine>();
public readonly ReadOnlyList<ChatLine> ChatCache;
@@ -79,9 +80,10 @@ namespace OpenRA.Network
Connection.Send(i, new List<byte[]>());
}
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
public OrderManager(string host, int port, string password, IConnection conn)
{
Endpoint = endpoint;
Host = host;
Port = port;
Password = password;
Connection = conn;
syncReport = new SyncReport(this);
@@ -115,7 +117,7 @@ namespace OpenRA.Network
Connection.SendImmediate(localImmediateOrders.Select(o => o.Serialize()));
localImmediateOrders.Clear();
var immediatePackets = new List<(int ClientId, byte[] Packet)>();
var immediatePackets = new List<Pair<int, byte[]>>();
Connection.Receive(
(clientId, packet) =>
@@ -123,27 +125,19 @@ namespace OpenRA.Network
var frame = BitConverter.ToInt32(packet, 0);
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
frameData.ClientQuit(clientId, frame);
else if (packet.Length > 4 && packet[4] == (byte)OrderType.SyncHash)
{
if (packet.Length != 4 + Order.SyncHashOrderLength)
{
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(packet.Length, 4 + Order.SyncHashOrderLength));
return;
}
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
CheckSync(packet);
}
else if (frame == 0)
immediatePackets.Add((clientId, packet));
immediatePackets.Add(Pair.New(clientId, packet));
else
frameData.AddFrameOrders(clientId, frame, packet);
});
foreach (var p in immediatePackets)
{
foreach (var o in p.Packet.ToOrderList(World))
foreach (var o in p.Second.ToOrderList(World))
{
UnitOrders.ProcessOrder(this, World, p.ClientId, o);
UnitOrders.ProcessOrder(this, World, p.First, o);
// A mod switch or other event has pulled the ground from beneath us
if (disposed)
@@ -157,7 +151,8 @@ namespace OpenRA.Network
void CheckSync(byte[] packet)
{
var frame = BitConverter.ToInt32(packet, 0);
if (syncForFrame.TryGetValue(frame, out var existingSync))
byte[] existingSync;
if (syncForFrame.TryGetValue(frame, out existingSync))
{
if (packet.Length != existingSync.Length)
OutOfSync(frame);
@@ -172,14 +167,14 @@ namespace OpenRA.Network
public bool IsReadyForNextFrame
{
get { return GameStarted && frameData.IsReadyForFrame(NetFrameNumber); }
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame(NetFrameNumber); }
}
public IEnumerable<Session.Client> GetClientsNotReadyForNextFrame
{
get
{
return GameStarted
return NetFrameNumber >= 1
? frameData.ClientsNotReadyForFrame(NetFrameNumber)
.Select(a => LobbyInfo.ClientWithIndex(a))
: NoClients;
@@ -200,16 +195,9 @@ namespace OpenRA.Network
UnitOrders.ProcessOrder(this, World, order.Client, order.Order);
if (NetFrameNumber + FramesAhead >= GameSaveLastSyncFrame)
{
var defeatState = 0UL;
for (var i = 0; i < World.Players.Length; i++)
if (World.Players[i].WinState == WinState.Lost)
defeatState |= 1UL << i;
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash(), defeatState));
}
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash()));
else
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0, 0));
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0));
if (generateSyncReport)
using (new PerfSample("sync_report"))
@@ -221,7 +209,8 @@ namespace OpenRA.Network
public void Dispose()
{
disposed = true;
Connection?.Dispose();
if (Connection != null)
Connection.Dispose();
}
}

View File

@@ -12,8 +12,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using OpenRA.FileFormats;
using OpenRA.Primitives;
namespace OpenRA.Network
{
@@ -22,7 +22,7 @@ namespace OpenRA.Network
class Chunk
{
public int Frame;
public (int ClientId, byte[] Packet)[] Packets;
public Pair<int, byte[]>[] Packets;
}
Queue<Chunk> chunks = new Queue<Chunk>();
@@ -32,13 +32,6 @@ namespace OpenRA.Network
public int LocalClientId { get { return -1; } }
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
public IPEndPoint EndPoint
{
get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); }
}
public string ErrorMessage { get { return null; } }
public readonly int TickCount;
public readonly int FinalGameTick;
public readonly bool IsValid;
@@ -54,26 +47,29 @@ namespace OpenRA.Network
// to avoid issues with all immediate orders being resolved on the first tick.
using (var rs = File.OpenRead(replayFilename))
{
var packets = new List<(int ClientId, byte[] Packet)>();
var packets = new List<Pair<int, byte[]>>();
var chunk = new Chunk();
while (rs.Position < rs.Length)
{
var client = rs.ReadInt32();
if (client == ReplayMetadata.MetaStartMarker)
break;
var packetLen = rs.ReadInt32();
var packet = rs.ReadBytes(packetLen);
var frame = BitConverter.ToInt32(packet, 0);
packets.Add((client, packet));
packets.Add(Pair.New(client, packet));
if (frame != int.MaxValue && (!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
if (frame != int.MaxValue &&
(!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
lastClientsFrame[client] = frame;
if (packet.Length > 4 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
continue;
if (frame == 0)
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
continue; // disconnect
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
continue; // sync
else if (frame == 0)
{
// Parse replay metadata from orders stream
var orders = packet.ToOrderList(null);
@@ -107,13 +103,13 @@ namespace OpenRA.Network
{
foreach (var tmpPacketPair in tmpChunk.Packets)
{
var client = tmpPacketPair.ClientId;
var client = tmpPacketPair.First;
// Don't replace the final disconnection packet - we still want this to end the replay.
if (client == lastClientToDisconnect)
continue;
var packet = tmpPacketPair.Packet;
var packet = tmpPacketPair.Second;
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
{
var lastClientFrame = lastClientsFrame[client];
@@ -152,7 +148,7 @@ namespace OpenRA.Network
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
foreach (var o in chunks.Dequeue().Packets)
packetFn(o.ClientId, o.Packet);
packetFn(o.First, o.Second);
}
public void Dispose() { }

View File

@@ -10,7 +10,6 @@
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
@@ -26,7 +25,9 @@ namespace OpenRA.Network
static bool IsGameStart(byte[] data)
{
if (data.Length > 4 && (data[4] == (byte)OrderType.Disconnect || data[4] == (byte)OrderType.SyncHash))
if (data.Length == 5 && data[4] == (byte)OrderType.Disconnect)
return false;
if (data.Length >= 5 && data[4] == (byte)OrderType.SyncHash)
return false;
var frame = BitConverter.ToInt32(data, 0);
@@ -44,7 +45,7 @@ namespace OpenRA.Network
{
var filename = chooseFilename();
var mod = Game.ModData.Manifest;
var dir = Path.Combine(Platform.SupportDir, "Replays", mod.Id, mod.Metadata.Version);
var dir = Platform.ResolvePath(Platform.SupportDirPrefix, "Replays", mod.Id, mod.Metadata.Version);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
@@ -84,14 +85,6 @@ namespace OpenRA.Network
writer.Write(data);
}
public void ReceiveFrame(int clientID, int frame, byte[] data)
{
var ms = new MemoryStream(4 + data.Length);
ms.WriteArray(BitConverter.GetBytes(frame));
ms.WriteArray(data);
Receive(clientID, ms.GetBuffer());
}
bool disposed;
public void Dispose()
@@ -107,7 +100,8 @@ namespace OpenRA.Network
Metadata.Write(writer);
}
preStartBuffer?.Dispose();
if (preStartBuffer != null)
preStartBuffer.Dispose();
writer.Close();
}
}

View File

@@ -26,8 +26,6 @@ namespace OpenRA.Network
// Keyed by the PlayerReference id that the slot corresponds to
public Dictionary<string, Slot> Slots = new Dictionary<string, Slot>();
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public Global GlobalSettings = new Global();
public static string AnonymizeIP(IPAddress ip)
@@ -71,9 +69,6 @@ namespace OpenRA.Network
var s = Slot.Deserialize(node.Value);
session.Slots.Add(s.PlayerReference, s);
break;
case "DisabledSpawnPoints":
session.DisabledSpawnPoints = FieldLoader.GetValue<HashSet<int>>("DisabledSpawnPoints", node.Value.Value);
break;
}
}
@@ -144,7 +139,6 @@ namespace OpenRA.Network
public ClientState State = ClientState.Invalid;
public int Team;
public int Handicap;
public string Slot; // Slot ID, or null for observer
public string Bot; // Bot type, null for real clients
public int BotControllerClientIndex; // who added the bot to the slot
@@ -194,7 +188,6 @@ namespace OpenRA.Network
public bool LockFaction;
public bool LockColor;
public bool LockTeam;
public bool LockHandicap;
public bool LockSpawn;
public bool Required;
@@ -257,7 +250,8 @@ namespace OpenRA.Network
public bool OptionOrDefault(string id, bool def)
{
if (LobbyOptions.TryGetValue(id, out var option))
LobbyOptionState option;
if (LobbyOptions.TryGetValue(id, out option))
return option.IsEnabled;
return def;
@@ -265,7 +259,8 @@ namespace OpenRA.Network
public string OptionOrDefault(string id, string def)
{
if (LobbyOptions.TryGetValue(id, out var option))
LobbyOptionState option;
if (LobbyOptions.TryGetValue(id, out option))
return option.Value;
return def;
@@ -274,10 +269,7 @@ namespace OpenRA.Network
public string Serialize()
{
var sessionData = new List<MiniYamlNode>()
{
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
};
var sessionData = new List<MiniYamlNode>();
foreach (var client in Clients)
sessionData.Add(client.Serialize());

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Network
readonly Report[] syncReports = new Report[NumSyncReports];
int curIndex = 0;
static (string[] Names, Values Values) DumpSyncTrait(ISync sync)
static Pair<string[], Values> DumpSyncTrait(ISync sync)
{
var type = sync.GetType();
TypeInfo typeInfo;
@@ -41,7 +41,7 @@ namespace OpenRA.Network
foreach (var func in typeInfo.SerializableCopyOfMemberFunctions)
values[index++] = func(sync);
return (typeInfo.Names, values);
return Pair.New(typeInfo.Names, values);
}
public SyncReport(OrderManager orderManager)
@@ -120,9 +120,9 @@ namespace OpenRA.Network
Log.Write("sync", "\t {0} {1} {2} {3} ({4})".F(a.ActorID, a.Type, a.Owner, a.Trait, a.Hash));
var nvp = a.NamesValues;
for (int i = 0; i < nvp.Names.Length; i++)
if (nvp.Values[i] != null)
Log.Write("sync", "\t\t {0}: {1}".F(nvp.Names[i], nvp.Values[i]));
for (int i = 0; i < nvp.First.Length; i++)
if (nvp.Second[i] != null)
Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
}
Log.Write("sync", "Synced Effects:");
@@ -131,9 +131,9 @@ namespace OpenRA.Network
Log.Write("sync", "\t {0} ({1})", e.Name, e.Hash);
var nvp = e.NamesValues;
for (int i = 0; i < nvp.Names.Length; i++)
if (nvp.Values[i] != null)
Log.Write("sync", "\t\t {0}: {1}".F(nvp.Names[i], nvp.Values[i]));
for (int i = 0; i < nvp.First.Length; i++)
if (nvp.Second[i] != null)
Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
}
Log.Write("sync", "Orders Issued:");
@@ -163,14 +163,14 @@ namespace OpenRA.Network
public string Owner;
public string Trait;
public int Hash;
public (string[] Names, Values Values) NamesValues;
public Pair<string[], Values> NamesValues;
}
struct EffectReport
{
public string Name;
public int Hash;
public (string[] Names, Values Values) NamesValues;
public Pair<string[], Values> NamesValues;
}
struct TypeInfo

View File

@@ -9,8 +9,10 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Server;
using OpenRA.Traits;
@@ -27,6 +29,13 @@ namespace OpenRA.Network
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
{
if (world != null)
{
if (!world.WorldActor.TraitsImplementing<IValidateOrder>().All(vo =>
vo.OrderValidation(orderManager, world, clientId, order)))
return;
}
switch (order.OrderString)
{
// Server message
@@ -39,13 +48,7 @@ namespace OpenRA.Network
{
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
if (client != null)
{
client.State = Session.ClientState.Disconnected;
var player = world?.FindPlayerByClient(client);
if (player != null)
world.OnPlayerDisconnected(player);
}
break;
}
@@ -144,7 +147,8 @@ namespace OpenRA.Network
var data = MiniYaml.FromString(order.TargetString)[0];
var traitIndex = int.Parse(data.Key);
world?.AddGameSaveTraitData(traitIndex, data.Value);
if (world != null)
world.AddGameSaveTraitData(traitIndex, data.Value);
break;
}
@@ -188,8 +192,9 @@ namespace OpenRA.Network
var request = HandshakeRequest.Deserialize(order.TargetString);
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
Game.ExternalMods.TryGetValue(externalKey, out var external))
ExternalMod external;
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version)
&& Game.ExternalMods.TryGetValue(externalKey, out external))
{
// The ConnectionFailedLogic will prompt the user to switch mods
orderManager.ServerExternalMod = external;
@@ -332,27 +337,17 @@ namespace OpenRA.Network
default:
{
if (world == null)
break;
if (order.GroupedActors == null)
ResolveOrder(order, world, orderManager, clientId);
else
foreach (var subject in order.GroupedActors)
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
ResolveOrder(order);
break;
}
}
}
static void ResolveOrder(Order order, World world, OrderManager orderManager, int clientId)
static void ResolveOrder(Order order)
{
if (order.Subject == null || order.Subject.IsDead)
return;
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
order.Subject.ResolveOrder(order);
if (order.Subject != null && !order.Subject.IsDead)
foreach (var t in order.Subject.TraitsImplementing<IResolveOrder>())
t.ResolveOrder(order.Subject, order);
}
static void SetOrderLag(OrderManager o)

View File

@@ -26,7 +26,7 @@ namespace OpenRA
readonly Cache<string, Type> typeCache;
readonly Cache<Type, ConstructorInfo> ctorCache;
readonly (Assembly Assembly, string Namespace)[] assemblies;
readonly Pair<Assembly, string>[] assemblies;
public ObjectCreator(Manifest manifest, InstalledMods mods)
{
@@ -48,7 +48,8 @@ namespace OpenRA
// 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;
if (!ResolvedAssemblies.TryGetValue(hash, out assembly))
{
assembly = Assembly.LoadFile(resolvedPath);
ResolvedAssemblies.Add(hash, assembly);
@@ -58,7 +59,7 @@ namespace OpenRA
}
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => (asm, ns))).ToArray();
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => Pair.New(asm, ns))).ToArray();
}
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
@@ -67,7 +68,10 @@ namespace OpenRA
if (a.FullName == e.Name)
return a;
return assemblies?.Select(a => a.Assembly).FirstOrDefault(a => a.FullName == e.Name);
if (assemblies == null)
return null;
return assemblies.Select(a => a.First).FirstOrDefault(a => a.FullName == e.Name);
}
// Only used by the linter to prevent exceptions from being thrown during a lint run
@@ -102,7 +106,7 @@ namespace OpenRA
public Type FindType(string className)
{
return assemblies
.Select(pair => pair.Assembly.GetType(pair.Namespace + "." + className, false))
.Select(pair => pair.First.GetType(pair.Second + "." + className, false))
.FirstOrDefault(t => t != null);
}
@@ -142,7 +146,7 @@ namespace OpenRA
public IEnumerable<Type> GetTypes()
{
return assemblies.Select(ma => ma.Assembly).Distinct()
return assemblies.Select(ma => ma.First).Distinct()
.SelectMany(ma => ma.GetTypes());
}

View File

@@ -1,27 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<UseVSHostingProcess>false</UseVSHostingProcess>
<LangVersion>7.3</LangVersion>
<LangVersion>5</LangVersion>
<DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
<RootNamespace>OpenRA</RootNamespace>
<OutputPath>../bin</OutputPath>
<OutputPath>..</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
<Configurations>Release;Debug</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
<Configurations>Release;Debug;Release-x86</Configurations>
<ManagedExeLauncher Condition="('$(OS)' != 'Windows_NT')">mono </ManagedExeLauncher>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
@@ -31,13 +27,44 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
<Prefer32bit>true</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=ra</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Dune 2000'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=d2k</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Dawn'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=cnc</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Sun'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=ts</StartArguments>
</PropertyGroup>
<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="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<Reference Include="Open.Nat">
<HintPath>..\thirdparty\download\Open.Nat.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>False</Private>
</Reference>
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<None Include="App.config" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<AdditionalFiles Include="../stylecop.json" />
<AdditionalFiles Include="Properties/launchSettings.json" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
@@ -45,4 +72,8 @@
<Analyzer Remove="@(Analyzer)" />
</ItemGroup>
</Target>
<Target Name="PostProcess" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(IntermediateOutputPath)\$(TargetFileName).processed">
<Exec Command="$(ManagedExeLauncher)&quot;$(TargetDir)OpenRA.PostProcess.exe&quot; &quot;$(TargetPath)&quot; -LAA" />
<Touch Files="$(IntermediateOutputPath)\$(TargetFileName).processed" AlwaysCreate="true" />
</Target>
</Project>

View File

@@ -10,7 +10,7 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Orders
@@ -18,14 +18,13 @@ namespace OpenRA.Orders
public class GenericSelectTarget : UnitOrderGenerator
{
public readonly string OrderName;
protected readonly IEnumerable<Actor> Subjects;
protected readonly string Cursor;
protected readonly MouseButton ExpectedButton;
protected IEnumerable<Actor> subjects;
public GenericSelectTarget(IEnumerable<Actor> subjects, string order, string cursor, MouseButton button)
{
this.subjects = subjects;
Subjects = subjects;
OrderName = order;
Cursor = cursor;
ExpectedButton = button;
@@ -54,7 +53,8 @@ namespace OpenRA.Orders
world.CancelInputMode();
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, subjects.ToArray());
foreach (var subject in Subjects)
yield return new Order(OrderName, subject, Target.FromCell(world, cell), queued);
}
}
@@ -69,11 +69,6 @@ namespace OpenRA.Orders
return true;
}
public override void SelectionChanged(World world, IEnumerable<Actor> selected)
{
subjects = selected;
}
public override bool ClearSelectionOnLeftClick { get { return false; } }
}
}

View File

@@ -24,6 +24,5 @@ namespace OpenRA
string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi);
void Deactivate();
bool HandleKeyPress(KeyInput e);
void SelectionChanged(World world, IEnumerable<Actor> selected);
}
}

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