Compare commits
199 Commits
playtest-2
...
geoip-cach
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0135dd9ed3 | ||
|
|
a6e9b86bbe | ||
|
|
237c4444b5 | ||
|
|
a68467292e | ||
|
|
978c69d0c3 | ||
|
|
3eabc59921 | ||
|
|
2b3d99fac2 | ||
|
|
2bdefe0e9e | ||
|
|
de81fc2aca | ||
|
|
b514e0a6e7 | ||
|
|
715dfa4541 | ||
|
|
ac57a37224 | ||
|
|
afd620b092 | ||
|
|
552bceb07c | ||
|
|
fe58ed1283 | ||
|
|
361e2d463c | ||
|
|
259c8d2c98 | ||
|
|
e12c1dc9aa | ||
|
|
7fb49e383d | ||
|
|
1df3e28253 | ||
|
|
bacec2689d | ||
|
|
bf397591f9 | ||
|
|
bd1a936c7a | ||
|
|
0871d6e321 | ||
|
|
265d296db6 | ||
|
|
5ec136b57c | ||
|
|
f1e8f9c9d0 | ||
|
|
f03841c4e4 | ||
|
|
0ae58ff0ea | ||
|
|
390c1899ca | ||
|
|
89aa6d1e4e | ||
|
|
24638b02a9 | ||
|
|
4b0ab6ab37 | ||
|
|
1f02d9f141 | ||
|
|
0103c38c13 | ||
|
|
3cc76f91b4 | ||
|
|
10dc248f07 | ||
|
|
d1f89c6217 | ||
|
|
76ba4fc32d | ||
|
|
37f90fff44 | ||
|
|
9fa6da3bc7 | ||
|
|
aa8cf237ab | ||
|
|
c131728aa4 | ||
|
|
2abd137494 | ||
|
|
90815ace59 | ||
|
|
53d916d7f1 | ||
|
|
2b4035979b | ||
|
|
38cdc93010 | ||
|
|
346dad3898 | ||
|
|
42256bc262 | ||
|
|
70babb4067 | ||
|
|
3603e6373d | ||
|
|
bd1760682f | ||
|
|
52d0490f95 | ||
|
|
b3b0aa75ae | ||
|
|
e42d177920 | ||
|
|
c2156af7b0 | ||
|
|
86394eb56c | ||
|
|
2d7790f5e4 | ||
|
|
3df43529a6 | ||
|
|
a1c9b27057 | ||
|
|
089dd233a5 | ||
|
|
86a7a0bd6c | ||
|
|
7ebca36a9c | ||
|
|
d5aed5a88a | ||
|
|
dac1f270ce | ||
|
|
839be24053 | ||
|
|
91c4179f05 | ||
|
|
a4b427bfac | ||
|
|
250f5bec18 | ||
|
|
336e2a10e0 | ||
|
|
b5e9b7635e | ||
|
|
0df7fa1596 | ||
|
|
c10487d635 | ||
|
|
bc9b3bef74 | ||
|
|
e57462e7ca | ||
|
|
78bf27709f | ||
|
|
fc84cd9204 | ||
|
|
e361f7b246 | ||
|
|
b0497b7505 | ||
|
|
a894e31fa5 | ||
|
|
dd062adec2 | ||
|
|
d187575a2c | ||
|
|
85096c4ba2 | ||
|
|
e13fd693c3 | ||
|
|
cc35512472 | ||
|
|
4e548291ce | ||
|
|
3ba86f329f | ||
|
|
5b34af0f12 | ||
|
|
0a9eb1ff83 | ||
|
|
942dd0e5f7 | ||
|
|
400102f3d3 | ||
|
|
9ccdeb3d36 | ||
|
|
7e0f0dd2d2 | ||
|
|
d920cbb7f6 | ||
|
|
417677e6f4 | ||
|
|
33f3038316 | ||
|
|
429dbe3e0c | ||
|
|
471fc44751 | ||
|
|
1b8e346307 | ||
|
|
a456583a08 | ||
|
|
a63c17baab | ||
|
|
9c4faddc0f | ||
|
|
6828c4c1e9 | ||
|
|
80131e7ec0 | ||
|
|
ac381a6f58 | ||
|
|
521b516bf9 | ||
|
|
6a825f8e60 | ||
|
|
f0a243ca10 | ||
|
|
e5457a3390 | ||
|
|
47e21f8bef | ||
|
|
331b854e4e | ||
|
|
274bc9cbba | ||
|
|
827f8d95b4 | ||
|
|
2946dd35d5 | ||
|
|
74d884787d | ||
|
|
5516e16fb8 | ||
|
|
cadf4eb322 | ||
|
|
269249e86e | ||
|
|
30f87d2308 | ||
|
|
8b7e72b95e | ||
|
|
1e64048956 | ||
|
|
8512e696f5 | ||
|
|
6a03a9ec5f | ||
|
|
5e04c99d57 | ||
|
|
82f15491c0 | ||
|
|
101843fbb7 | ||
|
|
9e534f3804 | ||
|
|
ca3cfc0184 | ||
|
|
d62fb901e2 | ||
|
|
73a2b59c2c | ||
|
|
2c7a56625c | ||
|
|
e2572b214f | ||
|
|
d22cd3a74f | ||
|
|
0c8fcedfdf | ||
|
|
99009c37ce | ||
|
|
d9f5771778 | ||
|
|
d35b5070fb | ||
|
|
02f41f9afc | ||
|
|
c797aa1d5e | ||
|
|
b2f7f67756 | ||
|
|
09014ab6d5 | ||
|
|
8f8747d65e | ||
|
|
be19e137e2 | ||
|
|
3155291064 | ||
|
|
a5b22e6a36 | ||
|
|
9c251e8b6a | ||
|
|
6056568182 | ||
|
|
7b7c1da18d | ||
|
|
fb5b4b3547 | ||
|
|
19918d485e | ||
|
|
45c6c6ba10 | ||
|
|
0e436bc686 | ||
|
|
b0dfea0a09 | ||
|
|
2c4e6c4188 | ||
|
|
9f3254dbd1 | ||
|
|
88cdad4189 | ||
|
|
4ba50a4379 | ||
|
|
2b6c104011 | ||
|
|
4b446d100e | ||
|
|
f9ca2114a9 | ||
|
|
afc9c6ef85 | ||
|
|
ac200f6173 | ||
|
|
73a78eadb1 | ||
|
|
c5139fb6c2 | ||
|
|
9faf9aa1b9 | ||
|
|
3c2e9be248 | ||
|
|
5b59f6612f | ||
|
|
3d69363f35 | ||
|
|
b580b4fd33 | ||
|
|
74f86d70f8 | ||
|
|
3959104f9b | ||
|
|
1ff037a257 | ||
|
|
32700df117 | ||
|
|
b4edec215e | ||
|
|
dffa1e45f4 | ||
|
|
df3b6dde34 | ||
|
|
834bbf467e | ||
|
|
4d4f94208e | ||
|
|
416713de0c | ||
|
|
c523ca8efe | ||
|
|
9acea56108 | ||
|
|
44a7422375 | ||
|
|
0d0e7eb179 | ||
|
|
ea6c840343 | ||
|
|
d2db0913ac | ||
|
|
3721dae74d | ||
|
|
9050a2447b | ||
|
|
df4c363e9c | ||
|
|
a909a3e692 | ||
|
|
dd26253905 | ||
|
|
69b7ba2d22 | ||
|
|
d2f306e488 | ||
|
|
4a6fefa434 | ||
|
|
16e0ea611e | ||
|
|
05a2e77be2 | ||
|
|
dd2fa36261 | ||
|
|
5fa1dec6d8 | ||
|
|
f86d96794d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,7 +26,7 @@ mods/*/*.pdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
|
||||
@@ -34,12 +34,13 @@ env:
|
||||
# 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"
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult OpenRA.Test.dll;
|
||||
fi
|
||||
|
||||
# Only watch the development branch and tagged release.
|
||||
branches:
|
||||
|
||||
6
AUTHORS
6
AUTHORS
@@ -160,9 +160,6 @@ FreeType License.
|
||||
|
||||
Using OpenAL Soft distributed under the GNU LGPL.
|
||||
|
||||
Using MaxMind GeoIP2 .NET API distributed under
|
||||
the Apache 2.0 license.
|
||||
|
||||
Using SDL2-CS and OpenAL-CS created by Ethan
|
||||
Lee and released under the zlib license.
|
||||
|
||||
@@ -182,6 +179,9 @@ Krueger and distributed under the GNU GPL terms.
|
||||
Using rix0rrr.BeaconLib developed by Rico Huijbers
|
||||
distributed under MIT License.
|
||||
|
||||
This site or product includes IP2Location LITE data
|
||||
available from http://www.ip2location.com.
|
||||
|
||||
Finally, special thanks goes to the original teams
|
||||
at Westwood Studios and EA for creating the classic
|
||||
games which OpenRA aims to reimagine.
|
||||
|
||||
37
INSTALL.md
37
INSTALL.md
@@ -8,15 +8,9 @@ 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.6.1 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net461) (or via Visual Studio 2017)
|
||||
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
|
||||
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
|
||||
|
||||
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.
|
||||
|
||||
@@ -25,11 +19,17 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
|
||||
Linux
|
||||
=====
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
|
||||
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.
|
||||
|
||||
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.
|
||||
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
|
||||
|
||||
If you choose to use system libraries, or your system is not x86_64, you will need to install 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`.
|
||||
|
||||
@@ -95,18 +95,17 @@ The EPEL repository is required in order for the following command to run proper
|
||||
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
|
||||
```
|
||||
|
||||
OSX
|
||||
macOS
|
||||
=====
|
||||
|
||||
Before compiling OpenRA you must install the following dependencies:
|
||||
* [Mono >= 5.4](https://www.mono-project.com/download/stable/#download-mac)
|
||||
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
|
||||
|
||||
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)
|
||||
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
|
||||
|
||||
To compile OpenRA, run `make` from the command line.
|
||||
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`)
|
||||
|
||||
Run with `./launch-game.sh`.
|
||||
|
||||
158
Makefile
158
Makefile
@@ -3,15 +3,13 @@
|
||||
# to compile, run:
|
||||
# make [DEBUG=true]
|
||||
#
|
||||
# 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 compile using system libraries for native dependencies, run:
|
||||
# make [DEBUG=true] TARGETPLATFORM=unix-generic
|
||||
#
|
||||
# to check the official mods for erroneous yaml files, run:
|
||||
# make test
|
||||
#
|
||||
# to check the official mod dlls for StyleCop violations, run:
|
||||
# to check the engine and official mod dlls for code style violations, run:
|
||||
# make check
|
||||
#
|
||||
# to install, run:
|
||||
@@ -22,6 +20,7 @@
|
||||
#
|
||||
# to install the engine and common mod files (omitting the default mods):
|
||||
# make install-engine
|
||||
# make install-dependencies
|
||||
# make install-common-mod-files
|
||||
#
|
||||
# to uninstall, run:
|
||||
@@ -40,11 +39,11 @@
|
||||
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 MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
|
||||
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.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
|
||||
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
|
||||
|
||||
NUNIT_LIBS_PATH :=
|
||||
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
|
||||
@@ -81,24 +80,31 @@ MSBUILD = msbuild -verbosity:m -nologo
|
||||
# Enable 32 bit builds while generating the windows installer
|
||||
WIN32 = false
|
||||
|
||||
# dependencies
|
||||
ifndef TARGETPLATFORM
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
TARGETPLATFORM = osx-x64
|
||||
else
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
TARGETPLATFORM = linux-x64
|
||||
else
|
||||
TARGETPLATFORM = unix-generic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# 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)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
os-dependencies = osx-dependencies
|
||||
else
|
||||
os-dependencies = linux-dependencies
|
||||
endif
|
||||
|
||||
check-scripts:
|
||||
@echo
|
||||
@echo "Checking for Lua syntax errors..."
|
||||
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
|
||||
@luac -p $(shell find lua/* -iname '*.lua')
|
||||
|
||||
check: dependencies
|
||||
check:
|
||||
@echo
|
||||
@echo "Compiling in debug mode..."
|
||||
@$(MSBUILD) -t:build -p:Configuration=Debug
|
||||
@@ -112,26 +118,6 @@ check: dependencies
|
||||
@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..."
|
||||
@@ -148,51 +134,21 @@ test: core
|
||||
|
||||
########################## MAKE/INSTALL RULES ##########################
|
||||
#
|
||||
all: dependencies core
|
||||
all: 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
|
||||
@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:
|
||||
@ $(MSBUILD) -t:clean
|
||||
@-$(RM_F) *.config
|
||||
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
|
||||
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
|
||||
@-$(RM_F) *.exe *.dll *.dll.config *.so *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
|
||||
@-$(RM_RF) ./*/bin ./*/obj
|
||||
@-$(RM_RF) ./thirdparty/download
|
||||
|
||||
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
|
||||
@ $(MSBUILD) -t:clean
|
||||
|
||||
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
|
||||
@@ -202,10 +158,33 @@ version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mo
|
||||
rm $${i}.tmp; \
|
||||
done
|
||||
|
||||
install: dependencies core install-core
|
||||
install: core install-core
|
||||
|
||||
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
|
||||
|
||||
install-dependencies:
|
||||
ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
|
||||
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) soft_oal.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SDL2.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) freetype6.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) lua51.dll "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), linux-x64)
|
||||
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) soft_oal.so "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SDL2.so "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) freetype6.so "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) lua51.so "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
ifeq ($(TARGETPLATFORM), osx-x64)
|
||||
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) soft_oal.dylib "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) SDL2.dylib "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) freetype6.dylib "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) lua51.dylib "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
|
||||
install-engine:
|
||||
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
|
||||
@@ -214,10 +193,13 @@ install-engine:
|
||||
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
ifneq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
|
||||
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
|
||||
endif
|
||||
@$(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)"
|
||||
@@ -227,8 +209,7 @@ install-engine:
|
||||
@$(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) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
@$(INSTALL_PROGRAM) BeaconLib.dll "$(DATA_INSTALL_DIR)"
|
||||
|
||||
install-common-mod-files:
|
||||
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
|
||||
@@ -353,23 +334,28 @@ uninstall:
|
||||
|
||||
help:
|
||||
@echo 'to compile, run:'
|
||||
@echo ' make [DEBUG=false]'
|
||||
@echo ' make [DEBUG=true]'
|
||||
@echo
|
||||
@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 'to compile using system libraries for native dependencies, run:'
|
||||
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
|
||||
@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
|
||||
@echo 'to install, run:'
|
||||
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
|
||||
@echo
|
||||
@echo 'to install Linux startup scripts, desktop files and icons'
|
||||
@echo ' make install-linux-shortcuts [DEBUG=false]'
|
||||
@echo
|
||||
@echo ' to install the engine and common mod files (omitting the default mods):'
|
||||
@echo ' make install-engine'
|
||||
@echo ' make install-dependencies'
|
||||
@echo ' make install-common-mod-files'
|
||||
@echo
|
||||
@echo 'to uninstall, run:'
|
||||
@echo ' make uninstall'
|
||||
@echo
|
||||
@@ -382,4 +368,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.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
|
||||
.PHONY: check-scripts check test all core clean version install install-linux-shortcuts install-dependencies 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
|
||||
|
||||
@@ -74,6 +74,31 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -93,6 +118,8 @@ namespace OpenRA
|
||||
{
|
||||
var init = new ActorInitializer(this, initDict);
|
||||
|
||||
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
|
||||
|
||||
World = world;
|
||||
ActorID = world.NextAID();
|
||||
if (initDict.Contains<OwnerInit>())
|
||||
@@ -148,9 +175,36 @@ namespace OpenRA
|
||||
{
|
||||
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);
|
||||
|
||||
// TODO: Some 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;
|
||||
@@ -233,7 +287,7 @@ namespace OpenRA
|
||||
yield return r;
|
||||
}
|
||||
|
||||
public Rectangle MouseBounds(WorldRenderer wr)
|
||||
public Polygon MouseBounds(WorldRenderer wr)
|
||||
{
|
||||
foreach (var mb in mouseBounds)
|
||||
{
|
||||
@@ -242,7 +296,7 @@ namespace OpenRA
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Rectangle.Empty;
|
||||
return Polygon.Empty;
|
||||
}
|
||||
|
||||
public void QueueActivity(bool queued, Activity nextActivity)
|
||||
@@ -454,6 +508,60 @@ namespace OpenRA
|
||||
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.</summary>
|
||||
/// <returns>The token that is used to revoke this condition.</returns>
|
||||
public int GrantCondition(string condition)
|
||||
{
|
||||
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)
|
||||
{
|
||||
string condition;
|
||||
if (!conditionTokens.TryGetValue(token, out 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;
|
||||
|
||||
20
OpenRA.Game/DefaultPlayer.cs
Normal file
20
OpenRA.Game/DefaultPlayer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,9 @@ namespace OpenRA
|
||||
|
||||
public ExternalMods()
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
// Don't try to load mod icons if we don't have a texture to put them in
|
||||
if (Game.Renderer != null)
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
|
||||
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
@@ -94,20 +96,24 @@ namespace OpenRA
|
||||
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
|
||||
{
|
||||
var mod = FieldLoader.Load<ExternalMod>(yaml);
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
if (sheetBuilder != null)
|
||||
{
|
||||
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
|
||||
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
|
||||
mod.Icon = sheetBuilder.Add(new Png(stream));
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
|
||||
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
|
||||
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
|
||||
|
||||
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
|
||||
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
|
||||
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
|
||||
}
|
||||
|
||||
// Avoid possibly overwriting a valid mod with an obviously bogus one
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace OpenRA
|
||||
|
||||
static int WindingDirectionTest(int2 v0, int2 v1, int2 p)
|
||||
{
|
||||
return (v1.X - v0.X) * (p.Y - v0.Y) - (p.X - v0.X) * (v1.Y - v0.Y);
|
||||
return Math.Sign((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,6 +101,16 @@ 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.
|
||||
|
||||
@@ -51,7 +51,6 @@ 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,13 +61,13 @@ namespace OpenRA
|
||||
|
||||
public static event Action OnShellmapLoaded = () => { };
|
||||
|
||||
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
|
||||
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
|
||||
{
|
||||
var connection = new NetworkConnection(host, port);
|
||||
var connection = new NetworkConnection(endpoint);
|
||||
if (recordReplay)
|
||||
connection.StartRecording(() => { return TimestampedFilename(); });
|
||||
|
||||
var om = new OrderManager(host, port, password, connection);
|
||||
var om = new OrderManager(endpoint, password, connection);
|
||||
JoinInner(om);
|
||||
return om;
|
||||
}
|
||||
@@ -89,12 +88,12 @@ namespace OpenRA
|
||||
|
||||
public static void JoinReplay(string replayFile)
|
||||
{
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
|
||||
}
|
||||
|
||||
static void JoinLocal()
|
||||
{
|
||||
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
|
||||
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
|
||||
}
|
||||
|
||||
// More accurate replacement for Environment.TickCount
|
||||
@@ -105,14 +104,14 @@ namespace OpenRA
|
||||
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
|
||||
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
|
||||
|
||||
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
|
||||
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
|
||||
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
|
||||
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
|
||||
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
|
||||
|
||||
public static void RemoteDirectConnect(string host, int port)
|
||||
public static void RemoteDirectConnect(ConnectionTarget endpoint)
|
||||
{
|
||||
OnRemoteDirectConnect(host, port);
|
||||
OnRemoteDirectConnect(endpoint);
|
||||
}
|
||||
|
||||
// Hacky workaround for orderManager visibility
|
||||
@@ -234,7 +233,7 @@ namespace OpenRA
|
||||
|
||||
LobbyInfoChanged += lobbyReady;
|
||||
|
||||
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
|
||||
om = JoinServer(CreateLocalServer(mapUID), "");
|
||||
}
|
||||
|
||||
public static bool IsHost
|
||||
@@ -302,6 +301,7 @@ 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)
|
||||
@@ -385,7 +385,7 @@ namespace OpenRA
|
||||
LobbyInfoChanged = () => { };
|
||||
ConnectionStateChanged = om => { };
|
||||
BeforeGameStart = () => { };
|
||||
OnRemoteDirectConnect = (a, b) => { };
|
||||
OnRemoteDirectConnect = endpoint => { };
|
||||
delayedActions = new ActionQueue();
|
||||
|
||||
Ui.ResetAll();
|
||||
@@ -899,12 +899,19 @@ namespace OpenRA
|
||||
return ModData.ObjectCreator.CreateObject<T>(name);
|
||||
}
|
||||
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
public static ConnectionTarget CreateServer(ServerSettings settings)
|
||||
{
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
|
||||
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();
|
||||
}
|
||||
|
||||
public static int CreateLocalServer(string map)
|
||||
public static ConnectionTarget CreateLocalServer(string map)
|
||||
{
|
||||
var settings = new ServerSettings()
|
||||
{
|
||||
@@ -913,9 +920,14 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
|
||||
var endpoints = new List<IPEndPoint>
|
||||
{
|
||||
new IPEndPoint(IPAddress.IPv6Loopback, 0),
|
||||
new IPEndPoint(IPAddress.Loopback, 0)
|
||||
};
|
||||
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
|
||||
|
||||
return server.Port;
|
||||
return server.GetEndpointForLocalConnection();
|
||||
}
|
||||
|
||||
public static bool IsCurrentWorld(World world)
|
||||
|
||||
@@ -50,6 +50,16 @@ namespace OpenRA.GameRules
|
||||
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() { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Graphics
|
||||
public bool IsDecoration { get; set; }
|
||||
|
||||
readonly SequenceProvider sequenceProvider;
|
||||
readonly Func<int> facingFunc;
|
||||
readonly Func<WAngle> 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, () => 0) { }
|
||||
: this(world, name, () => WAngle.Zero) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc)
|
||||
: this(world, name, facingFunc, null) { }
|
||||
|
||||
public Animation(World world, string name, Func<bool> paused)
|
||||
: this(world, name, () => 0, paused) { }
|
||||
: this(world, name, () => WAngle.Zero, paused) { }
|
||||
|
||||
public Animation(World world, string name, Func<int> facingFunc, Func<bool> paused)
|
||||
public Animation(World world, string name, Func<WAngle> facingFunc, Func<bool> paused)
|
||||
{
|
||||
sequenceProvider = world.Map.Rules.Sequences;
|
||||
Name = name.ToLowerInvariant();
|
||||
|
||||
@@ -15,9 +15,16 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum GLProfile
|
||||
{
|
||||
Modern,
|
||||
Embedded,
|
||||
Legacy
|
||||
}
|
||||
|
||||
public interface IPlatform
|
||||
{
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay);
|
||||
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
|
||||
ISoundEngine CreateSound(string device);
|
||||
IFont CreateFont(byte[] data);
|
||||
}
|
||||
@@ -32,7 +39,10 @@ namespace OpenRA
|
||||
Subtractive,
|
||||
Multiply,
|
||||
Multiplicative,
|
||||
DoubleMultiplicative
|
||||
DoubleMultiplicative,
|
||||
LowAdditive,
|
||||
Screen,
|
||||
Translucent
|
||||
}
|
||||
|
||||
public interface IPlatformWindow : IDisposable
|
||||
@@ -46,6 +56,7 @@ namespace OpenRA
|
||||
Size SurfaceSize { get; }
|
||||
int DisplayCount { get; }
|
||||
int CurrentDisplay { get; }
|
||||
bool HasInputFocus { get; }
|
||||
|
||||
event Action<float, float, float, float> OnWindowScaleChanged;
|
||||
|
||||
@@ -60,6 +71,10 @@ namespace OpenRA
|
||||
void SetHardwareCursor(IHardwareCursor cursor);
|
||||
void SetRelativeMouseMode(bool mode);
|
||||
void SetScaleModifier(float scale);
|
||||
|
||||
GLProfile GLProfile { get; }
|
||||
|
||||
GLProfile[] SupportedGLProfiles { get; }
|
||||
}
|
||||
|
||||
public interface IGraphicsContext : IDisposable
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace OpenRA.Graphics
|
||||
Rectangle Bounds { get; }
|
||||
|
||||
Sprite GetSprite(int frame);
|
||||
Sprite GetSprite(int frame, int facing);
|
||||
Sprite GetShadow(int frame, int facing);
|
||||
Sprite GetSprite(int frame, WAngle facing);
|
||||
Sprite GetShadow(int frame, WAngle facing);
|
||||
}
|
||||
|
||||
public interface ISpriteSequenceLoader
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace OpenRA.Graphics
|
||||
[Flags]
|
||||
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
|
||||
|
||||
public interface INotifyViewportZoomExtentsChanged
|
||||
{
|
||||
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
|
||||
}
|
||||
|
||||
public static class ViewportExts
|
||||
{
|
||||
public static bool Includes(this ScrollDirection d, ScrollDirection s)
|
||||
@@ -84,6 +89,8 @@ 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
|
||||
@@ -237,6 +244,9 @@ namespace OpenRA.Graphics
|
||||
Zoom = minZoom;
|
||||
else
|
||||
Zoom = Zoom.Clamp(minZoom, maxZoom);
|
||||
|
||||
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
|
||||
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
|
||||
}
|
||||
|
||||
public CPos ViewToWorld(int2 view)
|
||||
|
||||
@@ -277,11 +277,13 @@ namespace OpenRA.Graphics
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
|
||||
}
|
||||
|
||||
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
|
||||
{
|
||||
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);
|
||||
var points = b.Vertices
|
||||
.Select(p => Viewport.WorldToViewPx(p).ToFloat2())
|
||||
.ToArray();
|
||||
|
||||
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA
|
||||
public string Faction;
|
||||
|
||||
public bool LockColor = false;
|
||||
public Color Color = Color.FromAhsl(0, 0, 238);
|
||||
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
|
||||
|
||||
public bool LockSpawn = false;
|
||||
public int Spawn = 0;
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
#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;
|
||||
@@ -30,12 +33,63 @@ 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
|
||||
@@ -57,6 +111,16 @@ 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();
|
||||
@@ -138,35 +202,100 @@ namespace OpenRA.Network
|
||||
|
||||
sealed class NetworkConnection : EchoConnection
|
||||
{
|
||||
readonly TcpClient tcp;
|
||||
readonly ConnectionTarget target;
|
||||
TcpClient tcp;
|
||||
IPEndPoint endpoint;
|
||||
readonly List<byte[]> queuedSyncPackets = new List<byte[]>();
|
||||
volatile ConnectionState connectionState = ConnectionState.Connecting;
|
||||
volatile int clientId;
|
||||
bool disposed;
|
||||
string errorMessage;
|
||||
|
||||
public NetworkConnection(string host, int port)
|
||||
public override IPEndPoint EndPoint { get { return endpoint; } }
|
||||
|
||||
public override string ErrorMessage { get { return errorMessage; } }
|
||||
|
||||
public NetworkConnection(ConnectionTarget target)
|
||||
{
|
||||
try
|
||||
this.target = target;
|
||||
new Thread(NetworkConnectionConnect)
|
||||
{
|
||||
tcp = new TcpClient(host, port) { NoDelay = true };
|
||||
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 = GetType().Name + " " + host + ":" + port,
|
||||
Name = "{0} (receive from {1})".F(GetType().Name, tcp.Client.RemoteEndPoint),
|
||||
IsBackground = true
|
||||
}.Start(tcp.GetStream());
|
||||
}.Start();
|
||||
}
|
||||
catch
|
||||
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(object networkStreamObject)
|
||||
void NetworkConnectionReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
var networkStream = (NetworkStream)networkStreamObject;
|
||||
var reader = new BinaryReader(networkStream);
|
||||
var reader = new BinaryReader(tcp.GetStream());
|
||||
var handshakeProtocol = reader.ReadInt32();
|
||||
|
||||
if (handshakeProtocol != ProtocolVersion.Handshake)
|
||||
@@ -187,7 +316,11 @@ namespace OpenRA.Network
|
||||
AddPacket(new ReceivedPacket { FromClient = client, Data = buf });
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = "Connection failed";
|
||||
Log.Write("client", "Connection to {0} failed: {1}".F(endpoint, ex.Message));
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectionState = ConnectionState.NotConnected;
|
||||
|
||||
@@ -11,59 +11,120 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using MaxMind.Db;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
public class GeoIP
|
||||
{
|
||||
public class GeoIP2Record
|
||||
class IP2LocationReader
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2Record(GeoIP2Country country)
|
||||
public readonly DateTime Date;
|
||||
readonly Stream stream;
|
||||
readonly uint columnCount;
|
||||
readonly uint v4Count;
|
||||
readonly uint v4Offset;
|
||||
readonly uint v6Count;
|
||||
readonly uint v6Offset;
|
||||
|
||||
public IP2LocationReader(Stream source)
|
||||
{
|
||||
Country = country;
|
||||
// Copy stream data for reuse
|
||||
stream = new MemoryStream();
|
||||
source.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
|
||||
if (stream.ReadUInt8() != 1)
|
||||
throw new InvalidDataException("Only IP2Location type 1 databases are supported.");
|
||||
|
||||
columnCount = stream.ReadUInt8();
|
||||
var year = stream.ReadUInt8();
|
||||
var month = stream.ReadUInt8();
|
||||
var day = stream.ReadUInt8();
|
||||
Date = new DateTime(2000 + year, month, day);
|
||||
|
||||
v4Count = stream.ReadUInt32();
|
||||
v4Offset = stream.ReadUInt32();
|
||||
v6Count = stream.ReadUInt32();
|
||||
v6Offset = stream.ReadUInt32();
|
||||
}
|
||||
|
||||
public GeoIP2Country Country { get; set; }
|
||||
}
|
||||
|
||||
public class GeoIP2Country
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2Country(GeoIP2CountryNames names)
|
||||
BigInteger AddressForIndex(long index, bool isIPv6)
|
||||
{
|
||||
Names = names;
|
||||
var start = isIPv6 ? v6Offset : v4Offset;
|
||||
var offset = isIPv6 ? 12 : 0;
|
||||
stream.Seek(start + index * (4 * columnCount + offset) - 1, SeekOrigin.Begin);
|
||||
return new BigInteger(stream.ReadBytes(isIPv6 ? 16 : 4).Append((byte)0).ToArray());
|
||||
}
|
||||
|
||||
public GeoIP2CountryNames Names { get; set; }
|
||||
}
|
||||
|
||||
public class GeoIP2CountryNames
|
||||
{
|
||||
[Constructor]
|
||||
public GeoIP2CountryNames(string en)
|
||||
string CountryForIndex(long index, bool isIPv6)
|
||||
{
|
||||
English = en;
|
||||
// Read file offset for country entry
|
||||
var start = isIPv6 ? v6Offset : v4Offset;
|
||||
var offset = isIPv6 ? 12 : 0;
|
||||
stream.Seek(start + index * (4 * columnCount + offset) + offset + 3, SeekOrigin.Begin);
|
||||
var countryOffset = stream.ReadUInt32();
|
||||
|
||||
// Read length-prefixed country name
|
||||
stream.Seek(countryOffset + 3, SeekOrigin.Begin);
|
||||
var length = stream.ReadUInt8();
|
||||
|
||||
// "-" is used to represent an unknown country in the database
|
||||
var country = stream.ReadASCII(length);
|
||||
return country != "-" ? country : null;
|
||||
}
|
||||
|
||||
public string English { get; set; }
|
||||
public string LookupCountry(IPAddress ip)
|
||||
{
|
||||
var isIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6;
|
||||
if (!isIPv6 && ip.AddressFamily != AddressFamily.InterNetwork)
|
||||
return null;
|
||||
|
||||
// Locate IP using a binary search
|
||||
// The IP2Location python parser has an additional
|
||||
// optimization that can jump directly to the row, but this adds
|
||||
// extra complexity that isn't obviously needed for our limited database size
|
||||
long low = 0;
|
||||
long high = isIPv6 ? v6Count : v4Count;
|
||||
|
||||
// Append an empty byte to force the data to be treated as unsigned
|
||||
var ipNumber = new BigInteger(ip.GetAddressBytes().Reverse().Append((byte)0).ToArray());
|
||||
while (low <= high)
|
||||
{
|
||||
var mid = (low + high) / 2;
|
||||
var min = AddressForIndex(mid, isIPv6);
|
||||
var max = AddressForIndex(mid + 1, isIPv6);
|
||||
if (min <= ipNumber && ipNumber < max)
|
||||
return CountryForIndex(mid, isIPv6);
|
||||
|
||||
if (ipNumber < min)
|
||||
high = mid - 1;
|
||||
else
|
||||
low = mid + 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Reader database;
|
||||
static IP2LocationReader database;
|
||||
|
||||
public static void Initialize(string databasePath)
|
||||
public static void Initialize(string databasePath = "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP")
|
||||
{
|
||||
if (string.IsNullOrEmpty(databasePath))
|
||||
if (!File.Exists(databasePath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fileStream = new FileStream(databasePath, FileMode.Open, FileAccess.Read))
|
||||
using (var gzipStream = new GZipInputStream(fileStream))
|
||||
database = new Reader(gzipStream);
|
||||
using (var z = new ZipFile(databasePath))
|
||||
{
|
||||
var entry = z.FindEntry("IP2LOCATION-LITE-DB1.IPV6.BIN", false);
|
||||
database = new IP2LocationReader(z.GetInputStream(entry));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -77,9 +138,7 @@ namespace OpenRA.Network
|
||||
{
|
||||
try
|
||||
{
|
||||
var record = database.Find<GeoIP2Record>(ip);
|
||||
if (record != null)
|
||||
return record.Country.Names.English;
|
||||
return database.LookupCountry(ip);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum OrderFields : byte
|
||||
enum OrderFields : short
|
||||
{
|
||||
None = 0x0,
|
||||
Target = 0x01,
|
||||
@@ -35,7 +35,8 @@ namespace OpenRA
|
||||
ExtraLocation = 0x10,
|
||||
ExtraData = 0x20,
|
||||
TargetIsCell = 0x40,
|
||||
Subject = 0x80
|
||||
Subject = 0x80,
|
||||
Grouped = 0x100
|
||||
}
|
||||
|
||||
static class OrderFieldsExts
|
||||
@@ -52,6 +53,8 @@ namespace OpenRA
|
||||
public readonly Actor Subject;
|
||||
public readonly bool Queued;
|
||||
public readonly Target Target;
|
||||
public readonly Actor[] GroupedActors;
|
||||
|
||||
public string TargetString;
|
||||
public CPos ExtraLocation;
|
||||
public Actor[] ExtraActors;
|
||||
@@ -64,7 +67,7 @@ namespace OpenRA
|
||||
|
||||
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
|
||||
|
||||
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData)
|
||||
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
|
||||
{
|
||||
OrderString = orderString ?? "";
|
||||
Subject = subject;
|
||||
@@ -74,6 +77,7 @@ namespace OpenRA
|
||||
ExtraActors = extraActors;
|
||||
ExtraLocation = extraLocation;
|
||||
ExtraData = extraData;
|
||||
GroupedActors = groupedActors;
|
||||
}
|
||||
|
||||
public static Order Deserialize(World world, BinaryReader r)
|
||||
@@ -86,7 +90,7 @@ namespace OpenRA
|
||||
case OrderType.Fields:
|
||||
{
|
||||
var order = r.ReadString();
|
||||
var flags = (OrderFields)r.ReadByte();
|
||||
var flags = (OrderFields)r.ReadInt16();
|
||||
|
||||
Actor subject = null;
|
||||
if (flags.HasField(OrderFields.Subject))
|
||||
@@ -164,13 +168,23 @@ 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);
|
||||
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
|
||||
|
||||
if (subject == null && flags.HasField(OrderFields.Subject))
|
||||
return null;
|
||||
|
||||
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData);
|
||||
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
|
||||
}
|
||||
|
||||
case OrderType.Handshake:
|
||||
@@ -231,6 +245,11 @@ 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 };
|
||||
@@ -255,11 +274,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)
|
||||
: this(orderString, subject, Target.Invalid, null, queued, extraActors, 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, Target target, bool queued, Actor[] extraActors = null)
|
||||
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0) { }
|
||||
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
|
||||
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
|
||||
|
||||
public byte[] Serialize()
|
||||
{
|
||||
@@ -267,7 +286,7 @@ namespace OpenRA
|
||||
if (Type == OrderType.Handshake)
|
||||
minLength += TargetString.Length + 1;
|
||||
else if (Type == OrderType.Fields)
|
||||
minLength += 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
|
||||
minLength += 4 + 2 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
|
||||
|
||||
if (ExtraActors != null)
|
||||
minLength += ExtraActors.Length * 4;
|
||||
@@ -308,6 +327,9 @@ namespace OpenRA
|
||||
if (Queued)
|
||||
fields |= OrderFields.Queued;
|
||||
|
||||
if (GroupedActors != null)
|
||||
fields |= OrderFields.Grouped;
|
||||
|
||||
if (ExtraActors != null)
|
||||
fields |= OrderFields.ExtraActors;
|
||||
|
||||
@@ -317,7 +339,7 @@ namespace OpenRA
|
||||
if (Target.SerializableCell != null)
|
||||
fields |= OrderFields.TargetIsCell;
|
||||
|
||||
w.Write((byte)fields);
|
||||
w.Write((short)fields);
|
||||
|
||||
if (fields.HasField(OrderFields.Subject))
|
||||
w.Write(UIntFromActor(Subject));
|
||||
@@ -362,6 +384,13 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,10 @@ namespace OpenRA.Network
|
||||
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
|
||||
public World World;
|
||||
|
||||
public readonly string Host;
|
||||
public readonly int Port;
|
||||
public readonly ConnectionTarget Endpoint;
|
||||
public readonly string Password = "";
|
||||
|
||||
public string ServerError = "Server is not responding";
|
||||
public string ServerError = null;
|
||||
public bool AuthenticationFailed = false;
|
||||
public ExternalMod ServerExternalMod = null;
|
||||
|
||||
@@ -80,10 +79,9 @@ namespace OpenRA.Network
|
||||
Connection.Send(i, new List<byte[]>());
|
||||
}
|
||||
|
||||
public OrderManager(string host, int port, string password, IConnection conn)
|
||||
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
|
||||
{
|
||||
Host = host;
|
||||
Port = port;
|
||||
Endpoint = endpoint;
|
||||
Password = password;
|
||||
Connection = conn;
|
||||
syncReport = new SyncReport(this);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
@@ -32,6 +33,13 @@ 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;
|
||||
|
||||
@@ -337,7 +337,12 @@ namespace OpenRA.Network
|
||||
|
||||
default:
|
||||
{
|
||||
ResolveOrder(order);
|
||||
if (order.GroupedActors == null)
|
||||
ResolveOrder(order);
|
||||
else
|
||||
foreach (var subject in order.GroupedActors)
|
||||
ResolveOrder(Order.FromGroupedOrder(order, subject));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
@@ -16,8 +16,12 @@
|
||||
<ExternalConsole>false</ExternalConsole>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
|
||||
<Configurations>Release;Debug;Release-x86</Configurations>
|
||||
<ManagedExeLauncher Condition="('$(OS)' != 'Windows_NT')">mono </ManagedExeLauncher>
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
|
||||
@@ -27,7 +31,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
|
||||
<PropertyGroup Condition="'$(TargetPlatform)' == 'win-x86'">
|
||||
<Prefer32bit>true</Prefer32bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
|
||||
@@ -47,26 +51,12 @@
|
||||
<StartArguments>Game.Mod=ts</StartArguments>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<Reference Include="MaxMind.Db">
|
||||
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<None Include="App.config" />
|
||||
<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" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
<AdditionalFiles Include="Properties/launchSettings.json" />
|
||||
</ItemGroup>
|
||||
@@ -76,8 +66,4 @@
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="PostProcess" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(IntermediateOutputPath)\$(TargetFileName).processed">
|
||||
<Exec Command="$(ManagedExeLauncher)"$(TargetDir)OpenRA.PostProcess.exe" "$(TargetPath)" -LAA" />
|
||||
<Touch Files="$(IntermediateOutputPath)\$(TargetFileName).processed" AlwaysCreate="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Graphics;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Orders
|
||||
@@ -53,8 +53,7 @@ namespace OpenRA.Orders
|
||||
world.CancelInputMode();
|
||||
|
||||
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
|
||||
foreach (var subject in Subjects)
|
||||
yield return new Order(OrderName, subject, Target.FromCell(world, cell), queued);
|
||||
yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, Subjects.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace OpenRA.Orders
|
||||
|
||||
bool useSelect;
|
||||
if (Game.Settings.Game.UseClassicMouseStyle && !InputOverridesSelection(world, worldPixel, mi))
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>();
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>();
|
||||
else
|
||||
{
|
||||
var ordersWithCursor = world.Selection.Actors
|
||||
@@ -81,7 +81,7 @@ namespace OpenRA.Orders
|
||||
if (cursorOrder != null)
|
||||
return cursorOrder.Cursor;
|
||||
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>() &&
|
||||
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any());
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace OpenRA.Orders
|
||||
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
|
||||
{
|
||||
var actor = world.ScreenMap.ActorsAtMouse(xy)
|
||||
.Where(a => !a.Actor.IsDead)
|
||||
.Where(a => !a.Actor.IsDead && a.Actor.Info.HasTraitInfo<ISelectableInfo>() && (a.Actor.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(a.Actor)))
|
||||
.WithHighestSelectionPriority(xy, mi.Modifiers);
|
||||
|
||||
if (actor == null)
|
||||
|
||||
114
OpenRA.Game/Primitives/Polygon.cs
Normal file
114
OpenRA.Game/Primitives/Polygon.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Primitives
|
||||
{
|
||||
public struct Polygon
|
||||
{
|
||||
public static readonly Polygon Empty = new Polygon(Rectangle.Empty);
|
||||
|
||||
public readonly Rectangle BoundingRect;
|
||||
public readonly int2[] Vertices;
|
||||
bool isRectangle;
|
||||
|
||||
public Polygon(Rectangle bounds)
|
||||
{
|
||||
BoundingRect = bounds;
|
||||
Vertices = new[] { bounds.TopLeft, bounds.BottomLeft, bounds.BottomRight, bounds.TopRight };
|
||||
isRectangle = true;
|
||||
}
|
||||
|
||||
public Polygon(int2[] vertices)
|
||||
{
|
||||
if (vertices != null && vertices.Length > 0)
|
||||
{
|
||||
Vertices = vertices;
|
||||
var left = int.MaxValue;
|
||||
var right = int.MinValue;
|
||||
var top = int.MaxValue;
|
||||
var bottom = int.MinValue;
|
||||
foreach (var p in vertices)
|
||||
{
|
||||
left = Math.Min(left, p.X);
|
||||
right = Math.Max(right, p.X);
|
||||
top = Math.Min(top, p.Y);
|
||||
bottom = Math.Max(bottom, p.Y);
|
||||
}
|
||||
|
||||
BoundingRect = Rectangle.FromLTRB(left, top, right, bottom);
|
||||
isRectangle = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
isRectangle = true;
|
||||
BoundingRect = Rectangle.Empty;
|
||||
Vertices = Exts.MakeArray(4, _ => int2.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty { get { return BoundingRect.IsEmpty; } }
|
||||
public bool Contains(int2 xy)
|
||||
{
|
||||
return isRectangle ? BoundingRect.Contains(xy) : Vertices.PolygonContains(xy);
|
||||
}
|
||||
|
||||
public bool IntersectsWith(Rectangle rect)
|
||||
{
|
||||
var intersectsBoundingRect = BoundingRect.Left < rect.Right && BoundingRect.Right > rect.Left && BoundingRect.Top < rect.Bottom && BoundingRect.Bottom > rect.Top;
|
||||
if (isRectangle)
|
||||
return intersectsBoundingRect;
|
||||
|
||||
// Easy case 1: Rect and bounding box don't intersect
|
||||
if (!intersectsBoundingRect)
|
||||
return false;
|
||||
|
||||
// Easy case 2: Rect and bounding box intersect in a cross shape
|
||||
if ((rect.Left <= BoundingRect.Left && rect.Right >= BoundingRect.Right) || (rect.Top <= BoundingRect.Top && rect.Bottom >= BoundingRect.Bottom))
|
||||
return true;
|
||||
|
||||
// Easy case 3: Corner of rect is inside the polygon
|
||||
if (Vertices.PolygonContains(rect.TopLeft) || Vertices.PolygonContains(rect.TopRight) || Vertices.PolygonContains(rect.BottomLeft) || Vertices.PolygonContains(rect.BottomRight))
|
||||
return true;
|
||||
|
||||
// Easy case 4: Polygon vertex is inside rect
|
||||
if (Vertices.Any(p => rect.Contains(p)))
|
||||
return true;
|
||||
|
||||
// Hard case: check intersection of every line segment pair
|
||||
var rectVertices = new[]
|
||||
{
|
||||
rect.TopLeft,
|
||||
rect.BottomLeft,
|
||||
rect.BottomRight,
|
||||
rect.TopRight
|
||||
};
|
||||
|
||||
for (var i = 0; i < Vertices.Length; i++)
|
||||
for (var j = 0; j < 4; j++)
|
||||
if (Exts.LinesIntersect(Vertices[i], Vertices[(i + 1) % Vertices.Length], rectVertices[j], rectVertices[(j + 1) % 4]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var code = BoundingRect.GetHashCode();
|
||||
foreach (var v in Vertices)
|
||||
code = ((code << 5) + code) ^ v.GetHashCode();
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,11 @@ namespace OpenRA.Primitives
|
||||
public int2 Location { get { return new int2(X, Y); } }
|
||||
public Size Size { get { return new Size(Width, Height); } }
|
||||
|
||||
public int2 TopLeft { get { return Location; } }
|
||||
public int2 TopRight { get { return new int2(X + Width, Y); } }
|
||||
public int2 BottomLeft { get { return new int2(X, Y + Height); } }
|
||||
public int2 BottomRight { get { return new int2(X + Width, Y + Height); } }
|
||||
|
||||
public bool Contains(int x, int y)
|
||||
{
|
||||
return x >= Left && x < Right && y >= Top && y < Bottom;
|
||||
|
||||
@@ -139,5 +139,7 @@ namespace OpenRA.Primitives
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> ItemBounds { get { return itemBounds.Values; } }
|
||||
|
||||
public IEnumerable<T> Items { get { return itemBounds.Keys; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,15 @@ namespace OpenRA
|
||||
public RgbaColorRenderer RgbaColorRenderer { get; private set; }
|
||||
public SpriteRenderer SpriteRenderer { get; private set; }
|
||||
public RgbaSpriteRenderer RgbaSpriteRenderer { get; private set; }
|
||||
|
||||
public bool WindowHasInputFocus
|
||||
{
|
||||
get
|
||||
{
|
||||
return Window.HasInputFocus;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, SpriteFont> Fonts;
|
||||
|
||||
internal IPlatformWindow Window { get; private set; }
|
||||
@@ -66,7 +75,10 @@ namespace OpenRA
|
||||
this.platform = platform;
|
||||
var resolution = GetResolution(graphicSettings);
|
||||
|
||||
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, graphicSettings.VideoDisplay);
|
||||
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height),
|
||||
graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize,
|
||||
graphicSettings.VideoDisplay, graphicSettings.GLProfile);
|
||||
|
||||
Context = Window.Context;
|
||||
|
||||
TempBufferSize = graphicSettings.BatchSize;
|
||||
@@ -301,6 +313,8 @@ namespace OpenRA
|
||||
public Size NativeResolution { get { return Window.NativeWindowSize; } }
|
||||
public float WindowScale { get { return Window.EffectiveWindowScale; } }
|
||||
public float NativeWindowScale { get { return Window.NativeWindowScale; } }
|
||||
public GLProfile GLProfile { get { return Window.GLProfile; } }
|
||||
public GLProfile[] SupportedGLProfiles { get { return Window.SupportedGLProfiles; } }
|
||||
|
||||
public interface IBatchRenderer { void Flush(); }
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace OpenRA.Traits
|
||||
{
|
||||
public static int SelectionPriority(this ActorInfo a, Modifiers modifiers)
|
||||
{
|
||||
var selectableInfo = a.TraitInfoOrDefault<SelectableInfo>();
|
||||
var selectableInfo = a.TraitInfoOrDefault<ISelectableInfo>();
|
||||
return selectableInfo != null ? BaseSelectionPriority(selectableInfo, modifiers) : int.MinValue;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Traits
|
||||
|
||||
public static int SelectionPriority(this Actor a, Modifiers modifiers)
|
||||
{
|
||||
var info = a.Info.TraitInfo<SelectableInfo>();
|
||||
var info = a.Info.TraitInfo<ISelectableInfo>();
|
||||
var basePriority = BaseSelectionPriority(info, modifiers);
|
||||
|
||||
var viewer = (a.World.LocalPlayer == null || a.World.LocalPlayer.Spectating) ? a.World.RenderPlayer : a.World.LocalPlayer;
|
||||
@@ -47,7 +47,7 @@ namespace OpenRA.Traits
|
||||
}
|
||||
}
|
||||
|
||||
static int BaseSelectionPriority(SelectableInfo info, Modifiers modifiers)
|
||||
static int BaseSelectionPriority(ISelectableInfo info, Modifiers modifiers)
|
||||
{
|
||||
var priority = info.Priority;
|
||||
|
||||
@@ -73,14 +73,18 @@ namespace OpenRA.Traits
|
||||
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.MouseBounds, selectionPixel, modifiers));
|
||||
}
|
||||
|
||||
static long CalculateActorSelectionPriority(ActorInfo info, Rectangle bounds, int2 selectionPixel, Modifiers modifiers)
|
||||
static long CalculateActorSelectionPriority(ActorInfo info, Polygon bounds, int2 selectionPixel, Modifiers modifiers)
|
||||
{
|
||||
if (bounds.IsEmpty)
|
||||
return info.SelectionPriority(modifiers);
|
||||
|
||||
// Assume that the center of the polygon is the same as the center of the bounding box
|
||||
// This isn't necessarily true for arbitrary polygons, but is fine for the hexagonal and diamond
|
||||
// shapes that are currently implemented
|
||||
var br = bounds.BoundingRect;
|
||||
var centerPixel = new int2(
|
||||
bounds.Left + bounds.Size.Width / 2,
|
||||
bounds.Top + bounds.Size.Height / 2);
|
||||
br.Left + br.Size.Width / 2,
|
||||
br.Top + br.Size.Height / 2);
|
||||
|
||||
var pixelDistance = (centerPixel - selectionPixel).Length;
|
||||
return info.SelectionPriority(modifiers) - (long)pixelDistance << 16;
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
@@ -43,8 +42,6 @@ namespace OpenRA.Server
|
||||
{
|
||||
public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match.";
|
||||
|
||||
public readonly IPAddress Ip;
|
||||
public readonly int Port;
|
||||
public readonly MersenneTwister Random = new MersenneTwister();
|
||||
public readonly ServerType Type;
|
||||
|
||||
@@ -64,7 +61,7 @@ namespace OpenRA.Server
|
||||
public GameSave GameSave = null;
|
||||
|
||||
readonly int randomSeed;
|
||||
readonly TcpListener listener;
|
||||
readonly List<TcpListener> listeners = new List<TcpListener>();
|
||||
readonly TypeDictionary serverTraits = new TypeDictionary();
|
||||
readonly PlayerDatabase playerDatabase;
|
||||
|
||||
@@ -129,15 +126,43 @@ namespace OpenRA.Server
|
||||
t.GameEnded(this);
|
||||
}
|
||||
|
||||
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, ServerType type)
|
||||
public Server(List<IPEndPoint> endpoints, ServerSettings settings, ModData modData, ServerType type)
|
||||
{
|
||||
Log.AddChannel("server", "server.log", true);
|
||||
|
||||
listener = new TcpListener(endpoint);
|
||||
listener.Start();
|
||||
var localEndpoint = (IPEndPoint)listener.LocalEndpoint;
|
||||
Ip = localEndpoint.Address;
|
||||
Port = localEndpoint.Port;
|
||||
SocketException lastException = null;
|
||||
var checkReadServer = new List<Socket>();
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
var listener = new TcpListener(endpoint);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SocketException || ex is ArgumentException)
|
||||
Log.Write("server", "Failed to set socket option on {0}: {1}", endpoint.ToString(), ex.Message);
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
listener.Start();
|
||||
listeners.Add(listener);
|
||||
checkReadServer.Add(listener.Server);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
lastException = ex;
|
||||
Log.Write("server", "Failed to listen on {0}: {1}", endpoint.ToString(), ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (listeners.Count == 0)
|
||||
throw lastException;
|
||||
|
||||
Type = type;
|
||||
Settings = settings;
|
||||
|
||||
@@ -149,7 +174,8 @@ namespace OpenRA.Server
|
||||
|
||||
randomSeed = (int)DateTime.Now.ToBinary();
|
||||
|
||||
GeoIP.Initialize(settings.GeoIPDatabase);
|
||||
if (type != ServerType.Local && settings.EnableGeoIP)
|
||||
GeoIP.Initialize();
|
||||
|
||||
if (UPnP.Status == UPnPStatus.Enabled)
|
||||
UPnP.ForwardPort(Settings.ListenPort, Settings.ListenPort).Wait();
|
||||
@@ -185,7 +211,7 @@ namespace OpenRA.Server
|
||||
{
|
||||
var checkRead = new List<Socket>();
|
||||
if (State == ServerState.WaitingPlayers)
|
||||
checkRead.Add(listener.Server);
|
||||
checkRead.AddRange(checkReadServer);
|
||||
|
||||
checkRead.AddRange(Conns.Select(c => c.Socket));
|
||||
checkRead.AddRange(PreConns.Select(c => c.Socket));
|
||||
@@ -204,9 +230,10 @@ namespace OpenRA.Server
|
||||
|
||||
foreach (var s in checkRead)
|
||||
{
|
||||
if (s == listener.Server)
|
||||
var serverIndex = checkReadServer.IndexOf(s);
|
||||
if (serverIndex >= 0)
|
||||
{
|
||||
AcceptConnection();
|
||||
AcceptConnection(listeners[serverIndex]);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -245,9 +272,14 @@ namespace OpenRA.Server
|
||||
|
||||
PreConns.Clear();
|
||||
Conns.Clear();
|
||||
try { listener.Stop(); }
|
||||
catch { }
|
||||
}) { IsBackground = true }.Start();
|
||||
|
||||
foreach (var listener in listeners)
|
||||
{
|
||||
try { listener.Stop(); }
|
||||
catch { }
|
||||
}
|
||||
})
|
||||
{ IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
int nextPlayerIndex;
|
||||
@@ -256,7 +288,7 @@ namespace OpenRA.Server
|
||||
return nextPlayerIndex++;
|
||||
}
|
||||
|
||||
void AcceptConnection()
|
||||
void AcceptConnection(TcpListener listener)
|
||||
{
|
||||
Socket newSocket;
|
||||
|
||||
@@ -348,7 +380,7 @@ namespace OpenRA.Server
|
||||
{
|
||||
Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name),
|
||||
IPAddress = ipAddress.ToString(),
|
||||
AnonymizedIPAddress = Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
|
||||
AnonymizedIPAddress = Type != ServerType.Local && Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
|
||||
Location = GeoIP.LookupCountry(ipAddress),
|
||||
Index = newConn.PlayerIndex,
|
||||
PreferredColor = handshake.Client.PreferredColor,
|
||||
@@ -498,10 +530,16 @@ namespace OpenRA.Server
|
||||
profile.ProfileName, profile.ProfileID);
|
||||
}
|
||||
else if (profile.KeyRevoked)
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile = null;
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
|
||||
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
|
||||
}
|
||||
}
|
||||
else
|
||||
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",
|
||||
@@ -949,7 +987,8 @@ namespace OpenRA.Server
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
listener.Stop();
|
||||
foreach (var listener in listeners)
|
||||
listener.Stop();
|
||||
|
||||
Console.WriteLine("[{0}] Game started", DateTime.Now.ToString(Settings.TimestampFormat));
|
||||
|
||||
@@ -1011,5 +1050,22 @@ namespace OpenRA.Server
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionTarget GetEndpointForLocalConnection()
|
||||
{
|
||||
var endpoints = new List<DnsEndPoint>();
|
||||
foreach (var listener in listeners)
|
||||
{
|
||||
var endpoint = (IPEndPoint)listener.LocalEndpoint;
|
||||
if (IPAddress.IPv6Any.Equals(endpoint.Address))
|
||||
endpoints.Add(new DnsEndPoint(IPAddress.IPv6Loopback.ToString(), endpoint.Port));
|
||||
else if (IPAddress.Any.Equals(endpoint.Address))
|
||||
endpoints.Add(new DnsEndPoint(IPAddress.Loopback.ToString(), endpoint.Port));
|
||||
else
|
||||
endpoints.Add(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port));
|
||||
}
|
||||
|
||||
return new ConnectionTarget(endpoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,13 +82,12 @@ namespace OpenRA
|
||||
[Desc("Sets the timestamp format. Defaults to the ISO 8601 standard.")]
|
||||
public string TimestampFormat = "yyyy-MM-ddTHH:mm:ss";
|
||||
|
||||
[Desc("Path to a MaxMind GeoLite2 database to use for player geo-location.",
|
||||
"Database files can be downloaded from https://dev.maxmind.com/geoip/geoip2/geolite2/")]
|
||||
public string GeoIPDatabase = null;
|
||||
|
||||
[Desc("Allow clients to see anonymised IPs for other clients.")]
|
||||
public bool ShareAnonymizedIPs = true;
|
||||
|
||||
[Desc("Allow clients to see the country of other clients.")]
|
||||
public bool EnableGeoIP = true;
|
||||
|
||||
public ServerSettings Clone()
|
||||
{
|
||||
return (ServerSettings)MemberwiseClone();
|
||||
@@ -173,12 +172,15 @@ namespace OpenRA
|
||||
[Desc("Disable operating-system provided cursor rendering.")]
|
||||
public bool DisableHardwareCursors = false;
|
||||
|
||||
[Desc("Use OpenGL ES if both ES and regular OpenGL are available.")]
|
||||
public bool PreferGLES = false;
|
||||
|
||||
[Desc("Display index to use in a multi-monitor fullscreen setup.")]
|
||||
public int VideoDisplay = 0;
|
||||
|
||||
[Desc("Preferred OpenGL profile to use.",
|
||||
"Modern: OpenGL Core Profile 3.2 or greater.",
|
||||
"Embedded: OpenGL ES 3.0 or greater.",
|
||||
"Legacy: OpenGL 2.1 with framebuffer_object extension.")]
|
||||
public GLProfile GLProfile = GLProfile.Modern;
|
||||
|
||||
public int BatchSize = 8192;
|
||||
public int SheetSize = 2048;
|
||||
|
||||
@@ -228,7 +230,8 @@ namespace OpenRA
|
||||
public int MouseScrollDeadzone = 8;
|
||||
|
||||
public bool UseClassicMouseStyle = false;
|
||||
public bool ClassicMouseMiddleScroll = false;
|
||||
public bool UseAlternateScrollButton = false;
|
||||
|
||||
public StatusBarsType StatusBars = StatusBarsType.Standard;
|
||||
public TargetLinesType TargetLines = TargetLinesType.Manual;
|
||||
public bool UsePlayerStanceColors = false;
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Network;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class LaunchArguments
|
||||
@@ -38,17 +41,28 @@ namespace OpenRA
|
||||
FieldLoader.LoadField(this, f.Name, args.GetValue("Launch" + "." + f.Name, ""));
|
||||
}
|
||||
|
||||
public string GetConnectAddress()
|
||||
public ConnectionTarget GetConnectEndPoint()
|
||||
{
|
||||
var connect = string.Empty;
|
||||
try
|
||||
{
|
||||
Uri uri;
|
||||
if (!string.IsNullOrEmpty(URI))
|
||||
uri = new Uri(URI);
|
||||
else if (!string.IsNullOrEmpty(Connect))
|
||||
uri = new Uri("tcp://" + Connect);
|
||||
else
|
||||
return null;
|
||||
|
||||
if (!string.IsNullOrEmpty(Connect))
|
||||
connect = Connect;
|
||||
|
||||
if (!string.IsNullOrEmpty(URI))
|
||||
connect = URI.Substring(URI.IndexOf("://", System.StringComparison.Ordinal) + 3).TrimEnd('/');
|
||||
|
||||
return connect;
|
||||
if (uri.IsAbsoluteUri)
|
||||
return new ConnectionTarget(uri.Host, uri.Port);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("client", "Failed to parse Launch.URI or Launch.Connect: {0}", ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ namespace OpenRA.Traits
|
||||
public IRenderable[] Renderables = NoRenderables;
|
||||
public Rectangle[] ScreenBounds = NoBounds;
|
||||
|
||||
// TODO: Replace this with an int2[] polygon
|
||||
public Rectangle MouseBounds = Rectangle.Empty;
|
||||
public Polygon MouseBounds = Polygon.Empty;
|
||||
|
||||
static readonly IRenderable[] NoRenderables = new IRenderable[0];
|
||||
static readonly Rectangle[] NoBounds = new Rectangle[0];
|
||||
|
||||
@@ -66,9 +66,6 @@ namespace OpenRA.Traits
|
||||
void Kill(Actor self, Actor attacker, BitSet<DamageType> damageTypes);
|
||||
}
|
||||
|
||||
// depends on the order of pips in WorldRenderer.cs!
|
||||
public enum PipType { Transparent, Green, Yellow, Red, Gray, Blue, Ammo, AmmoEmpty }
|
||||
|
||||
[Flags]
|
||||
public enum Stance
|
||||
{
|
||||
@@ -123,43 +120,10 @@ namespace OpenRA.Traits
|
||||
IEnumerable<Rectangle> ScreenBounds(Actor self, WorldRenderer wr);
|
||||
}
|
||||
|
||||
// TODO: Replace Rectangle with an int2[] polygon
|
||||
public interface IMouseBounds { Rectangle MouseoverBounds(Actor self, WorldRenderer wr); }
|
||||
public interface IMouseBounds { Polygon MouseoverBounds(Actor self, WorldRenderer wr); }
|
||||
public interface IMouseBoundsInfo : ITraitInfoInterface { }
|
||||
public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); }
|
||||
|
||||
// HACK: This provides a shim for legacy code until it can be rewritten
|
||||
public interface IDecorationBounds { Rectangle DecorationBounds(Actor self, WorldRenderer wr); }
|
||||
public interface IDecorationBoundsInfo : ITraitInfoInterface { }
|
||||
public static class DecorationBoundsExtensions
|
||||
{
|
||||
public static Rectangle FirstNonEmptyBounds(this IEnumerable<IDecorationBounds> decorationBounds, Actor self, WorldRenderer wr)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var decoration in decorationBounds)
|
||||
{
|
||||
var bounds = decoration.DecorationBounds(self, wr);
|
||||
if (!bounds.IsEmpty)
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
public static Rectangle FirstNonEmptyBounds(this IDecorationBounds[] decorationBounds, Actor self, WorldRenderer wr)
|
||||
{
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var decoration in decorationBounds)
|
||||
{
|
||||
var bounds = decoration.DecorationBounds(self, wr);
|
||||
if (!bounds.IsEmpty)
|
||||
return bounds;
|
||||
}
|
||||
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IIssueOrder
|
||||
{
|
||||
IEnumerable<IOrderTargeter> Orders { get; }
|
||||
@@ -296,12 +260,14 @@ namespace OpenRA.Traits
|
||||
public interface ILoadsPalettes { void LoadPalettes(WorldRenderer wr); }
|
||||
public interface ILoadsPlayerPalettes { void LoadPlayerPalettes(WorldRenderer wr, string playerName, Color playerColor, bool replaceExisting); }
|
||||
public interface IPaletteModifier { void AdjustPalette(IReadOnlyDictionary<string, MutablePalette> b); }
|
||||
public interface IPips { IEnumerable<PipType> GetPips(Actor self); }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface ISelectionBar { float GetValue(); Color GetColor(); bool DisplayWhenEmpty { get; } }
|
||||
|
||||
public interface ISelectionDecorations { void DrawRollover(Actor self, WorldRenderer worldRenderer); }
|
||||
public interface ISelectionDecorations
|
||||
{
|
||||
IEnumerable<IRenderable> RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color);
|
||||
}
|
||||
|
||||
public interface IMapPreviewSignatureInfo : ITraitInfoInterface
|
||||
{
|
||||
@@ -440,6 +406,22 @@ namespace OpenRA.Traits
|
||||
bool SpatiallyPartitionable { get; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum SelectionPriorityModifiers
|
||||
{
|
||||
None = 0,
|
||||
Ctrl = 1,
|
||||
Alt = 2
|
||||
}
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface ISelectableInfo : ITraitInfoInterface
|
||||
{
|
||||
int Priority { get; }
|
||||
SelectionPriorityModifiers PriorityModifiers { get; }
|
||||
string Voice { get; }
|
||||
}
|
||||
|
||||
public interface ISelection
|
||||
{
|
||||
int Hash { get; }
|
||||
@@ -450,6 +432,8 @@ namespace OpenRA.Traits
|
||||
bool Contains(Actor a);
|
||||
void Combine(World world, IEnumerable<Actor> newSelection, bool isCombine, bool isClick);
|
||||
void Clear();
|
||||
bool RolloverContains(Actor a);
|
||||
void SetRollover(IEnumerable<Actor> actors);
|
||||
void DoControlGroup(World world, WorldRenderer worldRenderer, int group, Modifiers mods, int multiTapCount);
|
||||
void AddToControlGroup(Actor a, int group);
|
||||
void RemoveFromControlGroup(Actor a);
|
||||
@@ -555,4 +539,24 @@ namespace OpenRA.Traits
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface ICreationActivity { Activity GetCreationActivity(); }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface IObservesVariablesInfo : ITraitInfo { }
|
||||
|
||||
public delegate void VariableObserverNotifier(Actor self, IReadOnlyDictionary<string, int> variables);
|
||||
public struct VariableObserver
|
||||
{
|
||||
public VariableObserverNotifier Notifier;
|
||||
public IEnumerable<string> Variables;
|
||||
public VariableObserver(VariableObserverNotifier notifier, IEnumerable<string> variables)
|
||||
{
|
||||
Notifier = notifier;
|
||||
Variables = variables;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IObservesVariables
|
||||
{
|
||||
IEnumerable<VariableObserver> GetVariableObservers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,15 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public struct ActorBoundsPair : IEquatable<ActorBoundsPair>
|
||||
public struct ActorBoundsPair
|
||||
{
|
||||
public readonly Actor Actor;
|
||||
public readonly Polygon Bounds;
|
||||
|
||||
// TODO: Replace this with an int2[] polygon
|
||||
public readonly Rectangle Bounds;
|
||||
|
||||
public ActorBoundsPair(Actor actor, Rectangle bounds) { Actor = actor; Bounds = bounds; }
|
||||
|
||||
public static bool operator ==(ActorBoundsPair me, ActorBoundsPair other) { return me.Actor == other.Actor && Equals(me.Bounds, other.Bounds); }
|
||||
public static bool operator !=(ActorBoundsPair me, ActorBoundsPair other) { return !(me == other); }
|
||||
public ActorBoundsPair(Actor actor, Polygon bounds) { Actor = actor; Bounds = bounds; }
|
||||
|
||||
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
|
||||
|
||||
public bool Equals(ActorBoundsPair other) { return this == other; }
|
||||
public override bool Equals(object obj) { return obj is ActorBoundsPair && Equals((ActorBoundsPair)obj); }
|
||||
|
||||
public override string ToString() { return "{0}->{1}".F(Actor.Info.Name, Bounds.GetType().Name); }
|
||||
}
|
||||
|
||||
@@ -198,7 +190,7 @@ namespace OpenRA.Traits
|
||||
return partitionedMouseActors.InBox(r)
|
||||
.Where(actorIsInWorld)
|
||||
.Select(selectActorAndBounds)
|
||||
.Where(x => r.IntersectsWith(x.Bounds));
|
||||
.Where(x => x.Bounds.IntersectsWith(r));
|
||||
}
|
||||
|
||||
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
|
||||
@@ -224,12 +216,12 @@ namespace OpenRA.Traits
|
||||
foreach (var a in addOrUpdateActors)
|
||||
{
|
||||
var mouseBounds = a.MouseBounds(worldRenderer);
|
||||
if (!mouseBounds.Size.IsEmpty)
|
||||
if (!mouseBounds.IsEmpty)
|
||||
{
|
||||
if (partitionedMouseActors.Contains(a))
|
||||
partitionedMouseActors.Update(a, mouseBounds);
|
||||
partitionedMouseActors.Update(a, mouseBounds.BoundingRect);
|
||||
else
|
||||
partitionedMouseActors.Add(a, mouseBounds);
|
||||
partitionedMouseActors.Add(a, mouseBounds.BoundingRect);
|
||||
|
||||
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
|
||||
}
|
||||
@@ -263,12 +255,12 @@ namespace OpenRA.Traits
|
||||
foreach (var fa in kv.Value)
|
||||
{
|
||||
var mouseBounds = fa.MouseBounds;
|
||||
if (!mouseBounds.Size.IsEmpty)
|
||||
if (!mouseBounds.IsEmpty)
|
||||
{
|
||||
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
|
||||
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds);
|
||||
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds.BoundingRect);
|
||||
else
|
||||
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds);
|
||||
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds.BoundingRect);
|
||||
}
|
||||
else
|
||||
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
||||
@@ -308,11 +300,10 @@ namespace OpenRA.Traits
|
||||
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> MouseBounds(Player viewer)
|
||||
public IEnumerable<Polygon> MouseBounds(Player viewer)
|
||||
{
|
||||
var bounds = partitionedMouseActors.ItemBounds;
|
||||
|
||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].ItemBounds) : bounds;
|
||||
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
|
||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Items.Select(fa => fa.MouseBounds)) : bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,13 @@ namespace OpenRA
|
||||
if (o == null)
|
||||
continue;
|
||||
|
||||
if (PlayVoiceForOrder(o))
|
||||
if (o.GroupedActors != null)
|
||||
{
|
||||
foreach (var subject in o.GroupedActors)
|
||||
if (PlayVoiceForOrder(Order.FromGroupedOrder(o, subject)))
|
||||
return;
|
||||
}
|
||||
else if (PlayVoiceForOrder(o))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,11 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (minefield != null)
|
||||
{
|
||||
var positionable = (IPositionable)movement;
|
||||
var mobile = positionable as Mobile;
|
||||
minefield.RemoveAll(c => self.World.ActorMap.GetActorsAt(c)
|
||||
.Any(a => a.Info.Name == minelayer.Info.Mine.ToLowerInvariant()) ||
|
||||
(!positionable.CanEnterCell(c, null, BlockedByActor.Immovable) && !self.World.FogObscures(c)));
|
||||
.Any(a => a.Info.Name == minelayer.Info.Mine.ToLowerInvariant() && a.CanBeViewedByPlayer(self.Owner)) ||
|
||||
((!positionable.CanEnterCell(c, null, BlockedByActor.Immovable) || (mobile != null && !mobile.CanStayInCell(c)))
|
||||
&& self.Owner.Shroud.IsVisible(c)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,11 @@ namespace OpenRA.Mods.Cnc
|
||||
Sprite[] border;
|
||||
float2 nodPos, gdiPos, evaPos;
|
||||
Rectangle bounds;
|
||||
|
||||
SpriteFont loadingFont, versionFont;
|
||||
string loadingText, versionText;
|
||||
float2 loadingPos, versionPos;
|
||||
string versionText;
|
||||
|
||||
Sheet lastSheet;
|
||||
int lastDensity;
|
||||
Size lastResolution;
|
||||
IReadOnlyDictionary<string, SpriteFont> lastFonts;
|
||||
|
||||
public override void Init(ModData modData, Dictionary<string, string> info)
|
||||
{
|
||||
@@ -82,20 +78,6 @@ namespace OpenRA.Mods.Cnc
|
||||
|
||||
var barY = bounds.Height - 78;
|
||||
|
||||
// The fonts dictionary may change when switching between the mod and content installer
|
||||
if (r.Fonts != lastFonts)
|
||||
{
|
||||
lastFonts = r.Fonts;
|
||||
|
||||
loadingFont = lastFonts["BigBold"];
|
||||
loadingText = Info["Text"];
|
||||
loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
|
||||
|
||||
versionFont = lastFonts["Regular"];
|
||||
var versionSize = versionFont.Measure(versionText);
|
||||
versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2);
|
||||
}
|
||||
|
||||
loadTick = ++loadTick % 8;
|
||||
|
||||
r.RgbaSpriteRenderer.DrawSprite(gdiLogo, gdiPos);
|
||||
@@ -104,11 +86,18 @@ namespace OpenRA.Mods.Cnc
|
||||
|
||||
WidgetUtils.DrawPanel(bounds, border);
|
||||
|
||||
if (loadingFont != null)
|
||||
if (r.Fonts != null)
|
||||
{
|
||||
var loadingFont = r.Fonts["BigBold"];
|
||||
var loadingText = Info["Text"];
|
||||
var loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
|
||||
loadingFont.DrawText(loadingText, loadingPos, Color.Gray);
|
||||
|
||||
if (versionFont != null)
|
||||
var versionFont = r.Fonts["Regular"];
|
||||
var versionSize = versionFont.Measure(versionText);
|
||||
var versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2);
|
||||
versionFont.DrawTextWithContrast(versionText, versionPos, Color.White, Color.Black, 2);
|
||||
}
|
||||
|
||||
for (var i = 0; i <= 8; i++)
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
|
||||
Transforms[c + ids[k]] = s.ReadFloat();
|
||||
|
||||
Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16);
|
||||
if (Util.MatrixInverse(testMatrix) == null)
|
||||
if (OpenRA.Graphics.Util.MatrixInverse(testMatrix) == null)
|
||||
throw new InvalidDataException(
|
||||
"The transformation matrix for HVA file `{0}` section {1} frame {2} is invalid because it is not invertible!"
|
||||
.F(fileName, i, j));
|
||||
|
||||
50
OpenRA.Mods.Cnc/Graphics/ClassicSpriteSequence.cs
Normal file
50
OpenRA.Mods.Cnc/Graphics/ClassicSpriteSequence.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
#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 OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
public class ClassicSpriteSequenceLoader : DefaultSpriteSequenceLoader
|
||||
{
|
||||
public ClassicSpriteSequenceLoader(ModData modData)
|
||||
: base(modData) { }
|
||||
|
||||
public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
|
||||
{
|
||||
return new ClassicSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClassicSpriteSequence : DefaultSpriteSequence
|
||||
{
|
||||
readonly bool useClassicFacings;
|
||||
|
||||
public ClassicSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
|
||||
: base(modData, tileSet, cache, loader, sequence, animation, info)
|
||||
{
|
||||
var d = info.ToDictionary();
|
||||
useClassicFacings = LoadField(d, "UseClassicFacings", false);
|
||||
|
||||
if (useClassicFacings && Facings != 32)
|
||||
throw new InvalidOperationException(
|
||||
"{0}: Sequence {1}.{2}: UseClassicFacings is only valid for 32 facings"
|
||||
.F(info.Nodes[0].Location, sequence, animation));
|
||||
}
|
||||
|
||||
protected override int GetFacingFrameOffset(WAngle facing)
|
||||
{
|
||||
return Util.ClassicQuantizeFacing(facing.Facing, Facings, useClassicFacings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Graphics
|
||||
{
|
||||
public class ClassicTilesetSpecificSpriteSequenceLoader : ClassicSpriteSequenceLoader
|
||||
{
|
||||
public readonly string DefaultSpriteExtension = ".shp";
|
||||
public readonly Dictionary<string, string> TilesetExtensions = new Dictionary<string, string>();
|
||||
public readonly Dictionary<string, string> TilesetCodes = new Dictionary<string, string>();
|
||||
|
||||
public ClassicTilesetSpecificSpriteSequenceLoader(ModData modData)
|
||||
: base(modData)
|
||||
{
|
||||
var metadata = modData.Manifest.Get<SpriteSequenceFormat>().Metadata;
|
||||
MiniYaml yaml;
|
||||
if (metadata.TryGetValue("DefaultSpriteExtension", out yaml))
|
||||
DefaultSpriteExtension = yaml.Value;
|
||||
|
||||
if (metadata.TryGetValue("TilesetExtensions", out yaml))
|
||||
TilesetExtensions = yaml.ToDictionary(kv => kv.Value);
|
||||
|
||||
if (metadata.TryGetValue("TilesetCodes", out yaml))
|
||||
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
|
||||
}
|
||||
|
||||
public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
|
||||
{
|
||||
return new ClassicTilesetSpecificSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
|
||||
}
|
||||
}
|
||||
|
||||
public class ClassicTilesetSpecificSpriteSequence : ClassicSpriteSequence
|
||||
{
|
||||
public ClassicTilesetSpecificSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
|
||||
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
|
||||
|
||||
string ResolveTilesetId(TileSet tileSet, Dictionary<string, MiniYaml> d)
|
||||
{
|
||||
var tsId = tileSet.Id;
|
||||
|
||||
MiniYaml yaml;
|
||||
if (d.TryGetValue("TilesetOverrides", out yaml))
|
||||
{
|
||||
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tsId);
|
||||
if (tsNode != null)
|
||||
tsId = tsNode.Value.Value;
|
||||
}
|
||||
|
||||
return tsId;
|
||||
}
|
||||
|
||||
protected override string GetSpriteSrc(ModData modData, TileSet tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
|
||||
{
|
||||
var loader = (ClassicTilesetSpecificSpriteSequenceLoader)Loader;
|
||||
|
||||
var spriteName = sprite ?? sequence;
|
||||
|
||||
if (LoadField(d, "UseTilesetCode", false))
|
||||
{
|
||||
string code;
|
||||
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out code))
|
||||
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
|
||||
}
|
||||
|
||||
if (LoadField(d, "AddExtension", true))
|
||||
{
|
||||
var useTilesetExtension = LoadField(d, "UseTilesetExtension", false);
|
||||
|
||||
string tilesetExtension;
|
||||
if (useTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out tilesetExtension))
|
||||
return spriteName + tilesetExtension;
|
||||
|
||||
return spriteName + loader.DefaultSpriteExtension;
|
||||
}
|
||||
|
||||
return spriteName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,8 +74,8 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
t[14] *= l.Scale * (l.Bounds[5] - l.Bounds[2]) / l.Size[2];
|
||||
|
||||
// Center, flip and scale
|
||||
t = Util.MatrixMultiply(t, Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2]));
|
||||
t = Util.MatrixMultiply(Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t);
|
||||
t = OpenRA.Graphics.Util.MatrixMultiply(t, OpenRA.Graphics.Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2]));
|
||||
t = OpenRA.Graphics.Util.MatrixMultiply(OpenRA.Graphics.Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t);
|
||||
|
||||
return t;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
};
|
||||
|
||||
// Calculate limb bounding box
|
||||
var bb = Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b);
|
||||
var bb = OpenRA.Graphics.Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b);
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
ret[i] = Math.Min(ret[i], bb[i]);
|
||||
|
||||
@@ -77,8 +77,8 @@ namespace OpenRA.Mods.Cnc.Graphics
|
||||
var size = new Size(su, sv);
|
||||
var s = sheetBuilder.Allocate(size);
|
||||
var t = sheetBuilder.Allocate(size);
|
||||
Util.FastCopyIntoChannel(s, colors);
|
||||
Util.FastCopyIntoChannel(t, normals);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(s, colors);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(t, normals);
|
||||
|
||||
// s and t are guaranteed to use the same sheet because
|
||||
// of the custom voxel sheet allocation implementation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>5</LangVersion>
|
||||
@@ -21,10 +21,6 @@
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Eluant">
|
||||
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
@@ -32,6 +28,7 @@
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
</ItemGroup>
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
|
||||
@@ -34,8 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
readonly AttackLeapInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int leapToken = ConditionManager.InvalidConditionToken;
|
||||
int leapToken = Actor.InvalidConditionToken;
|
||||
|
||||
public AttackLeap(Actor self, AttackLeapInfo info)
|
||||
: base(self, info)
|
||||
@@ -43,12 +42,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (target.Type != TargetType.Actor)
|
||||
@@ -62,14 +55,14 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
public void GrantLeapCondition(Actor self)
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.LeapCondition))
|
||||
leapToken = conditionManager.GrantCondition(self, info.LeapCondition);
|
||||
if (!string.IsNullOrEmpty(info.LeapCondition))
|
||||
leapToken = self.GrantCondition(info.LeapCondition);
|
||||
}
|
||||
|
||||
public void RevokeLeapCondition(Actor self)
|
||||
{
|
||||
if (leapToken != ConditionManager.InvalidConditionToken)
|
||||
leapToken = conditionManager.RevokeCondition(self, leapToken);
|
||||
if (leapToken != Actor.InvalidConditionToken)
|
||||
leapToken = self.RevokeCondition(leapToken);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
|
||||
32
OpenRA.Mods.Cnc/Traits/ClassicFacingBodyOrientation.cs
Normal file
32
OpenRA.Mods.Cnc/Traits/ClassicFacingBodyOrientation.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#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.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
[Desc("Fudge the coordinate system angles like the early games (for sprite sequences that use classic facing fudge).")]
|
||||
public class ClassicFacingBodyOrientationInfo : BodyOrientationInfo
|
||||
{
|
||||
public override int QuantizeFacing(int facing, int facings)
|
||||
{
|
||||
return OpenRA.Mods.Cnc.Util.ClassicQuantizeFacing(facing, facings, true) * (256 / facings);
|
||||
}
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ClassicFacingBodyOrientation(init, this); }
|
||||
}
|
||||
|
||||
public class ClassicFacingBodyOrientation : BodyOrientation
|
||||
{
|
||||
public ClassicFacingBodyOrientation(ActorInitializer init, ClassicFacingBodyOrientationInfo info)
|
||||
: base(init, info) { }
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public object Create(ActorInitializer init) { return new ConyardChronoReturn(init, this); }
|
||||
}
|
||||
|
||||
public class ConyardChronoReturn : INotifyCreated, ITick, ISync, IObservesVariables, ISelectionBar, INotifySold,
|
||||
public class ConyardChronoReturn : ITick, ISync, IObservesVariables, ISelectionBar, INotifySold,
|
||||
IDeathActorInitModifier, ITransformActorInitModifier
|
||||
{
|
||||
readonly ConyardChronoReturnInfo info;
|
||||
@@ -70,8 +70,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
readonly Actor self;
|
||||
readonly string faction;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int conditionToken = ConditionManager.InvalidConditionToken;
|
||||
int conditionToken = Actor.InvalidConditionToken;
|
||||
|
||||
Actor chronosphere;
|
||||
int duration;
|
||||
@@ -110,11 +109,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
chronosphere = init.Get<ChronoshiftChronosphereInit, Actor>();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
IEnumerable<VariableObserver> IObservesVariables.GetVariableObservers()
|
||||
{
|
||||
if (info.ReturnOriginalActorOnCondition != null)
|
||||
@@ -128,8 +122,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
void TriggerVortex()
|
||||
{
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition) && conditionToken == ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.GrantCondition(self, info.Condition);
|
||||
if (!string.IsNullOrEmpty(info.Condition) && conditionToken == Actor.InvalidConditionToken)
|
||||
conditionToken = self.GrantCondition(info.Condition);
|
||||
|
||||
triggered = true;
|
||||
|
||||
@@ -140,8 +134,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
wsb.PlayCustomAnimation(self, info.Sequence, () =>
|
||||
{
|
||||
triggered = false;
|
||||
if (conditionToken != ConditionManager.InvalidConditionToken)
|
||||
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
|
||||
if (conditionToken != Actor.InvalidConditionToken)
|
||||
conditionToken = self.RevokeCondition(conditionToken);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public object Create(ActorInitializer init) { return new Disguise(init.Self, this); }
|
||||
}
|
||||
|
||||
class Disguise : INotifyCreated, IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
|
||||
class Disguise : IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
|
||||
INotifyDamage, INotifyUnload, INotifyDemolition, INotifyInfiltration, ITick
|
||||
{
|
||||
public ActorInfo AsActor { get; private set; }
|
||||
@@ -112,9 +112,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
readonly Actor self;
|
||||
readonly DisguiseInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
int disguisedToken = ConditionManager.InvalidConditionToken;
|
||||
int disguisedAsToken = ConditionManager.InvalidConditionToken;
|
||||
int disguisedToken = Actor.InvalidConditionToken;
|
||||
int disguisedAsToken = Actor.InvalidConditionToken;
|
||||
CPos? lastPos;
|
||||
|
||||
public Disguise(Actor self, DisguiseInfo info)
|
||||
@@ -125,11 +124,6 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
AsActor = self.Info;
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
IEnumerable<IOrderTargeter> IIssueOrder.Orders
|
||||
{
|
||||
get
|
||||
@@ -225,25 +219,22 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
foreach (var t in self.TraitsImplementing<INotifyEffectiveOwnerChanged>())
|
||||
t.OnEffectiveOwnerChanged(self, oldEffectiveOwner, AsPlayer);
|
||||
|
||||
if (conditionManager != null)
|
||||
if (Disguised != oldDisguiseSetting)
|
||||
{
|
||||
if (Disguised != oldDisguiseSetting)
|
||||
{
|
||||
if (Disguised && disguisedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(info.DisguisedCondition))
|
||||
disguisedToken = conditionManager.GrantCondition(self, info.DisguisedCondition);
|
||||
else if (!Disguised && disguisedToken != ConditionManager.InvalidConditionToken)
|
||||
disguisedToken = conditionManager.RevokeCondition(self, disguisedToken);
|
||||
}
|
||||
if (Disguised && disguisedToken == Actor.InvalidConditionToken && !string.IsNullOrEmpty(info.DisguisedCondition))
|
||||
disguisedToken = self.GrantCondition(info.DisguisedCondition);
|
||||
else if (!Disguised && disguisedToken != Actor.InvalidConditionToken)
|
||||
disguisedToken = self.RevokeCondition(disguisedToken);
|
||||
}
|
||||
|
||||
if (AsActor != oldEffectiveActor)
|
||||
{
|
||||
if (disguisedAsToken != ConditionManager.InvalidConditionToken)
|
||||
disguisedAsToken = conditionManager.RevokeCondition(self, disguisedAsToken);
|
||||
if (AsActor != oldEffectiveActor)
|
||||
{
|
||||
if (disguisedAsToken != Actor.InvalidConditionToken)
|
||||
disguisedAsToken = self.RevokeCondition(disguisedAsToken);
|
||||
|
||||
string disguisedAsCondition;
|
||||
if (info.DisguisedAsConditions.TryGetValue(AsActor.Name, out disguisedAsCondition))
|
||||
disguisedAsToken = conditionManager.GrantCondition(self, disguisedAsCondition);
|
||||
}
|
||||
string disguisedAsCondition;
|
||||
if (info.DisguisedAsConditions.TryGetValue(AsActor.Name, out disguisedAsCondition))
|
||||
disguisedAsToken = self.GrantCondition(disguisedAsCondition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Funds are transferred from the owner to the infiltrator.")]
|
||||
class InfiltrateForCashInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[Desc("Percentage of the victim's resources that will be stolen.")]
|
||||
@@ -34,7 +35,11 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when they get robbed.")]
|
||||
public readonly string Notification = null;
|
||||
public readonly string InfiltratedNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the perpetrator will hear after successful infiltration.")]
|
||||
public readonly string InfiltrationNotification = null;
|
||||
|
||||
[Desc("Whether to show the cash tick indicators rising from the actor.")]
|
||||
public readonly bool ShowTicks = true;
|
||||
@@ -63,8 +68,11 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
targetResources.TakeCash(toTake);
|
||||
spyResources.GiveCash(toGive);
|
||||
|
||||
if (info.Notification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.Notification, self.Owner.Faction.InternalName);
|
||||
if (info.InfiltratedNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (info.InfiltrationNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
|
||||
|
||||
if (info.ShowTicks)
|
||||
self.World.AddFrameEndTask(w => w.Add(new FloatingText(self.CenterPosition, infiltrator.Owner.Color, FloatingText.FormatCashTick(toGive), 30)));
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Reveals a decoration sprite to the indicated players when infiltrated.")]
|
||||
class InfiltrateForDecorationInfo : WithDecorationInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
public override object Create(ActorInitializer init) { return new InfiltrateForDecoration(init.Self, this); }
|
||||
|
||||
@@ -19,8 +19,17 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[Desc("Steal and reset the owner's exploration.")]
|
||||
class InfiltrateForExplorationInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when they get sabotaged.")]
|
||||
public readonly string InfiltratedNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the perpetrator will hear after successful infiltration.")]
|
||||
public readonly string InfiltrationNotification = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForExploration(init.Self, this); }
|
||||
}
|
||||
|
||||
@@ -38,6 +47,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (!info.Types.Overlaps(types))
|
||||
return;
|
||||
|
||||
if (info.InfiltratedNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (info.InfiltrationNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
|
||||
|
||||
infiltrator.Owner.Shroud.Explore(self.Owner.Shroud);
|
||||
var preventReset = self.Owner.PlayerActor.TraitsImplementing<IPreventsShroudReset>()
|
||||
.Any(p => p.PreventShroudReset(self));
|
||||
|
||||
@@ -17,10 +17,20 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class InfiltrateForPowerOutageInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[Desc("Measured in ticks.")]
|
||||
public readonly int Duration = 25 * 20;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when they get sabotaged.")]
|
||||
public readonly string InfiltratedNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the perpetrator will hear after successful infiltration.")]
|
||||
public readonly string InfiltrationNotification = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForPowerOutage(init.Self, this); }
|
||||
}
|
||||
|
||||
@@ -40,6 +50,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (!info.Types.Overlaps(types))
|
||||
return;
|
||||
|
||||
if (info.InfiltratedNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (info.InfiltrationNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
|
||||
|
||||
playerPower.TriggerPowerOutage(info.Duration);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,17 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly string Proxy = null;
|
||||
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when technology gets stolen.")]
|
||||
public readonly string InfiltratedNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the perpetrator will hear after successful infiltration.")]
|
||||
public readonly string InfiltrationNotification = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForSupportPower(this); }
|
||||
}
|
||||
|
||||
@@ -40,6 +49,12 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (!info.Types.Overlaps(types))
|
||||
return;
|
||||
|
||||
if (info.InfiltratedNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (info.InfiltrationNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
|
||||
|
||||
infiltrator.World.AddFrameEndTask(w => w.CreateActor(info.Proxy, new TypeDictionary
|
||||
{
|
||||
new OwnerInit(infiltrator.Owner)
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#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.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
class InfiltrateForSupportPowerResetInfo : ITraitInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the victim will hear when technology gets stolen.")]
|
||||
public readonly string InfiltratedNotification = null;
|
||||
|
||||
[NotificationReference("Speech")]
|
||||
[Desc("Sound the perpetrator will hear after successful infiltration.")]
|
||||
public readonly string InfiltrationNotification = null;
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForSupportPowerReset(this); }
|
||||
}
|
||||
|
||||
class InfiltrateForSupportPowerReset : INotifyInfiltrated
|
||||
{
|
||||
readonly InfiltrateForSupportPowerResetInfo info;
|
||||
|
||||
public InfiltrateForSupportPowerReset(InfiltrateForSupportPowerResetInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet<TargetableType> types)
|
||||
{
|
||||
if (!info.Types.Overlaps(types))
|
||||
return;
|
||||
|
||||
if (info.InfiltratedNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (info.InfiltrationNotification != null)
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
|
||||
|
||||
var manager = self.Owner.PlayerActor.Trait<SupportPowerManager>();
|
||||
var powers = manager.GetPowersForActor(self).Where(sp => !sp.Disabled);
|
||||
foreach (var power in powers)
|
||||
power.ResetTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
public readonly bool SkipMakeAnims = true;
|
||||
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
public object Create(ActorInitializer init) { return new InfiltrateForTransform(init, this); }
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
public class InfiltratesInfo : ConditionalTraitInfo
|
||||
{
|
||||
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
|
||||
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
|
||||
|
||||
[VoiceReference]
|
||||
|
||||
@@ -83,22 +83,15 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
}
|
||||
}
|
||||
|
||||
class MadTank : INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IIssueDeployOrder
|
||||
class MadTank : IIssueOrder, IResolveOrder, IOrderVoice, IIssueDeployOrder
|
||||
{
|
||||
readonly MadTankInfo info;
|
||||
|
||||
ConditionManager conditionManager;
|
||||
|
||||
public MadTank(Actor self, MadTankInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
@@ -195,8 +188,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return true;
|
||||
|
||||
if (mad.conditionManager != null && !string.IsNullOrEmpty(mad.info.DeployedCondition))
|
||||
mad.conditionManager.GrantCondition(self, mad.info.DeployedCondition);
|
||||
if (!string.IsNullOrEmpty(mad.info.DeployedCondition))
|
||||
self.GrantCondition(mad.info.DeployedCondition);
|
||||
|
||||
self.World.AddFrameEndTask(w => EjectDriver());
|
||||
if (mad.info.ThumpSequence != null)
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
var movement = self.Trait<IPositionable>();
|
||||
|
||||
var minefield = GetMinefieldCells(minefieldStart, cell, Info.MinefieldDepth)
|
||||
.Where(c => movement.CanEnterCell(c, null, BlockedByActor.Immovable) || self.World.FogObscures(c))
|
||||
.Where(c => movement.CanEnterCell(c, null, BlockedByActor.Immovable) || !self.Owner.Shroud.IsVisible(c))
|
||||
.OrderBy(c => (c - minefieldStart).LengthSquared).ToList();
|
||||
|
||||
self.QueueActivity(order.Queued, new LayMines(self, minefield));
|
||||
@@ -231,13 +231,14 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
minelayers.Max(m => m.Info.TraitInfo<MinelayerInfo>().MinefieldDepth));
|
||||
|
||||
var movement = minelayer.Trait<IPositionable>();
|
||||
var mobile = movement as Mobile;
|
||||
var pal = wr.Palette(TileSet.TerrainPaletteInternalName);
|
||||
foreach (var c in minefield)
|
||||
{
|
||||
var tile = tileOk;
|
||||
if (world.FogObscures(c))
|
||||
tile = tileUnknown;
|
||||
else if (!movement.CanEnterCell(c, null, BlockedByActor.Immovable))
|
||||
else if (!movement.CanEnterCell(c, null, BlockedByActor.Immovable) || (mobile != null && !mobile.CanStayInCell(c)))
|
||||
tile = tileBlocked;
|
||||
|
||||
yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c),
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
.FirstOrDefault(t => t.EnabledByDefault);
|
||||
if (renderSprites != null && infantryBody != null)
|
||||
{
|
||||
disguiseImage = renderSprites.GetImage(disguiseActor, self.World.Map.Rules.Sequences, disguisePlayer.InternalName);
|
||||
disguiseImage = renderSprites.GetImage(disguiseActor, self.World.Map.Rules.Sequences, disguisePlayer.Faction.InternalName);
|
||||
disguiseInfantryBody = infantryBody;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
var wsb = init.Actor.TraitInfos<WithSpriteBodyInfo>().FirstOrDefault();
|
||||
|
||||
// Show the correct turret facing
|
||||
var facing = init.Contains<TurretFacingInit>() ? init.Get<TurretFacingInit>().Value(init.World) : t.InitialFacing;
|
||||
var facing = WAngle.FromFacing(init.Contains<TurretFacingInit>() ? init.Get<TurretFacingInit>().Value(init.World) : t.InitialFacing);
|
||||
|
||||
var anim = new Animation(init.World, image, () => facing);
|
||||
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), wsb.Sequence));
|
||||
@@ -47,11 +47,11 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
{
|
||||
readonly Turreted turreted;
|
||||
|
||||
static Func<int> MakeTurretFacingFunc(Actor self)
|
||||
static Func<WAngle> MakeTurretFacingFunc(Actor self)
|
||||
{
|
||||
// Turret artwork is baked into the sprite, so only the first turret makes sense.
|
||||
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
|
||||
return () => turreted.TurretFacing;
|
||||
return () => WAngle.FromFacing(turreted.TurretFacing);
|
||||
}
|
||||
|
||||
public WithEmbeddedTurretSpriteBody(ActorInitializer init, WithSpriteBodyInfo info)
|
||||
|
||||
@@ -46,11 +46,11 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
readonly IFacing facing;
|
||||
readonly Turreted turret;
|
||||
|
||||
static Func<int> MakeTurretFacingFunc(Actor self)
|
||||
static Func<WAngle> MakeTurretFacingFunc(Actor self)
|
||||
{
|
||||
// Turret artwork is baked into the sprite, so only the first turret makes sense.
|
||||
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
|
||||
return () => turreted.TurretFacing;
|
||||
return () => WAngle.FromFacing(turreted.TurretFacing);
|
||||
}
|
||||
|
||||
public WithGunboatBody(ActorInitializer init, WithGunboatBodyInfo info)
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
|
||||
public WithRoof(Actor self, WithRoofInfo info)
|
||||
{
|
||||
var rs = self.Trait<RenderSprites>();
|
||||
var roof = new Animation(self.World, rs.GetImage(self), () => self.Trait<IFacing>().Facing);
|
||||
var roof = new Animation(self.World, rs.GetImage(self), RenderSprites.MakeFacingFunc(self));
|
||||
roof.Play(info.Sequence);
|
||||
rs.Add(new AnimationWithOffset(roof, null, null, 1024));
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
if (IsTraitDisabled)
|
||||
return;
|
||||
|
||||
var cash = Util.ApplyPercentageModifiers(amount, modifier);
|
||||
var cash = OpenRA.Mods.Common.Util.ApplyPercentageModifiers(amount, modifier);
|
||||
playerResources.GiveCash(cash);
|
||||
|
||||
if (Info.ShowTicks && self.Info.HasTraitInfo<IOccupySpaceInfo>())
|
||||
|
||||
@@ -179,8 +179,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
if (unit.CanBeViewedByPlayer(manager.Self.Owner))
|
||||
{
|
||||
var bounds = unit.TraitsImplementing<IDecorationBounds>().FirstNonEmptyBounds(unit, wr);
|
||||
yield return new SelectionBoxAnnotationRenderable(unit, bounds, Color.Red);
|
||||
var decorations = unit.TraitsImplementing<ISelectionDecorations>().FirstEnabledTraitOrDefault();
|
||||
foreach (var d in decorations.RenderSelectionAnnotations(unit, wr, Color.Red))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,8 +303,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
if (unit.CanBeViewedByPlayer(manager.Self.Owner))
|
||||
{
|
||||
var bounds = unit.TraitsImplementing<IDecorationBounds>().FirstNonEmptyBounds(unit, wr);
|
||||
yield return new SelectionBoxAnnotationRenderable(unit, bounds, Color.Red);
|
||||
var decorations = unit.TraitsImplementing<ISelectionDecorations>().FirstEnabledTraitOrDefault();
|
||||
foreach (var d in decorations.RenderSelectionAnnotations(unit, wr, Color.Red))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
int MovementSpeed
|
||||
{
|
||||
get { return Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); }
|
||||
get { return OpenRA.Mods.Common.Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); }
|
||||
}
|
||||
|
||||
public Pair<CPos, SubCell>[] OccupiedCells() { return new[] { Pair.New(TopLeft, SubCell.FullCell) }; }
|
||||
|
||||
42
OpenRA.Mods.Cnc/Util.cs
Normal file
42
OpenRA.Mods.Cnc/Util.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.Mods.Cnc
|
||||
{
|
||||
public static class Util
|
||||
{
|
||||
// TD and RA used a nonlinear mapping between artwork frames and unit facings for units with 32 facings.
|
||||
// This table defines the exclusive maximum facing for the i'th sprite frame.
|
||||
// i.e. sprite frame 1 is used for facings 5-13, sprite frame 2 for 14-21, and so on.
|
||||
// Sprite frame 0 is used for facings smaller than 5 or larger than 249.
|
||||
static readonly int[] SpriteFacings =
|
||||
{
|
||||
5, 14, 22, 33, 39, 46, 53, 60,
|
||||
67, 74, 81, 88, 96, 104, 113, 122,
|
||||
133, 142, 151, 161, 167, 174, 181, 188,
|
||||
195, 202, 209, 216, 224, 232, 241, 250
|
||||
};
|
||||
|
||||
public static int ClassicQuantizeFacing(int facing, int numFrames, bool useClassicFacingFudge)
|
||||
{
|
||||
if (useClassicFacingFudge && numFrames == 32)
|
||||
{
|
||||
for (var i = 0; i < SpriteFacings.Length; i++)
|
||||
if (facing < SpriteFacings[i])
|
||||
return i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Common.Util.QuantizeFacing(facing, numFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,10 +85,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
static string[] overlayActors = new string[]
|
||||
{
|
||||
// Fences
|
||||
"sbag", "cycl", "brik", "fenc", "wood", "wood",
|
||||
"sbag", "cycl", "brik", "fenc", "wood",
|
||||
|
||||
// Fields
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18"
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
|
||||
|
||||
// Crates
|
||||
"wcrate", "scrate"
|
||||
};
|
||||
|
||||
void UnpackOverlayData(MemoryStream ms)
|
||||
|
||||
@@ -73,10 +73,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
static string[] overlayActors = new string[]
|
||||
{
|
||||
// Fences
|
||||
"sbag", "cycl", "brik", "fenc", "wood", "wood",
|
||||
"sbag", "cycl", "brik", "fenc", "wood",
|
||||
|
||||
// Fields
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18"
|
||||
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
|
||||
|
||||
// Crates
|
||||
"wcrate", "scrate"
|
||||
};
|
||||
|
||||
void ReadOverlay(IniFile file)
|
||||
@@ -137,7 +140,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
faction = "gdi";
|
||||
break;
|
||||
case "BadGuy":
|
||||
color = "red"; // TODO: use the grey unit color theme for missions
|
||||
color = "red";
|
||||
faction = "nod";
|
||||
break;
|
||||
case "Special":
|
||||
|
||||
@@ -65,8 +65,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
var name = args.Length > 3 ? args[3] : Path.GetFileNameWithoutExtension(args[2]);
|
||||
metadata.AppendLine("\tName: {0}".F(name));
|
||||
metadata.AppendLine("\tId: {0}".F(name.ToUpperInvariant()));
|
||||
metadata.AppendLine("\tPalette: iso{0}.pal".F(extension));
|
||||
metadata.AppendLine("\tHeightDebugColors: 00000080, 00004480, 00008880, 0000CC80, 0000FF80, 4400CC80," +
|
||||
metadata.AppendLine("\tHeightDebugColors: 00000080, 00004480, 00008880, 0000CC80, 0000FF80, 4400CC80," +
|
||||
" 88008880, CC004480, FF110080, FF550080, FF990080, FFDD0080, DDFF0080, 99FF0080, 55FF0080, 11FF0080");
|
||||
|
||||
// Loop over template sets
|
||||
@@ -96,7 +95,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
|
||||
using (var s = modData.DefaultFileSystem.Open(templateFilename))
|
||||
{
|
||||
data.AppendLine("\tTemplate@{0}:".F(templateIndex));
|
||||
data.AppendLine("\t\tCategory: {0}".F(sectionCategory));
|
||||
data.AppendLine("\t\tCategories: {0}".F(sectionCategory));
|
||||
usedCategories.Add(sectionCategory);
|
||||
|
||||
data.AppendLine("\t\tId: {0}".F(templateIndex));
|
||||
|
||||
@@ -22,15 +22,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly Func<Activity> getInner;
|
||||
readonly bool isAssaultMove;
|
||||
AutoTarget autoTarget;
|
||||
ConditionManager conditionManager;
|
||||
AttackMove attackMove;
|
||||
int token = ConditionManager.InvalidConditionToken;
|
||||
int token = Actor.InvalidConditionToken;
|
||||
|
||||
public AttackMoveActivity(Actor self, Func<Activity> getInner, bool assaultMoving = false)
|
||||
{
|
||||
this.getInner = getInner;
|
||||
autoTarget = self.TraitOrDefault<AutoTarget>();
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
attackMove = self.TraitOrDefault<AttackMove>();
|
||||
isAssaultMove = assaultMoving;
|
||||
ChildHasPriority = false;
|
||||
@@ -41,13 +39,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
// Start moving.
|
||||
QueueChild(getInner());
|
||||
|
||||
if (conditionManager == null || attackMove == null)
|
||||
if (attackMove == null)
|
||||
return;
|
||||
|
||||
if (!isAssaultMove && !string.IsNullOrEmpty(attackMove.Info.AttackMoveCondition))
|
||||
token = conditionManager.GrantCondition(self, attackMove.Info.AttackMoveCondition);
|
||||
token = self.GrantCondition(attackMove.Info.AttackMoveCondition);
|
||||
else if (isAssaultMove && !string.IsNullOrEmpty(attackMove.Info.AssaultMoveCondition))
|
||||
token = conditionManager.GrantCondition(self, attackMove.Info.AssaultMoveCondition);
|
||||
token = self.GrantCondition(attackMove.Info.AssaultMoveCondition);
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
@@ -77,8 +75,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
if (conditionManager != null && token != ConditionManager.InvalidConditionToken)
|
||||
token = conditionManager.RevokeCondition(self, token);
|
||||
if (token != Actor.InvalidConditionToken)
|
||||
token = self.RevokeCondition(token);
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
|
||||
@@ -305,7 +305,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var newCell = path[path.Count - 1];
|
||||
path.RemoveAt(path.Count - 1);
|
||||
|
||||
return Pair.New(newCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor));
|
||||
return Pair.New(newCell, mobile.GetAvailableSubCell(nextCell, mobile.FromSubCell, ignoreActor));
|
||||
}
|
||||
else if (mobile.IsBlocking)
|
||||
{
|
||||
@@ -316,7 +316,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if ((nextCell - newCell).Value.LengthSquared > 2)
|
||||
path.Add(mobile.ToCell);
|
||||
|
||||
return Pair.New(newCell.Value, mobile.GetAvailableSubCell(newCell.Value, SubCell.Any, ignoreActor));
|
||||
return Pair.New(newCell.Value, mobile.GetAvailableSubCell(newCell.Value, mobile.FromSubCell, ignoreActor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
hasWaited = false;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
|
||||
return Pair.New(nextCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor));
|
||||
return Pair.New(nextCell, mobile.GetAvailableSubCell(nextCell, mobile.FromSubCell, ignoreActor));
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
@@ -347,10 +347,15 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
Cancel(self, keepQueue, false);
|
||||
}
|
||||
|
||||
public void Cancel(Actor self, bool keepQueue, bool forceClearPath)
|
||||
{
|
||||
// We need to clear the path here in order to prevent MovePart queueing new instances of itself
|
||||
// when the unit is making a turn.
|
||||
if (path != null && mobile.CanStayInCell(mobile.ToCell))
|
||||
if (path != null && (forceClearPath || mobile.CanStayInCell(mobile.ToCell)))
|
||||
path.Clear();
|
||||
|
||||
base.Cancel(self, keepQueue);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
@@ -77,7 +78,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
protected virtual IEnumerable<CPos> CandidateMovementCells(Actor self)
|
||||
{
|
||||
return Util.AdjacentCells(self.World, Target);
|
||||
return Util.AdjacentCells(self.World, Target)
|
||||
.Where(c => Mobile.CanStayInCell(c));
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
@@ -119,15 +121,23 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return TickChild(self);
|
||||
}
|
||||
|
||||
List<CPos> searchCells = new List<CPos>();
|
||||
int searchCellsTick = -1;
|
||||
|
||||
List<CPos> CalculatePathToTarget(Actor self, BlockedByActor check)
|
||||
{
|
||||
var targetCells = CandidateMovementCells(self);
|
||||
var searchCells = new List<CPos>();
|
||||
var loc = self.Location;
|
||||
|
||||
foreach (var cell in targetCells)
|
||||
if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell))
|
||||
searchCells.Add(cell);
|
||||
// PERF: Assume that CandidateMovementCells doesn't change within a tick to avoid repeated queries
|
||||
// when Move enumerates different BlockedByActor values
|
||||
if (searchCellsTick != self.World.WorldTick)
|
||||
{
|
||||
searchCells.Clear();
|
||||
searchCellsTick = self.World.WorldTick;
|
||||
foreach (var cell in CandidateMovementCells(self))
|
||||
if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell))
|
||||
searchCells.Add(cell);
|
||||
}
|
||||
|
||||
if (!searchCells.Any())
|
||||
return NoPath;
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
// We are now in range. Don't move any further!
|
||||
// HACK: This works around the pathfinder not returning the shortest path
|
||||
return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self);
|
||||
return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self) && Mobile.CanStayInCell(self.Location);
|
||||
}
|
||||
|
||||
protected override bool ShouldRepath(Actor self, CPos targetLocation)
|
||||
{
|
||||
return lastVisibleTargetLocation != targetLocation && (!AtCorrectRange(self.CenterPosition)
|
||||
|| !Mobile.CanInteractWithGroundLayer(self));
|
||||
|| !Mobile.CanInteractWithGroundLayer(self) || !Mobile.CanStayInCell(self.Location));
|
||||
}
|
||||
|
||||
protected override IEnumerable<CPos> CandidateMovementCells(Actor self)
|
||||
@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var minCells = minRange.Length / 1024;
|
||||
|
||||
return map.FindTilesInAnnulus(lastVisibleTargetLocation, minCells, maxCells)
|
||||
.Where(c => AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell)));
|
||||
.Where(c => Mobile.CanStayInCell(c) && AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell)));
|
||||
}
|
||||
|
||||
bool AtCorrectRange(WPos origin)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
Actor enterActor;
|
||||
IHealth enterHealth;
|
||||
EngineerRepairable enterEngineerRepariable;
|
||||
|
||||
public RepairBuilding(Actor self, Target target, EngineerRepairInfo info)
|
||||
: base(self, target, Color.Yellow)
|
||||
@@ -32,11 +33,12 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
enterActor = targetActor;
|
||||
enterHealth = targetActor.TraitOrDefault<IHealth>();
|
||||
enterEngineerRepariable = targetActor.TraitOrDefault<EngineerRepairable>();
|
||||
|
||||
// Make sure we can still repair the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
var stance = self.Owner.Stances[enterActor.Owner];
|
||||
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || !info.ValidStances.HasStance(stance))
|
||||
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || enterEngineerRepariable == null || enterEngineerRepariable.IsTraitDisabled || !info.ValidStances.HasStance(stance))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
@@ -52,6 +54,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
if (enterEngineerRepariable.IsTraitDisabled)
|
||||
return;
|
||||
|
||||
if (enterHealth.DamageState == DamageState.Undamaged)
|
||||
return;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly Aircraft aircraft;
|
||||
readonly bool stayOnResupplier;
|
||||
readonly bool wasRepaired;
|
||||
readonly PlayerResources playerResources;
|
||||
readonly int unitCost;
|
||||
|
||||
int remainingTicks;
|
||||
bool played;
|
||||
@@ -54,6 +56,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
transportCallers = self.TraitsImplementing<ICallForTransport>().ToArray();
|
||||
move = self.Trait<IMove>();
|
||||
aircraft = move as Aircraft;
|
||||
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
|
||||
|
||||
var valued = self.Info.TraitInfoOrDefault<ValuedInfo>();
|
||||
unitCost = valued != null ? valued.Cost : 0;
|
||||
|
||||
var cannotRepairAtHost = health == null || health.DamageState == DamageState.Undamaged
|
||||
|| !allRepairsUnits.Any()
|
||||
@@ -165,6 +171,12 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override void Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
// HACK: force move activities to ignore the transit-only cells when cancelling
|
||||
// The idle handler will take over and move them into a safe cell
|
||||
if (ChildActivity != null)
|
||||
foreach (var c in ChildActivity.ActivitiesImplementing<Move>())
|
||||
c.Cancel(self, false, true);
|
||||
|
||||
foreach (var t in transportCallers)
|
||||
t.MovementCancelled(self);
|
||||
|
||||
@@ -221,7 +233,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
void RepairTick(Actor self)
|
||||
{
|
||||
// First active.
|
||||
var repairsUnits = allRepairsUnits.FirstOrDefault(r => !r.IsTraitDisabled && !r.IsTraitPaused);
|
||||
if (repairsUnits == null)
|
||||
{
|
||||
@@ -248,8 +259,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
if (remainingTicks == 0)
|
||||
{
|
||||
var valued = self.Info.TraitInfoOrDefault<ValuedInfo>();
|
||||
var unitCost = valued != null ? valued.Cost : 0;
|
||||
var hpToRepair = repairable != null && repairable.Info.HpPerStep > 0 ? repairable.Info.HpPerStep : repairsUnits.Info.HpPerStep;
|
||||
|
||||
// Cast to long to avoid overflow when multiplying by the health
|
||||
@@ -261,13 +270,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", repairsUnits.Info.StartRepairingNotification, self.Owner.Faction.InternalName);
|
||||
}
|
||||
|
||||
if (!self.Owner.PlayerActor.Trait<PlayerResources>().TakeCash(cost, true))
|
||||
if (!playerResources.TakeCash(cost, true))
|
||||
{
|
||||
remainingTicks = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
self.InflictDamage(host.Actor, new Damage(-hpToRepair));
|
||||
self.InflictDamage(host.Actor, new Damage(-hpToRepair, repairsUnits.Info.RepairDamageTypes));
|
||||
remainingTicks = repairsUnits.Info.Interval;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -31,14 +31,18 @@ namespace OpenRA.Mods.Common.Effects
|
||||
// Facing is last on these overloads partially for backwards compatibility with previous main ctor revision
|
||||
// and partially because most effects don't need it. The latter is also the reason for placement of 'delay'.
|
||||
public SpriteEffect(WPos pos, World world, string image, string sequence, string palette,
|
||||
bool visibleThroughFog = false, int facing = 0, int delay = 0)
|
||||
: this(() => pos, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
|
||||
bool visibleThroughFog = false, int delay = 0)
|
||||
: this(() => pos, () => WAngle.Zero, world, image, sequence, palette, visibleThroughFog, delay) { }
|
||||
|
||||
public SpriteEffect(Actor actor, World world, string image, string sequence, string palette,
|
||||
bool visibleThroughFog = false, int facing = 0, int delay = 0)
|
||||
: this(() => actor.CenterPosition, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
|
||||
bool visibleThroughFog = false, int delay = 0)
|
||||
: this(() => actor.CenterPosition, () => WAngle.Zero, world, image, sequence, palette, visibleThroughFog, delay) { }
|
||||
|
||||
public SpriteEffect(Func<WPos> posFunc, Func<int> facingFunc, World world, string image, string sequence, string palette,
|
||||
public SpriteEffect(WPos pos, WAngle facing, World world, string image, string sequence, string palette,
|
||||
bool visibleThroughFog = false, int delay = 0)
|
||||
: this(() => pos, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
|
||||
|
||||
public SpriteEffect(Func<WPos> posFunc, Func<WAngle> facingFunc, World world, string image, string sequence, string palette,
|
||||
bool visibleThroughFog = false, int delay = 0)
|
||||
{
|
||||
this.world = world;
|
||||
@@ -62,16 +66,18 @@ namespace OpenRA.Mods.Common.Effects
|
||||
world.ScreenMap.Add(this, pos, anim.Image);
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
anim.Tick();
|
||||
|
||||
anim.Tick();
|
||||
|
||||
pos = posFunc();
|
||||
world.ScreenMap.Update(this, pos, anim.Image);
|
||||
pos = posFunc();
|
||||
world.ScreenMap.Update(this, pos, anim.Image);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||
{
|
||||
if (!visibleThroughFog && world.FogObscures(pos))
|
||||
if (!initialized || (!visibleThroughFog && world.FogObscures(pos)))
|
||||
return SpriteRenderable.None;
|
||||
|
||||
return anim.Render(pos, wr.Palette(palette));
|
||||
|
||||
@@ -67,20 +67,23 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
return () => orientation;
|
||||
}
|
||||
|
||||
public Func<int> GetFacing()
|
||||
public Func<WAngle> GetFacing()
|
||||
{
|
||||
var facingInfo = Actor.TraitInfoOrDefault<IFacingInfo>();
|
||||
if (facingInfo == null)
|
||||
return () => 0;
|
||||
return () => WAngle.Zero;
|
||||
|
||||
// Dynamic facing takes priority
|
||||
var dynamicInit = dict.GetOrDefault<DynamicFacingInit>();
|
||||
if (dynamicInit != null)
|
||||
return dynamicInit.Value(null);
|
||||
{
|
||||
var getFacing = dynamicInit.Value(null);
|
||||
return () => WAngle.FromFacing(getFacing());
|
||||
}
|
||||
|
||||
// Fall back to initial actor facing if an Init isn't available
|
||||
var facingInit = dict.GetOrDefault<FacingInit>();
|
||||
var facing = facingInit != null ? facingInit.Value(null) : facingInfo.GetInitialFacing();
|
||||
var facing = WAngle.FromFacing(facingInit != null ? facingInit.Value(null) : facingInfo.GetInitialFacing());
|
||||
return () => facing;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,11 +95,11 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
static readonly WDist DefaultShadowSpriteZOffset = new WDist(-5);
|
||||
protected Sprite[] sprites;
|
||||
readonly bool reverseFacings, transpose, useClassicFacingFudge;
|
||||
readonly bool reverseFacings, transpose;
|
||||
readonly string sequence;
|
||||
|
||||
protected readonly ISpriteSequenceLoader Loader;
|
||||
|
||||
readonly string sequence;
|
||||
public string Name { get; private set; }
|
||||
public int Start { get; private set; }
|
||||
public int Length { get; private set; }
|
||||
@@ -156,7 +156,6 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
Tick = LoadField(d, "Tick", 40);
|
||||
transpose = LoadField(d, "Transpose", false);
|
||||
Frames = LoadField<int[]>(d, "Frames", null);
|
||||
useClassicFacingFudge = LoadField(d, "UseClassicFacingFudge", false);
|
||||
|
||||
var flipX = LoadField(d, "FlipX", false);
|
||||
var flipY = LoadField(d, "FlipY", false);
|
||||
@@ -168,11 +167,6 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
Facings = -Facings;
|
||||
}
|
||||
|
||||
if (useClassicFacingFudge && Facings != 32)
|
||||
throw new InvalidOperationException(
|
||||
"{0}: Sequence {1}.{2}: UseClassicFacingFudge is only valid for 32 facings"
|
||||
.F(info.Nodes[0].Location, sequence, animation));
|
||||
|
||||
var offset = LoadField(d, "Offset", float3.Zero);
|
||||
var blendMode = LoadField(d, "BlendMode", BlendMode.Alpha);
|
||||
|
||||
@@ -369,22 +363,22 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
|
||||
public Sprite GetSprite(int frame)
|
||||
{
|
||||
return GetSprite(Start, frame, 0);
|
||||
return GetSprite(Start, frame, WAngle.Zero);
|
||||
}
|
||||
|
||||
public Sprite GetSprite(int frame, int facing)
|
||||
public Sprite GetSprite(int frame, WAngle facing)
|
||||
{
|
||||
return GetSprite(Start, frame, facing);
|
||||
}
|
||||
|
||||
public Sprite GetShadow(int frame, int facing)
|
||||
public Sprite GetShadow(int frame, WAngle facing)
|
||||
{
|
||||
return ShadowStart >= 0 ? GetSprite(ShadowStart, frame, facing) : null;
|
||||
}
|
||||
|
||||
protected virtual Sprite GetSprite(int start, int frame, int facing)
|
||||
protected virtual Sprite GetSprite(int start, int frame, WAngle facing)
|
||||
{
|
||||
var f = Util.QuantizeFacing(facing, Facings, useClassicFacingFudge);
|
||||
var f = GetFacingFrameOffset(facing);
|
||||
if (reverseFacings)
|
||||
f = (Facings - f) % Facings;
|
||||
|
||||
@@ -398,5 +392,10 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
|
||||
return sprites[j];
|
||||
}
|
||||
|
||||
protected virtual int GetFacingFrameOffset(WAngle facing)
|
||||
{
|
||||
return Util.QuantizeFacing(facing.Facing, Facings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,18 +66,17 @@ namespace OpenRA.Mods.Common.Graphics
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var wcr = Game.Renderer.WorldRgbaColorRenderer;
|
||||
var center = wr.Screen3DPosition(centerPosition);
|
||||
var cr = Game.Renderer.RgbaColorRenderer;
|
||||
var center = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(centerPosition));
|
||||
|
||||
for (var i = 0; i < trailCount; i++)
|
||||
{
|
||||
var angle = trailAngle - new WAngle(i * (trailSeparation.Angle <= 512 ? 1 : -1));
|
||||
var length = radius.Length * new WVec(angle.Cos(), angle.Sin(), 0) / 1024;
|
||||
var end = wr.Screen3DPosition(centerPosition + length);
|
||||
var end = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(centerPosition + length));
|
||||
var alpha = color.A - i * color.A / trailCount;
|
||||
|
||||
wcr.DrawLine(center, end, 3, Color.FromArgb(alpha, contrastColor));
|
||||
wcr.DrawLine(center, end, 1, Color.FromArgb(alpha, color));
|
||||
cr.DrawLine(center, end, 3, Color.FromArgb(alpha, contrastColor));
|
||||
cr.DrawLine(center, end, 1, Color.FromArgb(alpha, color));
|
||||
}
|
||||
|
||||
RangeCircleAnnotationRenderable.DrawRangeCircle(wr, centerPosition, radius, 1, color, 3, contrastColor);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#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 OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
public struct IsometricSelectionBarsAnnotationRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
const int BarWidth = 3;
|
||||
const int BarHeight = 4;
|
||||
const int BarStride = 5;
|
||||
|
||||
static readonly Color EmptyColor = Color.FromArgb(160, 30, 30, 30);
|
||||
static readonly Color DarkEmptyColor = Color.FromArgb(160, 15, 15, 15);
|
||||
static readonly Color DarkenColor = Color.FromArgb(24, 0, 0, 0);
|
||||
static readonly Color LightenColor = Color.FromArgb(24, 255, 255, 255);
|
||||
|
||||
readonly WPos pos;
|
||||
readonly Actor actor;
|
||||
readonly bool displayHealth;
|
||||
readonly bool displayExtra;
|
||||
readonly Polygon bounds;
|
||||
|
||||
public IsometricSelectionBarsAnnotationRenderable(Actor actor, Polygon bounds, bool displayHealth, bool displayExtra)
|
||||
: this(actor.CenterPosition, actor, bounds)
|
||||
{
|
||||
this.displayHealth = displayHealth;
|
||||
this.displayExtra = displayExtra;
|
||||
}
|
||||
|
||||
public IsometricSelectionBarsAnnotationRenderable(WPos pos, Actor actor, Polygon bounds)
|
||||
: this()
|
||||
{
|
||||
this.pos = pos;
|
||||
this.actor = actor;
|
||||
this.bounds = bounds;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
public bool DisplayHealth { get { return displayHealth; } }
|
||||
public bool DisplayExtra { get { return displayExtra; } }
|
||||
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return 0; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return this; }
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBarsAnnotationRenderable(pos + vec, actor, bounds); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
void DrawExtraBars(WorldRenderer wr)
|
||||
{
|
||||
var i = 1;
|
||||
foreach (var extraBar in actor.TraitsImplementing<ISelectionBar>())
|
||||
{
|
||||
var value = extraBar.GetValue();
|
||||
if (value != 0 || extraBar.DisplayWhenEmpty)
|
||||
DrawBar(wr, extraBar.GetValue(), extraBar.GetColor(), i++);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawBar(WorldRenderer wr, float value, Color barColor, int barNum, float? secondValue = null, Color? secondColor = null)
|
||||
{
|
||||
var darkColor = Color.FromArgb(barColor.A, barColor.R / 2, barColor.G / 2, barColor.B / 2);
|
||||
var barAspect = new float2(1f, 0.5f);
|
||||
var stepAspect = new float2(1f, -0.5f);
|
||||
|
||||
var offset = barNum * BarStride * barAspect - new float2(0, BarHeight + 1);
|
||||
var start = wr.Viewport.WorldToViewPx(bounds.Vertices[1]).ToFloat2() + offset;
|
||||
var end = wr.Viewport.WorldToViewPx(bounds.Vertices[0]).ToFloat2() + offset;
|
||||
|
||||
// HACK: Work around rounding errors that may cause a few-px offset in the end relative to the start
|
||||
// Force the bar to take a 45 degree angle
|
||||
end = new float2(end.X, start.Y - (end.X - start.X) / 2);
|
||||
|
||||
// Round the cut point to the nearest pixel to avoid potential off-by-one pixel offsets distorting the bar
|
||||
var cutX = (int)(float2.Lerp(start.X, end.X, value) + 0.5f);
|
||||
var cut = new float2(cutX, start.Y - (cutX - start.X) / 2);
|
||||
|
||||
var cr = Game.Renderer.RgbaColorRenderer;
|
||||
var da = BarWidth * barAspect;
|
||||
var db = new int2(0, BarHeight);
|
||||
var dc = da + db;
|
||||
|
||||
// Filled bar
|
||||
cr.FillRect(start + da, start + dc, cut + dc, cut + da, darkColor);
|
||||
cr.FillRect(start, start + da, start + dc, start + db, darkColor);
|
||||
cr.FillRect(start, start + da, cut + da, cut, barColor);
|
||||
|
||||
// Faint marks to break the monotony of the solid bar
|
||||
var dx = BarWidth;
|
||||
while (dx < cut.X - start.X)
|
||||
{
|
||||
var step = start + dx * stepAspect;
|
||||
cr.DrawLine(step, step + da, 1, DarkenColor);
|
||||
cr.DrawLine(step + da, step + dc, 1, LightenColor);
|
||||
dx += BarWidth;
|
||||
}
|
||||
|
||||
// Second bar (e.g. applied damage)
|
||||
if (secondValue.HasValue && secondColor.HasValue)
|
||||
{
|
||||
var secondCutX = (int)(float2.Lerp(start.X, end.X, secondValue.Value) + 0.5f);
|
||||
var secondCut = new float2(secondCutX, start.Y - (secondCutX - start.X) / 2);
|
||||
var darkSecond = Color.FromArgb(secondColor.Value.A, secondColor.Value.R / 2, secondColor.Value.G / 2, secondColor.Value.B / 2);
|
||||
|
||||
cr.FillRect(cut + da, cut + dc, secondCut + dc, secondCut + da, darkSecond);
|
||||
cr.FillRect(cut, cut + da, secondCut + da, secondCut, secondColor.Value);
|
||||
|
||||
value = secondValue.Value;
|
||||
cut = secondCut;
|
||||
}
|
||||
|
||||
// Empty bar
|
||||
if (value < 1)
|
||||
{
|
||||
cr.FillRect(cut + da, cut + dc, end + dc, end + da, DarkEmptyColor);
|
||||
cr.FillRect(cut, cut + da, end + da, end, EmptyColor);
|
||||
}
|
||||
}
|
||||
|
||||
Color GetHealthColor(IHealth health)
|
||||
{
|
||||
if (Game.Settings.Game.UsePlayerStanceColors)
|
||||
return actor.Owner.PlayerStanceColor(actor);
|
||||
|
||||
return health.DamageState == DamageState.Critical ? Color.Red :
|
||||
health.DamageState == DamageState.Heavy ? Color.Yellow : Color.LimeGreen;
|
||||
}
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
if (!actor.IsInWorld || actor.IsDead)
|
||||
return;
|
||||
|
||||
var health = actor.TraitOrDefault<IHealth>();
|
||||
|
||||
if (DisplayHealth)
|
||||
{
|
||||
if (health == null || health.IsDead)
|
||||
return;
|
||||
|
||||
var displayValue = health.DisplayHP != health.HP ? (float?)health.DisplayHP / health.MaxHP : null;
|
||||
DrawBar(wr, (float)health.HP / health.MaxHP, GetHealthColor(health), 0, displayValue, Color.OrangeRed);
|
||||
}
|
||||
|
||||
if (DisplayExtra)
|
||||
DrawExtraBars(wr);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#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.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
public struct IsometricSelectionBoxAnnotationRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
static readonly float2 TLOffset = new float2(-12, -6);
|
||||
static readonly float2 TROffset = new float2(12, -6);
|
||||
static readonly float2 TOffset = new float2(0, -13);
|
||||
static readonly float2[] Offsets =
|
||||
{
|
||||
-TROffset, -TLOffset, -TOffset,
|
||||
TROffset, -TOffset, -TLOffset,
|
||||
-TLOffset, TOffset, TROffset,
|
||||
TLOffset, TROffset, TOffset,
|
||||
-TROffset, TOffset, TLOffset,
|
||||
TLOffset, -TOffset, -TROffset
|
||||
};
|
||||
|
||||
readonly WPos pos;
|
||||
readonly Polygon bounds;
|
||||
readonly Color color;
|
||||
|
||||
public IsometricSelectionBoxAnnotationRenderable(Actor actor, Polygon bounds, Color color)
|
||||
{
|
||||
pos = actor.CenterPosition;
|
||||
this.bounds = bounds;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public IsometricSelectionBoxAnnotationRenderable(WPos pos, Polygon bounds, Color color)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.bounds = bounds;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public WPos Pos { get { return pos; } }
|
||||
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return 0; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return this; }
|
||||
public IRenderable WithZOffset(int newOffset) { return this; }
|
||||
public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBoxAnnotationRenderable(pos + vec, bounds, color); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
var screen = bounds.Vertices.Select(v => wr.Viewport.WorldToViewPx(v).ToFloat2()).ToArray();
|
||||
|
||||
var tl = new float2(-12, -6);
|
||||
var tr = new float2(12, -6);
|
||||
var t = new float2(0, -13);
|
||||
|
||||
var cr = Game.Renderer.RgbaColorRenderer;
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
cr.DrawLine(new float3[] { screen[i] + Offsets[3 * i], screen[i], screen[i] + Offsets[3 * i + 1] }, 1, color, true);
|
||||
cr.DrawLine(new float3[] { screen[i], screen[i] + Offsets[3 * i + 2] }, 1, color, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr) { }
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
}
|
||||
}
|
||||
72
OpenRA.Mods.Common/Graphics/UITextRenderable.cs
Normal file
72
OpenRA.Mods.Common/Graphics/UITextRenderable.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
#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 OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.Common.Graphics
|
||||
{
|
||||
public struct UITextRenderable : IRenderable, IFinalizedRenderable
|
||||
{
|
||||
readonly SpriteFont font;
|
||||
readonly WPos effectiveWorldPos;
|
||||
readonly int2 screenPos;
|
||||
readonly int zOffset;
|
||||
readonly Color color;
|
||||
readonly Color bgDark;
|
||||
readonly Color bgLight;
|
||||
readonly string text;
|
||||
|
||||
public UITextRenderable(SpriteFont font, WPos effectiveWorldPos, int2 screenPos, int zOffset, Color color, Color bgDark, Color bgLight, string text)
|
||||
{
|
||||
this.font = font;
|
||||
this.effectiveWorldPos = effectiveWorldPos;
|
||||
this.screenPos = screenPos;
|
||||
this.zOffset = zOffset;
|
||||
this.color = color;
|
||||
this.bgDark = bgDark;
|
||||
this.bgLight = bgLight;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public UITextRenderable(SpriteFont font, WPos effectiveWorldPos, int2 screenPos, int zOffset, Color color, string text)
|
||||
: this(font, effectiveWorldPos, screenPos, zOffset, color,
|
||||
ChromeMetrics.Get<Color>("TextContrastColorDark"),
|
||||
ChromeMetrics.Get<Color>("TextContrastColorLight"),
|
||||
text) { }
|
||||
|
||||
public WPos Pos { get { return effectiveWorldPos; } }
|
||||
public PaletteReference Palette { get { return null; } }
|
||||
public int ZOffset { get { return zOffset; } }
|
||||
public bool IsDecoration { get { return true; } }
|
||||
|
||||
public IRenderable WithPalette(PaletteReference newPalette) { return new UITextRenderable(font, effectiveWorldPos, screenPos, zOffset, color, text); }
|
||||
public IRenderable WithZOffset(int newOffset) { return new UITextRenderable(font, effectiveWorldPos, screenPos, zOffset, color, text); }
|
||||
public IRenderable OffsetBy(WVec vec) { return new UITextRenderable(font, effectiveWorldPos + vec, screenPos, zOffset, color, text); }
|
||||
public IRenderable AsDecoration() { return this; }
|
||||
|
||||
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
font.DrawTextWithContrast(text, screenPos, color, bgDark, bgLight, 1);
|
||||
}
|
||||
|
||||
public void RenderDebugGeometry(WorldRenderer wr)
|
||||
{
|
||||
var size = font.Measure(text).ToFloat2();
|
||||
Game.Renderer.RgbaColorRenderer.DrawRect(screenPos - 0.5f * size, screenPos + 0.5f * size, 1, Color.Red);
|
||||
}
|
||||
|
||||
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace OpenRA.Mods.Common.Lint
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
if (!dict.ContainsKey(value.ToLower()))
|
||||
if (!dict.ContainsKey(value.ToLowerInvariant()))
|
||||
emitError("{0}.{1}.{2}: Missing weapon `{3}`."
|
||||
.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
|
||||
}
|
||||
@@ -110,7 +110,7 @@ namespace OpenRA.Mods.Common.Lint
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
if (!dict.ContainsKey(value.ToLower()))
|
||||
if (!dict.ContainsKey(value.ToLowerInvariant()))
|
||||
emitError("{0}.{1}.{2}: Missing voice `{3}`."
|
||||
.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Lint
|
||||
@@ -60,9 +59,6 @@ namespace OpenRA.Mods.Common.Lint
|
||||
var ungranted = consumed.Except(granted);
|
||||
if (ungranted.Any())
|
||||
emitError("Actor type `{0}` consumes conditions that are not granted: {1}".F(actorInfo.Key, ungranted.JoinWith(", ")));
|
||||
|
||||
if ((consumed.Any() || granted.Any()) && actorInfo.Value.TraitInfoOrDefault<ConditionManagerInfo>() == null)
|
||||
emitError("Actor type `{0}` defines conditions but does not include a ConditionManager".F(actorInfo.Key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Lint
|
||||
|
||||
@@ -53,19 +53,12 @@ namespace OpenRA.Mods.Common.LoadScreens
|
||||
}
|
||||
|
||||
// Join a server directly
|
||||
var connect = Launch.GetConnectAddress();
|
||||
if (!string.IsNullOrEmpty(connect))
|
||||
var connect = Launch.GetConnectEndPoint();
|
||||
if (connect != null)
|
||||
{
|
||||
var parts = connect.Split(':');
|
||||
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var host = parts[0];
|
||||
var port = Exts.ParseIntegerInvariant(parts[1]);
|
||||
Game.LoadShellMap();
|
||||
Game.RemoteDirectConnect(host, port);
|
||||
return;
|
||||
}
|
||||
Game.LoadShellMap();
|
||||
Game.RemoteDirectConnect(connect);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a map directly
|
||||
@@ -122,6 +115,7 @@ namespace OpenRA.Mods.Common.LoadScreens
|
||||
}
|
||||
|
||||
// Saved settings may have been invalidated by a hardware change
|
||||
Game.Settings.Graphics.GLProfile = Game.Renderer.GLProfile;
|
||||
Game.Settings.Graphics.VideoDisplay = Game.Renderer.CurrentDisplay;
|
||||
|
||||
// If a ModContent section is defined then we need to make sure that the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<LangVersion>5</LangVersion>
|
||||
@@ -20,29 +20,14 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FuzzyLogicLibrary">
|
||||
<HintPath>..\thirdparty\download\FuzzyLogicLibrary.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="rix0rrr.BeaconLib">
|
||||
<HintPath>..\thirdparty\download\rix0rrr.BeaconLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" />
|
||||
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="../stylecop.json" />
|
||||
</ItemGroup>
|
||||
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
|
||||
|
||||
@@ -34,9 +34,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
world.CancelInputMode();
|
||||
|
||||
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
|
||||
foreach (var subject in Subjects)
|
||||
if (subject != target)
|
||||
yield return new Order(OrderName, subject, Target.FromActor(target), queued);
|
||||
yield return new Order(OrderName, null, Target.FromActor(target), queued, null, Subjects.Where(s => s != target).ToArray());
|
||||
}
|
||||
|
||||
public override void Tick(World world)
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Image))
|
||||
{
|
||||
anim = new Animation(world, info.Image, new Func<int>(GetEffectiveFacing));
|
||||
anim = new Animation(world, info.Image, new Func<WAngle>(GetEffectiveFacing));
|
||||
anim.PlayRepeating(info.Sequences.Random(world.SharedRandom));
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
remainingBounces = info.BounceCount;
|
||||
}
|
||||
|
||||
int GetEffectiveFacing()
|
||||
WAngle GetEffectiveFacing()
|
||||
{
|
||||
var at = (float)ticks / (length - 1);
|
||||
var attitude = angle.Tan() * (1 - 2 * at) / (4 * 1024);
|
||||
@@ -184,9 +184,11 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
var u = (facing % 128) / 128f;
|
||||
var scale = 512 * u * (1 - u);
|
||||
|
||||
return (int)(facing < 128
|
||||
var effective = (int)(facing < 128
|
||||
? facing - scale * attitude
|
||||
: facing + scale * attitude);
|
||||
|
||||
return WAngle.FromFacing(effective);
|
||||
}
|
||||
|
||||
public void Tick(World world)
|
||||
@@ -210,8 +212,8 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0)
|
||||
{
|
||||
var delayedPos = WPos.LerpQuadratic(source, target, angle, ticks - info.TrailDelay, length);
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom),
|
||||
trailPalette, facing: GetEffectiveFacing())));
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, GetEffectiveFacing(), w,
|
||||
info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette)));
|
||||
|
||||
smokeTicks = info.TrailInterval;
|
||||
}
|
||||
|
||||
@@ -65,13 +65,14 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
this.info = info;
|
||||
this.args = args;
|
||||
pos = args.Source;
|
||||
var facing = WAngle.FromFacing(args.Facing);
|
||||
var convertedVelocity = new WVec(info.Velocity.Y, -info.Velocity.X, info.Velocity.Z);
|
||||
velocity = convertedVelocity.Rotate(WRot.FromFacing(args.Facing));
|
||||
velocity = convertedVelocity.Rotate(WRot.FromYaw(facing));
|
||||
acceleration = new WVec(info.Acceleration.Y, -info.Acceleration.X, info.Acceleration.Z);
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Image))
|
||||
{
|
||||
anim = new Animation(args.SourceActor.World, info.Image, () => args.Facing);
|
||||
anim = new Animation(args.SourceActor.World, info.Image, () => facing);
|
||||
|
||||
if (!string.IsNullOrEmpty(info.OpenSequence))
|
||||
anim.PlayThen(info.OpenSequence, () => anim.PlayRepeating(info.Sequences.Random(args.SourceActor.World.SharedRandom)));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Graphics;
|
||||
@@ -148,8 +149,11 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
source = args.CurrentSource();
|
||||
|
||||
if (hasLaunchEffect && ticks == 0)
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, args.CurrentMuzzleFacing, world,
|
||||
{
|
||||
Func<WAngle> getMuzzleFacing = () => WAngle.FromFacing(args.CurrentMuzzleFacing());
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, getMuzzleFacing, world,
|
||||
info.LaunchEffectImage, info.LaunchEffectSequence, info.LaunchEffectPalette)));
|
||||
}
|
||||
|
||||
// Beam tracks target
|
||||
if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
@@ -203,7 +204,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
WDist distanceCovered;
|
||||
WDist rangeLimit;
|
||||
|
||||
int renderFacing;
|
||||
WAngle renderFacing;
|
||||
|
||||
[Sync]
|
||||
int hFacing;
|
||||
@@ -428,7 +429,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
if (!tp.Trait.DeflectionStances.HasStance(tp.Actor.Owner.Stances[args.SourceActor.Owner]))
|
||||
return false;
|
||||
|
||||
return tp.Actor.World.SharedRandom.Next(100 / tp.Trait.Chance) == 0;
|
||||
return tp.Actor.World.SharedRandom.Next(100) < tp.Trait.Chance;
|
||||
}
|
||||
|
||||
void ChangeSpeed(int sign = 1)
|
||||
@@ -835,7 +836,7 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
else
|
||||
move = HomingTick(world, tarDistVec, relTarHorDist);
|
||||
|
||||
renderFacing = new WVec(move.X, move.Y - move.Z, 0).Yaw.Facing;
|
||||
renderFacing = new WVec(move.X, move.Y - move.Z, 0).Yaw;
|
||||
|
||||
// Move the missile
|
||||
var lastPos = pos;
|
||||
@@ -857,8 +858,8 @@ namespace OpenRA.Mods.Common.Projectiles
|
||||
// Create the sprite trail effect
|
||||
if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || info.TrailWhenDeactivated))
|
||||
{
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom),
|
||||
trailPalette, facing: renderFacing)));
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, renderFacing, w,
|
||||
info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette)));
|
||||
|
||||
ticksToNextSmoke = info.TrailInterval;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
@@ -31,14 +31,14 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
|
||||
[Desc("Grant an external condition on this actor and return the revocation token.",
|
||||
"Conditions must be defined on an ExternalConditions trait on the actor.",
|
||||
"If duration > 0 the condition will be automatically revoked after the defined number of ticks")]
|
||||
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
|
||||
public int GrantCondition(string condition, int duration = 0)
|
||||
{
|
||||
var external = externalConditions
|
||||
.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(Self, this));
|
||||
|
||||
if (external == null)
|
||||
throw new InvalidDataException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
|
||||
throw new LuaException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
|
||||
|
||||
return external.GrantCondition(Self, this, duration);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#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.Linq;
|
||||
using Eluant;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
|
||||
namespace OpenRA.Mods.Common.Scripting
|
||||
{
|
||||
[ScriptPropertyGroup("Player")]
|
||||
public class PlayerConditionProperties : ScriptPlayerProperties
|
||||
{
|
||||
readonly ExternalCondition[] externalConditions;
|
||||
|
||||
public PlayerConditionProperties(ScriptContext context, Player player)
|
||||
: base(context, player)
|
||||
{
|
||||
externalConditions = player.PlayerActor.TraitsImplementing<ExternalCondition>().ToArray();
|
||||
}
|
||||
|
||||
[Desc("Grant an external condition on the player actor and return the revocation token.",
|
||||
"Conditions must be defined on an ExternalConditions trait on the player actor.",
|
||||
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
|
||||
public int GrantCondition(string condition, int duration = 0)
|
||||
{
|
||||
var external = externalConditions
|
||||
.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(Player.PlayerActor, this));
|
||||
|
||||
if (external == null)
|
||||
throw new LuaException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
|
||||
|
||||
return external.GrantCondition(Player.PlayerActor, this, duration);
|
||||
}
|
||||
|
||||
[Desc("Revoke a condition using the token returned by GrantCondition.")]
|
||||
public void RevokeCondition(int token)
|
||||
{
|
||||
foreach (var external in externalConditions)
|
||||
if (external.TryRevokeCondition(Player.PlayerActor, this, token))
|
||||
break;
|
||||
}
|
||||
|
||||
[Desc("Check whether this player actor accepts a specific external condition.")]
|
||||
public bool AcceptsCondition(string condition)
|
||||
{
|
||||
return externalConditions
|
||||
.Any(t => t.Info.Condition == condition && t.CanGrantCondition(Player.PlayerActor, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,8 +398,17 @@ namespace OpenRA.Mods.Common.Server
|
||||
LoadMapSettings(server, server.LobbyInfo.GlobalSettings, server.Map.Rules);
|
||||
|
||||
// Reset client states
|
||||
var selectableFactions = server.Map.Rules.Actors["world"].TraitInfos<FactionInfo>()
|
||||
.Where(f => f.Selectable)
|
||||
.Select(f => f.InternalName)
|
||||
.ToList();
|
||||
|
||||
foreach (var c in server.LobbyInfo.Clients)
|
||||
{
|
||||
c.State = Session.ClientState.Invalid;
|
||||
if (!selectableFactions.Contains(c.Faction))
|
||||
c.Faction = "Random";
|
||||
}
|
||||
|
||||
// Reassign players into new slots based on their old slots:
|
||||
// - Observers remain as observers
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Server
|
||||
|
||||
public void ServerStarted(S server)
|
||||
{
|
||||
if (!server.Ip.Equals(IPAddress.Loopback) && LanGameBeacon != null)
|
||||
if (server.Type != ServerType.Local && LanGameBeacon != null)
|
||||
LanGameBeacon.Start();
|
||||
}
|
||||
|
||||
|
||||
@@ -96,13 +96,18 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
regions = new List<Rectangle>();
|
||||
offsets = new List<float2>();
|
||||
var pngRectangle = new Rectangle(0, 0, png.Width, png.Height);
|
||||
|
||||
string frame;
|
||||
for (var i = 0; png.EmbeddedData.TryGetValue("Frame[" + i + "]", out frame); i++)
|
||||
{
|
||||
// Format: x,y,width,height;offsetX,offsetY
|
||||
var coords = frame.Split(';');
|
||||
regions.Add(FieldLoader.GetValue<Rectangle>("Region", coords[0]));
|
||||
var region = FieldLoader.GetValue<Rectangle>("Region", coords[0]);
|
||||
if (!pngRectangle.Contains(region))
|
||||
throw new InvalidDataException("Invalid frame regions {0} defined.".F(region));
|
||||
|
||||
regions.Add(region);
|
||||
offsets.Add(FieldLoader.GetValue<float2>("Offset", coords[1]));
|
||||
}
|
||||
}
|
||||
@@ -122,7 +127,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
if (png.EmbeddedData.ContainsKey("FrameAmount"))
|
||||
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
|
||||
else
|
||||
frameAmount = png.Width / frameSize.Width * png.Height / frameSize.Height;
|
||||
frameAmount = (png.Width / frameSize.Width) * (png.Height / frameSize.Height);
|
||||
}
|
||||
else if (png.EmbeddedData.ContainsKey("FrameAmount"))
|
||||
{
|
||||
@@ -141,6 +146,10 @@ namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
|
||||
var framesPerRow = png.Width / frameSize.Width;
|
||||
|
||||
var rows = (frameAmount + framesPerRow - 1) / framesPerRow;
|
||||
if (png.Width < frameSize.Width * frameAmount / rows || png.Height < frameSize.Height * rows)
|
||||
throw new InvalidDataException("Invalid frame size {0} and frame amount {1} defined.".F(frameSize, frameAmount));
|
||||
|
||||
regions = new List<Rectangle>();
|
||||
offsets = new List<float2>();
|
||||
|
||||
|
||||
@@ -196,7 +196,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Repairable repairable;
|
||||
Rearmable rearmable;
|
||||
IAircraftCenterPositionOffset[] positionOffsets;
|
||||
ConditionManager conditionManager;
|
||||
IDisposable reservation;
|
||||
IEnumerable<int> speedModifiers;
|
||||
INotifyMoving[] notifyMoving;
|
||||
@@ -230,8 +229,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
bool airborne;
|
||||
bool cruising;
|
||||
int airborneToken = ConditionManager.InvalidConditionToken;
|
||||
int cruisingToken = ConditionManager.InvalidConditionToken;
|
||||
int airborneToken = Actor.InvalidConditionToken;
|
||||
int cruisingToken = Actor.InvalidConditionToken;
|
||||
|
||||
MovementType movementTypes;
|
||||
WPos cachedPosition;
|
||||
@@ -293,7 +292,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
repairable = self.TraitOrDefault<Repairable>();
|
||||
rearmable = self.TraitOrDefault<Rearmable>();
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
speedModifiers = self.TraitsImplementing<ISpeedModifier>().ToArray().Select(sm => sm.GetSpeedModifier());
|
||||
cachedPosition = self.CenterPosition;
|
||||
notifyMoving = self.TraitsImplementing<INotifyMoving>().ToArray();
|
||||
@@ -1116,8 +1114,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
airborne = true;
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(Info.AirborneCondition) && airborneToken == ConditionManager.InvalidConditionToken)
|
||||
airborneToken = conditionManager.GrantCondition(self, Info.AirborneCondition);
|
||||
if (!string.IsNullOrEmpty(Info.AirborneCondition) && airborneToken == Actor.InvalidConditionToken)
|
||||
airborneToken = self.GrantCondition(Info.AirborneCondition);
|
||||
}
|
||||
|
||||
void OnAirborneAltitudeLeft()
|
||||
@@ -1126,8 +1124,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
airborne = false;
|
||||
if (conditionManager != null && airborneToken != ConditionManager.InvalidConditionToken)
|
||||
airborneToken = conditionManager.RevokeCondition(self, airborneToken);
|
||||
if (airborneToken != Actor.InvalidConditionToken)
|
||||
airborneToken = self.RevokeCondition(airborneToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1140,8 +1138,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
cruising = true;
|
||||
if (conditionManager != null && !string.IsNullOrEmpty(Info.CruisingCondition) && cruisingToken == ConditionManager.InvalidConditionToken)
|
||||
cruisingToken = conditionManager.GrantCondition(self, Info.CruisingCondition);
|
||||
if (!string.IsNullOrEmpty(Info.CruisingCondition) && cruisingToken == Actor.InvalidConditionToken)
|
||||
cruisingToken = self.GrantCondition(Info.CruisingCondition);
|
||||
}
|
||||
|
||||
void OnCruisingAltitudeLeft()
|
||||
@@ -1150,8 +1148,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return;
|
||||
|
||||
cruising = false;
|
||||
if (conditionManager != null && cruisingToken != ConditionManager.InvalidConditionToken)
|
||||
cruisingToken = conditionManager.RevokeCondition(self, cruisingToken);
|
||||
if (cruisingToken != Actor.InvalidConditionToken)
|
||||
cruisingToken = self.RevokeCondition(cruisingToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user