Compare commits

...

153 Commits

Author SHA1 Message Date
Paul Chote
7b0a0ded48 Fix activity cancellation on FlyAttack RTB.
* AbortOnResupply will now cancel queued activities
  in addition to the current attack.
* Resupply if no ammo is available during a standard attack.
* Don't resupply (move directly to target) if no ammo is available
  during an attack move (C&C3 style).
2020-01-18 15:42:35 +00:00
Paul Chote
7e6dcb1f90 Add AttackSource enum. 2020-01-18 15:42:23 +00:00
Paul Chote
9cf3d2a647 Don't run NextActivity if it has been canceled. 2020-01-18 15:42:12 +00:00
Paul Chote
9095211159 Split fixed-wing attack and strafe attack types. 2020-01-12 21:23:16 +01:00
tovl
5370750d36 Fix aircraft failing to engage targets within range. 2020-01-12 21:14:51 +01:00
Paul Chote
eaf12ffb04 Restore MODIFIER_OVERRIDES handling of unmodified attack move hotkey. 2020-01-12 16:35:32 +01:00
Paul Chote
9f4e1b04b4 Load and save registrations to both the active and legacy support directories. 2020-01-12 14:09:49 +01:00
Paul Chote
53bb1449c0 Rework support dir initialization. 2020-01-12 14:09:39 +01:00
abcdefg30
04e56be8c7 Prevent cell triggers in sarin-gas-2 from firing several times 2020-01-12 13:49:00 +01:00
Paul Chote
017c89cb57 Restore red lines for harvester targets. 2020-01-12 04:12:34 +01:00
Ivaylo Draganov
aaf55210e5 Use system chat line for audio muted/unmuted notifications 2020-01-04 23:05:36 +00:00
Paul Chote
c66a01ad26 Fix hardware cursors on systems with >150% DPI scaling. 2020-01-04 22:50:23 +01:00
Paul Chote
1b3d8dfc8d Rework HardwareCursor sprite padding.
All frames in a sequence now use the same bounds
and hotspot, and have a size that is a multiple of 8.
2020-01-04 22:44:55 +01:00
abcdefg30
c60ee03a58 Fix ants teleporting into the map area
Waypoint17 is not at the map edge, waypoint7 is.
Also fixes a path, since waypoint20 -> waypoint10 is going back and makes no sense.
Going directly waypoint20 -> waypoint2 works and as compensation I added waypoint21 -> waypoint10 -> waypoint2.
2020-01-02 16:35:06 +00:00
reaperrr
d71b116969 Fix DeliverUnit-related crash 2020-01-01 20:42:25 +01:00
Paul Chote
16e18c1cb2 Bypass fingerprint validation for skirmish/mission servers. 2020-01-01 20:35:20 +01:00
Paul Chote
00a553ca41 Replace Server.Dedicated with Server.Type. 2020-01-01 20:34:30 +01:00
abcdefg30
7d9179c5b3 Remove a leftover geoip reference from the linux packaging script 2019-12-31 17:03:20 +00:00
Paul Chote
f5e4c1d952 Add last public release of GeoLite2 database. 2019-12-31 16:41:27 +00:00
Paul Chote
e31bcd3f02 Remove automatic GeoIP download from the build scripts.
The database file is now locked behind an account login.

https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases/
2019-12-31 17:29:06 +01:00
reaperrr
a5b0b047ea Disable SupportPowerInstances when player lost
Fixes bots using player actor powers after defeat.
2019-12-30 23:06:33 +01:00
teinarss
844da265bd Add IsDead check to FerryUnit OnFirstRun 2019-12-29 19:19:42 +01:00
Paul Chote
373bfdbd26 Disable sound controls when no audio device is available. 2019-12-29 17:57:54 +01:00
Paul Chote
846df4f1b5 Rename dummy sound label to "No Sound Output". 2019-12-29 17:57:38 +01:00
Paul Chote
73caeb36a4 Filter invalid actor IDs when restoring selection save data. 2019-12-28 20:51:28 +01:00
Ivaylo Draganov
26c3ea50fa Remove "Surrender" button for single player games 2019-12-28 15:06:58 +00:00
Ivaylo Draganov
4e9cf96d6d Always prompt the user to confirm when leaving a game 2019-12-28 15:06:52 +00:00
Ivaylo Draganov
db72971717 Add restart button to main menu for single player (missions, skirmish, replays) 2019-12-28 15:06:46 +00:00
Paul Chote
1852a8739b Remove overlapping vision ranges from RA actors.
This brings a significant perf saving by reducing
the number of evaluated tiles.
2019-12-23 13:53:47 +01:00
Paul Chote
3812de6953 Add MinRange support to AffectsShroud. 2019-12-23 13:53:42 +01:00
Paul Chote
398406294a Fix ATEK vision ranges. 2019-12-23 13:53:40 +01:00
abcdefg30
97c8dca131 Drop invisible invalid targets immediately when changing stance 2019-12-14 17:24:46 +01:00
abcdefg30
91772847d2 Prevent a crash in AutoTarget.HasValidTargetPriority 2019-12-14 17:24:41 +01:00
abcdefg30
1130ccf0bc Fix team chat messages not always being displayed 2019-12-10 13:14:28 +01:00
Ivaylo Draganov
6366ca8c4a Remove stray "Hotkeys" label in the Input Settings panel in TD 2019-12-08 21:40:32 +01:00
reaperrr
324d2bae92 Restore old shroud recalc frequency for aircraft
The logic has shown to cause serious performance issues
with fast movement.
Disabling it on aircraft for the hotfix release.
2019-12-08 20:56:34 +01:00
Ivaylo Draganov
6606f517cf Add selection tiers as inheritable templates 2019-12-08 18:40:02 +01:00
Abdurrahmaan Iqbal
af9c2972b1 Fix #17230: Dummy audio output class 2019-11-29 14:11:42 +01:00
reaperrr
99bc783eb8 Fix FCOM still providing space while being captured 2019-11-27 23:04:19 +01:00
blackhand1001
93e4083640 Fix squadmanager adding naval units to ground attack forces.
Fix squadmanager adding naval units to ground attack forces. This was breaking the behavior of both naval and ground squads.
2019-11-24 00:36:41 +01:00
abcdefg30
609dadc51b Fix McvManagerBotModule spamming deploy orders
Removes the 'activeMCVs' list since it was not useful.
The real bugfix is not iterating over 'activeMCVs' when issueing new orders
(this was previously needed for already discovered mcvs that stopped)
but over 'newMCVs' instead.
2019-11-24 00:36:23 +01:00
blackhand1001
b2f2b81629 Simplify for loop structure now that it only has one check
Simplify for loop structure now that it only has one check
2019-11-24 00:36:09 +01:00
blackhand1001
b867f3250d Fix MCV Manager glitch when restrict building area is enabled.
Fix MCV Manager glitch when restrict building area is enabled. It was checking if the location was close enough to the Base center instead of using the MCV Managers min and max ranges. This would cause it to often have no valid locations despite having a huge range.
2019-11-24 00:35:56 +01:00
matjaeck
916599d313 Let AI ignore frozen actors and target original actors instead. 2019-11-24 00:35:37 +01:00
Jonas A. Lind
8f983453c7 Redo buildpaletteorder for RA vehicles
Restructure RA's vehicle build tab. More streamlined and better looking.
2019-11-24 00:18:31 +01:00
SoScared
16fc540226 Redo buildpaletteorder for D2K buildings 2019-11-24 00:18:20 +01:00
Abdurrahmaan Iqbal
7a74a5059c Prevent showing wall connections in unexplored terrain 2019-11-24 00:00:26 +01:00
abcdefg30
3ec995258d Prevent chronoshifting an empty selection 2019-11-23 23:57:45 +01:00
abcdefg30
2b4bd76a99 Don't allow movement for actors without IMove
However, this does not check if any existing IMove traits are enabled.
2019-11-23 23:53:21 +01:00
abcdefg30
bc5c084250 Fix actors returning fire at invisible attackers 2019-11-23 23:53:08 +01:00
reaperrr
75fe91b921 Fix RA desert tree fire palette
By default WithDamageOverlay uses the actors'
palette, but RA's desert terrain uses the TD desert.pal
which isn't compatible with RA's fire animation shps.
2019-11-23 18:45:16 +01:00
Paul Chote
a47969b31b Spawn ejected pilots inside a FrameEndTask. 2019-11-16 23:13:29 +01:00
Paul Chote
33b1e74296 Remove landing behaviour from force-move orders on selectable buildings. 2019-11-02 18:29:51 +00:00
teinarss
059b8502c0 Fix OccupiedCells for units sharing cells 2019-10-31 21:33:15 +00:00
Paul Chote
fdfc368db4 Override selection if the mouse is over an already selected actor. 2019-10-30 14:52:49 +01:00
Paul Chote
c42b5d9e07 Pass contextual information to TargetOverridesSelection. 2019-10-30 14:52:47 +01:00
Paul Chote
5e24183108 Force selection cursor when selection overrides input. 2019-10-30 14:52:46 +01:00
Paul Chote
a94856df0c Remove WorldRenderer argument from InputOverridesSelection. 2019-10-30 14:52:45 +01:00
Paul Chote
1c36b38c1d Fix InputOverridesSelection only considering the closest actor. 2019-10-30 14:52:41 +01:00
Paul Chote
6c8b3e17de Fix EjectOnDeath pilot spawning. 2019-10-29 23:05:36 +01:00
Paul Chote
adbc368b14 Install x64 release into correct Program Files on Windows. 2019-10-28 21:03:31 +01:00
Paul Chote
3deaf6c7bb Fix infantry idle animations playing immediately after creation. 2019-10-20 16:42:36 +02:00
Paul Chote
8bbf43f45d Don't consider unit creation as movement. 2019-10-20 16:42:26 +02:00
abcdefg30
5cd674445c Explain that CanEnterCell ignores 'subCell' if there is a free subcell 2019-10-19 23:15:10 +01:00
abcdefg30
27f1c37eca Let Mobile's CanEnterCell consider ToSubCell 2019-10-19 23:15:10 +01:00
abcdefg30
258259af4a Add a subCell parameter to IPositionableInfo.CanEnterCell 2019-10-19 23:15:10 +01:00
Paul Chote
276b1a9509 Don't override spawn CenterPosition for non-aircraft reinforcements. 2019-10-19 23:45:59 +02:00
Paul Chote
5b19dbe20c Disable Carryable while submerged. 2019-10-19 13:18:54 +02:00
Paul Chote
40424814a4 Allow carryall pickup orders on deployed vehicles. 2019-10-19 13:18:54 +02:00
Paul Chote
c0ea92850b Add UndeployOnPickup to GrantConditionOnDeploy. 2019-10-19 13:18:54 +02:00
Paul Chote
dc1f11f412 Prevent movement pausing at invalid position. 2019-10-19 13:18:54 +02:00
Paul Chote
e4602f8db2 Replace MoveIntoWorld with ReturnToCell/AssociateWithAirfield. 2019-10-17 23:32:28 +02:00
Paul Chote
b4b4412664 Revert "Suppress MoveIntoWorldInit for map-placed Mobile actors."
This reverts commit f0c28cc153.
2019-10-17 23:32:26 +02:00
reaperrr
a23634897a Fix zombie stand2 sequence and run tickrate 2019-10-14 17:29:24 +02:00
abc013
082efe1b08 Adjusted die sequence length of zombie 2019-10-14 17:29:22 +02:00
abc013
c6c05802d2 Fixed zombie.shp
Added missing zombie attack frame
and other fixes.
2019-10-14 17:29:20 +02:00
tovl
c091d7dc67 Fix lastVisibleTarget not being set in FlyAttack and AttackActivity. 2019-10-14 01:23:22 +02:00
abcdefg30
ac1d95c863 Guard against overlaps on HiDPI by having a 5px border on graphs 2019-10-14 01:05:38 +02:00
abcdefg30
f15bb98f4a Special case the TD spectator UI to fit the minimum width 2019-10-14 01:05:24 +02:00
abcdefg30
1353ad38a0 Reduce the width of the combat stats tab 2019-10-14 01:05:10 +02:00
abcdefg30
508f74822c Reduce the Width of INGAME_OBSERVERSTATS_BG 2019-10-14 01:04:55 +02:00
reaperrr
4b5ae12536 Fix missing rules in prep update path 2019-10-13 22:37:23 +01:00
abc013
4ad4e143b7 Add isDead-check to the flamethrowers in allies06b 2019-10-13 18:21:12 +02:00
abc013
94aa310612 Make normal difficulty on allies06b easier 2019-10-13 18:21:10 +02:00
Nakarin Srijumrat
06d5826a0b increased ingame edge scrollspeed to 30 from 10 2019-10-13 13:23:02 +02:00
Paul Chote
a64f0eb0f5 Update macOS launcher package.
This pulls in a fix for the missing libmono-native-compat.dylib
2019-10-11 22:03:20 +02:00
abcdefg30
814878126b Minor style fixes 2019-10-11 21:17:46 +02:00
abcdefg30
3438dd3f74 Reduce string allocations in ObserverStatsLogic 2019-10-11 21:17:31 +02:00
abcdefg30
e6b9bc07e9 Keep army and income graph disabled if they were disabled once 2019-10-11 21:17:17 +02:00
abcdefg30
cee511f59d Replace "$/min" by "Income" and increase graph update rates 2019-10-11 21:17:04 +02:00
abcdefg30
992559d317 Remove $/min from the basic stats 2019-10-11 21:16:49 +02:00
abcdefg30
f27440d67e Add an XAxisTicksPerLabel property to LineGraphWidget 2019-10-11 21:16:37 +02:00
abcdefg30
6b1aac4d98 Work around a recursive loop in TargetAndAttack 2019-10-11 21:04:22 +02:00
Paul Chote
96b95c8980 Suppress MoveIntoWorldInit for map-placed Mobile actors. 2019-10-07 19:09:42 +02:00
teinarss
088288fee1 PlayerStatistics should stop ticking after lost/win 2019-10-06 20:50:04 +01:00
tovl
a93fbc3219 pause MovePart when Mobile is paused. 2019-10-06 20:29:47 +01:00
abcdefg30
58273c532c Fix FallsToEarth queueing an activity in its ctor 2019-10-06 14:43:26 +01:00
abcdefg30
4498f52723 Add an ICreationActivity interface 2019-10-06 14:43:22 +01:00
abcdefg30
28ede7ad8f Fix setting the position of the wrong actor 2019-10-06 14:43:16 +01:00
abcdefg30
fd663cc3f3 Fix the XAxis of LineGraphWidget not being updated properly 2019-10-06 13:12:07 +01:00
abcdefg30
75d6495003 Cache method call results in variables in Draw of LineGraphWidget 2019-10-06 13:11:58 +01:00
Paul Chote
eb4ba7e793 Remove double-negative from appimage wrapper. 2019-10-05 18:31:51 +02:00
Paul Chote
4532cf9b55 Reset environment variables before switching mods. 2019-10-05 18:31:49 +02:00
Paul Chote
2fba47151c Add Engine.LaunchWrapper launch argument for mod switching. 2019-10-05 18:31:47 +02:00
Paul Chote
d4f1ea54e7 Compile using Mono 6.4.0. 2019-10-05 18:26:25 +02:00
Paul Chote
4a6efc99ee Update packaged mono to 6.4.0. 2019-10-05 18:26:22 +02:00
Paul Chote
c150ed373c Filter invalid actors when loading and saving games. 2019-10-05 17:51:10 +02:00
Paul Chote
d99b22db51 Replace actor list with count in UnitBuilderBotModule. 2019-10-05 17:50:51 +02:00
abcdefg30
51ad7e6b59 Fix double clicking a save in the save game dialogue loading it 2019-10-05 17:38:57 +02:00
abcdefg30
493294c61e Fix harvesters idling on Infiltration 2019-10-05 16:13:39 +02:00
abcdefg30
ea7d00df8d Fix the town attackers in Infiltration not stopping 2019-10-05 16:13:26 +02:00
abcdefg30
3218249a2d Remove unnecessary SearchFromHarvesterRadius overwrites from TD missions 2019-10-05 16:02:56 +02:00
abcdefg30
2be9200ce7 Add SearchFromProcRadius to TD missions that need it 2019-10-05 16:02:36 +02:00
abcdefg30
90ca7cedc2 Fix a crash when MaxLevel of GainsExperience is zero 2019-10-05 15:59:42 +02:00
teinarss
4f884f9835 Remove CanEnterCell from OccupiedCells 2019-10-05 14:45:58 +02:00
teinarss
1f8aa67593 Mark cells that have changed MovementType as dirty 2019-10-05 14:45:53 +02:00
Ivaylo Draganov
44af88bb49 Set duplicates flag for hotkeys in HotkeyManager 2019-10-05 13:17:44 +02:00
tovl
522ee22845 Fix deployed units being nudgeable. 2019-10-05 11:15:18 +01:00
Ivaylo Draganov
9d98276c7d Add duplicate hotkey tracking with a boolean property on the definition 2019-10-05 11:08:35 +01:00
Punsho
7ecc653183 RA balance changes for September 2019 2019-10-05 11:03:52 +01:00
Michael Silber
de330daf85 Fix destroyed truck escaping ra mission sarin-gas-1 2019-10-04 22:41:49 +01:00
Paul Chote
e2a74f21b7 Expire invalid instances from the SupportPowerBotModule cache. 2019-10-01 19:30:59 +02:00
Paul Chote
7bb594e11b Drop invalid power references when loading save games. 2019-10-01 19:29:54 +02:00
abcdefg30
4055952be3 Increase the SearchFromProcRadius radius in soviet05 2019-09-30 20:31:04 +02:00
abcdefg30
77daea61bb Remove the now unnecessary Helper refinery 2019-09-30 20:31:01 +02:00
Punsho
fec1e244f3 Make mine targetable on attack everything stance for AutoTargetGround 2019-09-28 14:31:15 +02:00
teinarss
1e26672b70 Add check to see if transport is dead to UnreserveSpace 2019-09-28 14:10:01 +02:00
abcdefg30
aead03d9b3 Fix the expansion mcv in soviet05 being transported off the map 2019-09-28 14:06:38 +02:00
abcdefg30
a9d44d4e44 Revert the search radius decrease in D2k 2019-09-28 13:57:49 +02:00
tovl
7047ba3a31 Skip rally point if order is queued after resupply. 2019-09-27 13:44:31 +02:00
tovl
e169368cf4 Add missing target line to aircraft taking off from resupplying. 2019-09-27 13:44:30 +02:00
tovl
90f3788187 Refactor unreserve actions. 2019-09-27 13:44:30 +02:00
tovl
96b65aa969 Prevent bogus attackmove on take-off. 2019-09-27 13:44:30 +02:00
Punsho
0771a42749 Fix ctank and stank building faster then they should 2019-09-27 13:44:29 +02:00
SoScared
8afa1d8dde Add map Climax to RA map pool 2019-09-27 13:44:29 +02:00
Paul Chote
79f6a51deb Fix player viewport saving for non-spectators. 2019-09-27 13:44:29 +02:00
Oliver Brakmann
4bb4897ba3 Fix idling aircraft on Intervention 2019-09-27 13:44:29 +02:00
abcdefg30
f82aa58d2b Remove selling from Infilitration
It is weird, unsatisfying for the player and inconsistent with the rest of our missions
2019-09-27 13:44:28 +02:00
abcdefg30
30ee546fb3 Fix potentially bogus usages of OnAllRemovedFromWorld 2019-09-27 13:44:28 +02:00
abcdefg30
2dad293328 Prevent users from selecting a directional target outside the map 2019-09-27 13:44:28 +02:00
tovl
14c4e2978f Fix crash with dead cargo. 2019-09-27 13:44:27 +02:00
Jan Beich
cb75a85341 Extend Linux dllmap to other systems
- Drop `os` in Eluant config as it's only used on Linux
- Make generic to help BSDs and Solaris
- Update OpenAL-CS and SDL2-CS to get the same

Exception of type `System.DllNotFoundException`: lua51.dll
TypeName=``
  at (wrapper managed-to-native) Eluant.LuaApi.lua_newstate(Eluant.LuaRuntime/LuaAllocator,intptr)
  at Eluant.LuaRuntime..ctor ()
  at Eluant.MemoryConstrainedLuaRuntime..ctor ()
  at OpenRA.Scripting.ScriptContext..ctor (OpenRA.World world, OpenRA.Graphics.WorldRenderer worldRenderer, System.Collections.Generic.IEnumerable`1[T] scripts)
  at OpenRA.Mods.Common.Scripting.LuaScript.OpenRA.Traits.IWorldLoaded.WorldLoaded (OpenRA.World world, OpenRA.Graphics.WorldRenderer worldRenderer)
  at OpenRA.World.LoadComplete (OpenRA.Graphics.WorldRenderer wr)
  at OpenRA.Game.StartGame (System.String mapUID, OpenRA.WorldType type)
  at OpenRA.Game.LoadShellMap ()
  at OpenRA.Mods.Common.LoadScreens.BlankLoadScreen.StartGame (OpenRA.Arguments args)
  at OpenRA.Game.InitializeMod (System.String mod, OpenRA.Arguments args)
  at OpenRA.Game.Initialize (OpenRA.Arguments args)
  at OpenRA.Game.InitializeAndRun (System.String[] args)
  at OpenRA.Program.Main (System.String[] args)
2019-09-27 13:44:27 +02:00
Jan Beich
dbbbb2c782 command -v with more than one argument is non-portable
On FreeBSD build fails, so check if `msbuild` exists without arguments.

$ gmake
command: wrong number of arguments
OpenRA requires the 'msbuild -verbosity:m -nologo' tool provided by Mono >= 5.4.
gmake: *** [Makefile:154: core] Error 1

# FreeBSD sh
$ command -v echo ls
command: wrong number of arguments

# dash
$ command -v echo ls
echo

# ksh, bash, zsh
$ command -v echo ls
echo
/bin/ls
2019-09-27 13:44:27 +02:00
teinarss
bf432df830 Locomotor cache should handle custom layers 2019-09-27 13:44:27 +02:00
abcdefg30
c42e2db542 Replaced "Earned this min" by an Oil Derrick count in the economy statistics 2019-09-27 13:44:26 +02:00
abcdefg30
bc55717b6b Add an "UpdatesDerrickCount" trait 2019-09-27 13:44:26 +02:00
teinarss
19551063a8 Remove IronCurtainable from RA aircraft. 2019-09-08 12:33:02 +02:00
teinarss
6d0f3a0008 Fix crushable logic for actors in cell 2019-09-07 13:32:08 +01:00
abcdefg30
88d11b4c66 Fix a division by zero error in FindAndDeliverResources
by preventing an overflow through dividing directly
2019-09-07 10:49:00 +01:00
puritylake
9f7c3d6bac #17018 Gets rif of cutoff line in chat window 2019-09-06 23:04:54 +02:00
teinarss
55ba6b9e26 More robust logic for ThisMinute stats 2019-09-06 14:03:31 +02:00
teinarss
79627d036e GetAvailableSubCell should block cells outside the map 2019-09-06 13:33:13 +02:00
abcdefg30
2438ee487a Remove the duplicate Selectable trait on TENF 2019-09-06 13:26:42 +02:00
211 changed files with 2528 additions and 1676 deletions

1
.gitignore vendored
View File

@@ -26,7 +26,6 @@ mods/*/*.pdb
/*.exe
/*.exe.config
thirdparty/download/*
*.mmdb.gz
# backup files by various editors
*~

View File

@@ -3,7 +3,7 @@
dist: xenial
language: csharp
mono: 5.20.1
mono: 6.4.0
cache:
directories:

BIN
GeoLite2-Country.mmdb.gz Normal file

Binary file not shown.

View File

@@ -151,7 +151,7 @@ test: core
all: dependencies core
core:
@command -v $(MSBUILD) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
@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
@@ -173,30 +173,26 @@ cli-dependencies:
@ $(CP_R) thirdparty/download/*.dll.config .
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
linux-dependencies: cli-dependencies geoip-dependencies linux-native-dependencies
linux-dependencies: cli-dependencies linux-native-dependencies
linux-native-dependencies:
@./thirdparty/configure-native-deps.sh
windows-dependencies: cli-dependencies geoip-dependencies
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 geoip-dependencies
osx-dependencies: cli-dependencies
@./thirdparty/fetch-thirdparty-deps-osx.sh
@ $(CP_R) thirdparty/download/osx/*.dylib .
@ $(CP_R) thirdparty/download/osx/*.dll.config .
geoip-dependencies:
@./thirdparty/fetch-geoip-db.sh
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
dependencies: $(os-dependencies)
all-dependencies: cli-dependencies windows-dependencies osx-dependencies geoip-dependencies
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
@echo "$(VERSION)" > VERSION
@@ -387,4 +383,4 @@ help:
.SUFFIXES:
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies geoip-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 nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help

View File

@@ -49,7 +49,31 @@ namespace OpenRA.Activities
public ActivityState State { get; private set; }
protected Activity ChildActivity { get; private set; }
public Activity NextActivity { get; private set; }
Activity nextActivity;
public Activity NextActivity
{
get
{
// If Activity.NextActivity.Cancel() was called, NextActivity will be in the Canceling
// state rather than Queued (the activity system guarantees that it cannot be Active or Done).
// An unknown number of ticks may have elapsed between the Activity.NextActivity.Cancel() call
// and now, so we cannot make any assumptions on the value of Activity.NextActivity.NextActivity.
// We must not return nextActivity (ticking it would be bogus), but returning null would potentially
// drop valid activities queued after it. Walk the queue until we find a valid activity or
// (more likely) run out of activities.
var next = nextActivity;
while (next != null && next.State == ActivityState.Canceling)
next = next.NextActivity;
return next;
}
private set
{
nextActivity = value;
}
}
public bool IsInterruptible { get; protected set; }
public bool ChildHasPriority { get; protected set; }

View File

@@ -82,6 +82,7 @@ namespace OpenRA
readonly INotifyIdle[] tickIdles;
readonly ITargetablePositions[] targetablePositions;
WPos[] staticTargetablePositions;
bool created;
internal Actor(World world, string name, TypeDictionary initDict)
{
@@ -138,6 +139,35 @@ namespace OpenRA
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
}
internal void Created()
{
created = true;
foreach (var t in TraitsImplementing<INotifyCreated>())
t.Created(this);
// 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;
foreach (var ica in TraitsImplementing<ICreationActivity>())
{
if (!ica.IsTraitEnabled())
continue;
if (creationActivity != null)
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
var activity = ica.GetCreationActivity();
if (activity == null)
continue;
creationActivity = ica;
activity.Queue(CurrentActivity);
CurrentActivity = activity;
}
}
public void Tick()
{
var wasIdle = IsIdle;
@@ -214,11 +244,15 @@ namespace OpenRA
{
if (!queued)
CancelActivity();
QueueActivity(nextActivity);
}
public void QueueActivity(Activity nextActivity)
{
if (!created)
throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
if (CurrentActivity == null)
CurrentActivity = nextActivity;
else

View File

@@ -45,10 +45,15 @@ namespace OpenRA
{
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
// If the player has defined a local support directory (in the game directory)
// then this will override both the regular and system support dirs
var sources = new[] { Platform.SystemSupportDir, Platform.SupportDir };
foreach (var source in sources.Distinct())
// Several types of support directory types are available, depending on
// how the player has installed and launched the game.
// Read registration metadata from all of them
var sources = Enum.GetValues(typeof(SupportDirType))
.Cast<SupportDirType>()
.Select(t => Platform.GetSupportDir(t))
.Distinct();
foreach (var source in sources)
{
var metadataPath = Path.Combine(source, "ModMetadata");
if (!Directory.Exists(metadataPath))
@@ -112,10 +117,18 @@ namespace OpenRA
var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.SystemSupportDir);
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
sources.Add(Platform.SupportDir);
{
sources.Add(Platform.GetSupportDir(SupportDirType.User));
// If using the modern support dir we must also write the registration
// to the legacy support dir for older engine versions, but ONLY if it exists
var legacyPath = Platform.GetSupportDir(SupportDirType.LegacyUser);
if (Directory.Exists(legacyPath))
sources.Add(legacyPath);
}
// Make sure the mod is available for this session, even if saving it fails
LoadMod(yaml.First().Value, forceRegistration: true);
@@ -148,10 +161,16 @@ namespace OpenRA
{
var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.SystemSupportDir);
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
sources.Add(Platform.SupportDir);
{
// User support dir may be using the modern or legacy value, or overridden by the user
// Add all the possibilities and let the .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var activeModKey = ExternalMod.MakeKey(activeMod);
foreach (var source in sources.Distinct())
@@ -207,10 +226,16 @@ namespace OpenRA
{
var sources = new List<string>();
if (registration.HasFlag(ModRegistration.System))
sources.Add(Platform.SystemSupportDir);
sources.Add(Platform.GetSupportDir(SupportDirType.System));
if (registration.HasFlag(ModRegistration.User))
sources.Add(Platform.SupportDir);
{
// User support dir may be using the modern or legacy value, or overridden by the user
// Add all the possibilities and let the .Distinct() below ignore the duplicates
sources.Add(Platform.GetSupportDir(SupportDirType.User));
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
}
var key = ExternalMod.MakeKey(mod);
mods.Remove(key);

View File

@@ -22,6 +22,7 @@ using System.Threading.Tasks;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Server;
using OpenRA.Support;
using OpenRA.Widgets;
@@ -41,6 +42,7 @@ namespace OpenRA
public static ICursor Cursor;
public static bool HideCursor;
static WorldRenderer worldRenderer;
static string modLaunchWrapper;
internal static OrderManager OrderManager;
static Server.Server server;
@@ -351,6 +353,8 @@ namespace OpenRA
foreach (var mod in Mods)
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
ExternalMods = new ExternalMods();
Manifest currentMod;
@@ -509,8 +513,15 @@ namespace OpenRA
{
try
{
var path = mod.LaunchPath;
var args = launchArguments != null ? mod.LaunchArgs.Append(launchArguments) : mod.LaunchArgs;
var p = Process.Start(mod.LaunchPath, args.Select(a => "\"" + a + "\"").JoinWith(" "));
if (modLaunchWrapper != null)
{
path = modLaunchWrapper;
args = new[] { mod.LaunchPath }.Concat(args);
}
var p = Process.Start(path, args.Select(a => "\"" + a + "\"").JoinWith(" "));
if (p == null || p.HasExited)
onFailed();
else
@@ -886,7 +897,7 @@ namespace OpenRA
public static void CreateServer(ServerSettings settings)
{
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, false);
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
}
public static int CreateLocalServer(string map)
@@ -898,7 +909,7 @@ namespace OpenRA
AdvertiseOnline = false
};
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, false);
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
return server.Port;
}

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
namespace OpenRA.Graphics
@@ -38,19 +37,46 @@ namespace OpenRA.Graphics
hardwarePalette.AddPalette(p.Key, p.Value, false);
hardwarePalette.Initialize();
sheetBuilder = new SheetBuilder(SheetType.Indexed);
foreach (var kv in cursorProvider.Cursors)
{
var frames = kv.Value.Frames;
var palette = cursorProvider.Palettes[kv.Value.Palette];
var hc = kv.Value.Frames
.Select(f => CreateCursor(f, palette, kv.Key, kv.Value))
.ToArray();
hardwareCursors.Add(kv.Key, hc);
// Hardware cursors have a number of odd platform-specific bugs/limitations.
// Reduce the number of edge cases by padding the individual frames such that:
// - the hotspot is inside the frame bounds (enforced by SDL)
// - all frames within a sequence have the same size (needed for macOS 10.15)
// - the frame size is a multiple of 8 (needed for Windows)
var sequenceBounds = Rectangle.FromLTRB(0, 0, 1, 1);
var frameHotspots = new int2[frames.Length];
for (var i = 0; i < frames.Length; i++)
{
// Hotspot relative to the center of the frame
frameHotspots[i] = kv.Value.Hotspot - frames[i].Offset.ToInt2() + new int2(frames[i].Size) / 2;
var s = kv.Value.Frames.Select(a => sheetBuilder.Add(a)).ToArray();
sprites.Add(kv.Key, s);
// Bounds relative to the hotspot
sequenceBounds = Rectangle.Union(sequenceBounds, new Rectangle(-frameHotspots[i], frames[i].Size));
}
// Pad bottom-right edge to make the frame size a multiple of 8
var paddedSize = 8 * new int2((sequenceBounds.Width + 7) / 8, (sequenceBounds.Height + 7) / 8);
var cursors = new IHardwareCursor[frames.Length];
var frameSprites = new Sprite[frames.Length];
for (var i = 0; i < frames.Length; i++)
{
// Software rendering is used when the cursor is locked
frameSprites[i] = sheetBuilder.Add(frames[i].Data, frames[i].Size, 0, frames[i].Offset);
// Calculate the padding to position the frame within sequenceBounds
var paddingTL = -(sequenceBounds.Location + frameHotspots[i]);
var paddingBR = paddedSize - new int2(frames[i].Size) - paddingTL;
cursors[i] = CreateCursor(kv.Key, frames[i], palette, paddingTL, paddingBR, -sequenceBounds.Location);
}
hardwareCursors.Add(kv.Key, cursors);
sprites.Add(kv.Key, frameSprites);
}
sheetBuilder.Current.ReleaseBuffer();
@@ -64,48 +90,24 @@ namespace OpenRA.Graphics
return new PaletteReference(name, hardwarePalette.GetPaletteIndex(name), pal, hardwarePalette);
}
IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence)
IHardwareCursor CreateCursor(string name, ISpriteFrame frame, ImmutablePalette palette, int2 paddingTL, int2 paddingBR, int2 hotspot)
{
var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2;
// Expand the frame if required to include the hotspot
var frameWidth = f.Size.Width;
var dataWidth = f.Size.Width;
var dataX = 0;
if (hotspot.X < 0)
// Pad the cursor and convert to RBGA
var newWidth = paddingTL.X + frame.Size.Width + paddingBR.X;
var newHeight = paddingTL.Y + frame.Size.Height + paddingBR.Y;
var rgbaData = new byte[4 * newWidth * newHeight];
for (var j = 0; j < frame.Size.Height; j++)
{
dataX = -hotspot.X;
dataWidth += dataX;
hotspot = hotspot.WithX(0);
}
else if (hotspot.X >= frameWidth)
dataWidth = hotspot.X + 1;
var frameHeight = f.Size.Height;
var dataHeight = f.Size.Height;
var dataY = 0;
if (hotspot.Y < 0)
{
dataY = -hotspot.Y;
dataHeight += dataY;
hotspot = hotspot.WithY(0);
}
else if (hotspot.Y >= frameHeight)
dataHeight = hotspot.Y + 1;
var data = new byte[4 * dataWidth * dataHeight];
for (var j = 0; j < frameHeight; j++)
{
for (var i = 0; i < frameWidth; i++)
for (var i = 0; i < frame.Size.Width; i++)
{
var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]);
var start = 4 * ((j + dataY) * dataWidth + dataX + i);
var bytes = BitConverter.GetBytes(palette[frame.Data[j * frame.Size.Width + i]]);
var o = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
for (var k = 0; k < 4; k++)
data[start + k] = bytes[k];
rgbaData[o + k] = bytes[k];
}
}
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, hotspot);
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot);
}
public void SetCursor(string cursorName)

View File

@@ -20,6 +20,7 @@ namespace OpenRA
public readonly Hotkey Default = Hotkey.Invalid;
public readonly string Description = "";
public readonly HashSet<string> Types = new HashSet<string>();
public bool HasDuplicates { get; internal set; }
public HotkeyDefinition(string name, MiniYaml node)
{

View File

@@ -38,6 +38,9 @@ namespace OpenRA
if (definitions.ContainsKey(kv.Key))
keys[kv.Key] = kv.Value;
}
foreach (var hd in definitions)
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
}
internal Func<Hotkey> GetHotkeyReference(string name)
@@ -65,6 +68,20 @@ namespace OpenRA
settings[name] = value;
else
settings.Remove(name);
var hadDuplicates = definition.HasDuplicates;
definition.HasDuplicates = GetFirstDuplicate(definition.Name, this[definition.Name].GetValue(), definition) != null;
if (hadDuplicates || definition.HasDuplicates)
{
foreach (var hd in definitions)
{
if (hd.Value == definition)
continue;
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
}
}
}
public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)

View File

@@ -88,14 +88,12 @@ namespace OpenRA.Network
break;
}
if (orderManager.LocalClient == null)
break;
var player = world.FindPlayerByClient(client);
var localClientIsObserver = orderManager.LocalClient.IsObserver || (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
// ExtraData gives us the team number, uint.MaxValue means Spectators
if (order.ExtraData == uint.MaxValue && (localClientIsObserver || world.IsReplay))
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
{
// Validate before adding the line
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
@@ -105,8 +103,8 @@ namespace OpenRA.Network
}
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
var isSameTeam = order.ExtraData == orderManager.LocalClient.Team && world.LocalPlayer != null
&& world.LocalPlayer.WinState == WinState.Undefined;
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
if (valid && (isSameTeam || world.IsReplay))
Game.AddChatLine("[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, client.Color, message);

View File

@@ -63,7 +63,7 @@ namespace OpenRA.Orders
return world.Map.Contains(cell) ? Cursor : "generic-blocked";
}
public override bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
public override bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
// Custom order generators always override selection
return true;

View File

@@ -67,21 +67,27 @@ namespace OpenRA.Orders
public virtual string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
var useSelect = false;
var target = TargetForInput(world, cell, worldPixel, mi);
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
if (target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any()))
useSelect = true;
bool useSelect;
if (Game.Settings.Game.UseClassicMouseStyle && !InputOverridesSelection(world, worldPixel, mi))
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>();
else
{
var ordersWithCursor = world.Selection.Actors
.Select(a => OrderForUnit(a, target, actorsAt, cell, mi))
.Where(o => o != null && o.Cursor != null);
var ordersWithCursor = world.Selection.Actors
.Select(a => OrderForUnit(a, target, actorsAt, cell, mi))
.Where(o => o != null && o.Cursor != null);
var cursorOrder = ordersWithCursor.MaxByOrDefault(o => o.Order.OrderPriority);
if (cursorOrder != null)
return cursorOrder.Cursor;
var cursorOrder = ordersWithCursor.MaxByOrDefault(o => o.Order.OrderPriority);
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any());
}
return cursorOrder != null ? cursorOrder.Cursor : (useSelect ? "select" : "default");
return useSelect ? "select" : "default";
}
public void Deactivate() { }
@@ -89,7 +95,7 @@ namespace OpenRA.Orders
bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; }
// Used for classic mouse orders, determines whether or not action at xy is move or select
public virtual bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAtMouse(xy)
.Where(a => !a.Actor.IsDead)
@@ -101,22 +107,19 @@ namespace OpenRA.Orders
var target = Target.FromActor(actor);
var cell = world.Map.CellContaining(target.CenterPosition);
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
var underCursor = world.Selection.Actors
.Select(a => new ActorBoundsPair(a, a.MouseBounds(wr)))
.WithHighestSelectionPriority(xy, mi.Modifiers);
var o = OrderForUnit(underCursor, target, actorsAt, cell, mi);
if (o != null)
var modifiers = TargetModifiers.None;
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
modifiers |= TargetModifiers.ForceAttack;
if (mi.Modifiers.HasModifier(Modifiers.Shift))
modifiers |= TargetModifiers.ForceQueue;
if (mi.Modifiers.HasModifier(Modifiers.Alt))
modifiers |= TargetModifiers.ForceMove;
foreach (var a in world.Selection.Actors)
{
var modifiers = TargetModifiers.None;
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
modifiers |= TargetModifiers.ForceAttack;
if (mi.Modifiers.HasModifier(Modifiers.Shift))
modifiers |= TargetModifiers.ForceQueue;
if (mi.Modifiers.HasModifier(Modifiers.Alt))
modifiers |= TargetModifiers.ForceMove;
if (o.Order.TargetOverridesSelection(modifiers))
var o = OrderForUnit(a, target, actorsAt, cell, mi);
if (o != null && o.Order.TargetOverridesSelection(a, target, actorsAt, cell, modifiers))
return true;
}

View File

@@ -18,6 +18,8 @@ namespace OpenRA
{
public enum PlatformType { Unknown, Windows, OSX, Linux }
public enum SupportDirType { System, ModernUser, LegacyUser, User }
public static class Platform
{
public const string SupportDirPrefix = "^";
@@ -26,6 +28,12 @@ namespace OpenRA
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);
static bool supportDirInitialized;
static string systemSupportPath;
static string legacyUserSupportPath;
static string modernUserSupportPath;
static string userSupportPath;
static PlatformType GetCurrentPlatform()
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
@@ -66,18 +74,91 @@ namespace OpenRA
/// <summary>
/// Directory containing user-specific support files (settings, maps, replays, game data, etc).
/// The directory will automatically be created if it does not exist when this is queried.
/// </summary>
public static string SupportDir { get { return supportDir.Value; } }
static Lazy<string> supportDir = Exts.Lazy(GetSupportDir);
static string supportDirOverride;
public static string SupportDir { get { return GetSupportDir(SupportDirType.User); } }
public static string GetSupportDir(SupportDirType type)
{
if (!supportDirInitialized)
InitializeSupportDir();
switch (type)
{
case SupportDirType.System: return systemSupportPath;
case SupportDirType.LegacyUser: return legacyUserSupportPath;
case SupportDirType.ModernUser: return modernUserSupportPath;
default: return userSupportPath;
}
}
static void InitializeSupportDir()
{
// The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions
switch (CurrentPlatform)
{
case PlatformType.Windows:
{
modernUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA");
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA");
systemSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
break;
}
case PlatformType.OSX:
{
modernUserSupportPath = legacyUserSupportPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"Library", "Application Support", "OpenRA");
systemSupportPath = "/Library/Application Support/OpenRA/";
break;
}
case PlatformType.Linux:
{
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
if (string.IsNullOrEmpty(xdgConfigHome))
xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config");
modernUserSupportPath = Path.Combine(xdgConfigHome, "openra");
systemSupportPath = "/var/games/openra/";
break;
}
default:
{
modernUserSupportPath = legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
systemSupportPath = "/var/games/openra/";
break;
}
}
// Use a local directory in the game root if it exists (shared with the system support dir)
var localSupportDir = Path.Combine(GameDir, "Support");
if (Directory.Exists(localSupportDir))
userSupportPath = systemSupportPath = localSupportDir + Path.DirectorySeparatorChar;
// Use the fallback directory if it exists and the preferred one does not
else if (!Directory.Exists(modernUserSupportPath) && Directory.Exists(legacyUserSupportPath))
userSupportPath = legacyUserSupportPath + Path.DirectorySeparatorChar;
else
userSupportPath = modernUserSupportPath + Path.DirectorySeparatorChar;
supportDirInitialized = true;
}
/// <summary>
/// Specify a custom support directory that already exists on the filesystem.
/// MUST be called before Platform.SupportDir is first accessed.
/// Cannot be called after Platform.SupportDir / GetSupportDir have been accessed.
/// </summary>
public static void OverrideSupportDir(string path)
{
if (supportDirInitialized)
throw new InvalidOperationException("Attempted to override user support directory after it has already been accessed.");
if (!Directory.Exists(path))
throw new DirectoryNotFoundException(path);
@@ -85,92 +166,8 @@ namespace OpenRA
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
path += Path.DirectorySeparatorChar;
supportDirOverride = path;
}
static string GetSupportDir()
{
// Use the custom override if it has been defined
if (supportDirOverride != null)
return supportDirOverride;
// Use a local directory in the game root if it exists (shared with the system support dir)
var localSupportDir = Path.Combine(GameDir, "Support");
if (Directory.Exists(localSupportDir))
return localSupportDir + Path.DirectorySeparatorChar;
// The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions
string preferredSupportDir;
string fallbackSupportDir;
switch (CurrentPlatform)
{
case PlatformType.Windows:
{
preferredSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA");
fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA");
break;
}
case PlatformType.OSX:
{
preferredSupportDir = fallbackSupportDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"Library", "Application Support", "OpenRA");
break;
}
case PlatformType.Linux:
{
fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
if (string.IsNullOrEmpty(xdgConfigHome))
xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config");
preferredSupportDir = Path.Combine(xdgConfigHome, "openra");
break;
}
default:
{
preferredSupportDir = fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
break;
}
}
// Use the fallback directory if it exists and the preferred one does not
if (!Directory.Exists(preferredSupportDir) && Directory.Exists(fallbackSupportDir))
return fallbackSupportDir + Path.DirectorySeparatorChar;
return preferredSupportDir + Path.DirectorySeparatorChar;
}
/// <summary>
/// Directory containing system-wide support files (mod metadata).
/// This directory is not guaranteed to exist or be writable.
/// Consumers are expected to check the validity of the returned value, and
/// fall back to the user support directory if necessary.
/// </summary>
public static string SystemSupportDir { get { return systemSupportDir.Value; } }
static Lazy<string> systemSupportDir = Exts.Lazy(GetSystemSupportDir);
static string GetSystemSupportDir()
{
// Use a local directory in the game root if it exists (shared with the system support dir)
var localSupportDir = Path.Combine(GameDir, "Support");
if (Directory.Exists(localSupportDir))
return localSupportDir + Path.DirectorySeparatorChar;
switch (CurrentPlatform)
{
case PlatformType.Windows:
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
case PlatformType.OSX:
return "/Library/Application Support/OpenRA/";
default:
return "/var/games/openra/";
}
InitializeSupportDir();
userSupportPath = path;
}
public static string GameDir

View File

@@ -32,6 +32,13 @@ namespace OpenRA.Server
ShuttingDown = 3
}
public enum ServerType
{
Local = 0,
Multiplayer = 1,
Dedicated = 2
}
public class Server
{
public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match.";
@@ -39,7 +46,7 @@ namespace OpenRA.Server
public readonly IPAddress Ip;
public readonly int Port;
public readonly MersenneTwister Random = new MersenneTwister();
public readonly bool Dedicated;
public readonly ServerType Type;
// Valid player connections
public List<Connection> Conns = new List<Connection>();
@@ -122,7 +129,7 @@ namespace OpenRA.Server
t.GameEnded(this);
}
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, bool dedicated)
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, ServerType type)
{
Log.AddChannel("server", "server.log", true);
@@ -131,7 +138,7 @@ namespace OpenRA.Server
var localEndpoint = (IPEndPoint)listener.LocalEndpoint;
Ip = localEndpoint.Address;
Port = localEndpoint.Port;
Dedicated = dedicated;
Type = type;
Settings = settings;
Settings.Name = OpenRA.Settings.SanitizedServerName(Settings.Name);
@@ -157,10 +164,10 @@ namespace OpenRA.Server
RandomSeed = randomSeed,
Map = settings.Map,
ServerName = settings.Name,
EnableSingleplayer = settings.EnableSingleplayer || !dedicated,
EnableSingleplayer = settings.EnableSingleplayer || Type != ServerType.Dedicated,
EnableSyncReports = settings.EnableSyncReports,
GameUid = Guid.NewGuid().ToString(),
Dedicated = dedicated
Dedicated = Type == ServerType.Dedicated
}
};
@@ -216,7 +223,7 @@ namespace OpenRA.Server
delayedActions.PerformActions(0);
// PERF: Dedicated servers need to drain the action queue to remove references blocking the GC from cleaning up disposed objects.
if (dedicated)
if (Type == ServerType.Dedicated)
Game.PerformDelayedActions();
foreach (var t in serverTraits.WithInterface<ITick>())
@@ -434,7 +441,7 @@ namespace OpenRA.Server
// Send initial ping
SendOrderTo(newConn, "Ping", Game.RunTime.ToString(CultureInfo.InvariantCulture));
if (Dedicated)
if (Type == ServerType.Dedicated)
{
var motdFile = Platform.ResolvePath(Platform.SupportDirPrefix, "motd.txt");
if (!File.Exists(motdFile))
@@ -454,7 +461,13 @@ namespace OpenRA.Server
SendOrderTo(newConn, "Message", "Bots have been disabled on this map.");
};
if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature))
if (Type == ServerType.Local)
{
// Local servers can only be joined by the local client, so we can trust their identity without validation
client.Fingerprint = handshake.Fingerprint;
completeConnection();
}
else if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature))
{
waitingForAuthenticationCallback++;
@@ -502,9 +515,9 @@ namespace OpenRA.Server
delayedActions.Add(() =>
{
var notAuthenticated = Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any());
var blacklisted = Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID);
var notWhitelisted = Dedicated && Settings.ProfileIDWhitelist.Any() &&
var notAuthenticated = Type == ServerType.Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any());
var blacklisted = Type == ServerType.Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID);
var notWhitelisted = Type == ServerType.Dedicated && Settings.ProfileIDWhitelist.Any() &&
(profile == null || !Settings.ProfileIDWhitelist.Contains(profile.ProfileID));
if (notAuthenticated)
@@ -534,7 +547,7 @@ namespace OpenRA.Server
}
else
{
if (Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()))
if (Type == ServerType.Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()))
{
Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint);
SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account");
@@ -616,7 +629,7 @@ namespace OpenRA.Server
{
DispatchOrdersToClients(conn, 0, Order.FromTargetString("Message", text, true).Serialize());
if (Dedicated)
if (Type == ServerType.Dedicated)
Console.WriteLine("[{0}] {1}".F(DateTime.Now.ToString(Settings.TimestampFormat), text));
}
@@ -731,7 +744,7 @@ namespace OpenRA.Server
case "LoadGameSave":
{
if (Dedicated || State >= ServerState.GameStarted)
if (Type == ServerType.Dedicated || State >= ServerState.GameStarted)
break;
// Sanitize potentially malicious input
@@ -834,7 +847,7 @@ namespace OpenRA.Server
// Client was the server admin
// TODO: Reassign admin for game in progress via an order
if (Dedicated && dropClient.IsAdmin && State == ServerState.WaitingPlayers)
if (Type == ServerType.Dedicated && dropClient.IsAdmin && State == ServerState.WaitingPlayers)
{
// Remove any bots controlled by the admin
LobbyInfo.Clients.RemoveAll(c => c.Bot != null && c.BotControllerClientIndex == toDrop.PlayerIndex);
@@ -856,10 +869,10 @@ namespace OpenRA.Server
foreach (var t in serverTraits.WithInterface<INotifyServerEmpty>())
t.ServerEmpty(this);
if (Conns.Any() || Dedicated)
if (Conns.Any() || Type == ServerType.Dedicated)
SyncLobbyClients();
if (!Dedicated && dropClient.IsAdmin)
if (Type != ServerType.Dedicated && dropClient.IsAdmin)
Shutdown();
}
@@ -952,7 +965,7 @@ namespace OpenRA.Server
// Enable game saves for singleplayer missions only
// TODO: Enable for multiplayer (non-dedicated servers only) once the lobby UI has been created
LobbyInfo.GlobalSettings.GameSavesEnabled = !Dedicated && LobbyInfo.NonBotClients.Count() == 1;
LobbyInfo.GlobalSettings.GameSavesEnabled = Type != ServerType.Dedicated && LobbyInfo.NonBotClients.Count() == 1;
SyncLobbyInfo();
State = ServerState.GameStarted;

View File

@@ -202,7 +202,7 @@ namespace OpenRA
public MouseScrollType MiddleMouseScroll = MouseScrollType.Standard;
public MouseScrollType RightMouseScroll = MouseScrollType.Disabled;
public MouseButtonPreference MouseButtonPreference = new MouseButtonPreference();
public float ViewportEdgeScrollStep = 10f;
public float ViewportEdgeScrollStep = 30f;
public float UIScrollSpeed = 50f;
public int SelectionDeadzone = 24;
public int MouseScrollDeadzone = 8;

View File

@@ -45,10 +45,12 @@ namespace OpenRA
ISound video;
MusicInfo currentMusic;
Dictionary<uint, ISound> currentSounds = new Dictionary<uint, ISound>();
public bool DummyEngine { get; private set; }
public Sound(IPlatform platform, SoundSettings soundSettings)
{
soundEngine = platform.CreateSound(soundSettings.Device);
DummyEngine = soundEngine.Dummy;
if (soundSettings.Mute)
MuteAudio();

View File

@@ -20,6 +20,7 @@ namespace OpenRA
ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate);
ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume);
ISound Play2DStream(Stream stream, int channels, int sampleBits, int sampleRate, bool loop, bool relative, WPos pos, float volume);
bool Dummy { get; }
float Volume { get; set; }
void PauseSound(ISound sound, bool paused);
void StopSound(ISound sound);

View File

@@ -186,20 +186,22 @@ namespace OpenRA.Traits
Hash += 1;
}
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist range, int maxHeightDelta = -1)
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist minRange, WDist maxRange, int maxHeightDelta = -1)
{
// Account for potential extra half-cell from odd-height terrain
var r = (range.Length + 1023 + 512) / 1024;
var limit = range.LengthSquared;
var r = (maxRange.Length + 1023 + 512) / 1024;
var minLimit = minRange.LengthSquared;
var maxLimit = maxRange.LengthSquared;
// Project actor position into the shroud plane
var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
var projectedCell = map.CellContaining(projectedPos);
var projectedHeight = pos.Z / 512;
foreach (var c in map.FindTilesInCircle(projectedCell, r, true))
foreach (var c in map.FindTilesInAnnulus(projectedCell, minRange.Length / 1024, r, true))
{
if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit)
var dist = (map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared;
if (dist <= maxLimit && (dist == 0 || dist > minLimit))
{
var puv = (PPos)c.ToMPos(map);
if (maxHeightDelta < 0 || map.ProjectedHeight(puv) < projectedHeight + maxHeightDelta)
@@ -210,7 +212,7 @@ namespace OpenRA.Traits
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, CPos cell, WDist range, int maxHeightDelta = -1)
{
return ProjectedCellsInRange(map, map.CenterOfCell(cell), range, maxHeightDelta);
return ProjectedCellsInRange(map, map.CenterOfCell(cell), WDist.Zero, range, maxHeightDelta);
}
public void AddSource(object key, SourceType type, PPos[] projectedCells)

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using OpenRA.Activities;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Network;
@@ -172,7 +173,7 @@ namespace OpenRA.Traits
int OrderPriority { get; }
bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor);
bool IsQueued { get; }
bool TargetOverridesSelection(TargetModifiers modifiers);
bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers);
}
public interface IResolveOrder { void ResolveOrder(Actor self, Order order); }
@@ -260,6 +261,7 @@ namespace OpenRA.Traits
WDist LargestActorRadius { get; }
WDist LargestBlockingActorRadius { get; }
void UpdateOccupiedCells(IOccupySpace ios);
event Action<CPos> CellUpdated;
}
@@ -312,7 +314,7 @@ namespace OpenRA.Traits
public interface IPositionableInfo : IOccupySpaceInfo
{
bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true);
bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true);
}
public interface IPositionable : IOccupySpace
@@ -527,4 +529,7 @@ namespace OpenRA.Traits
[RequireExplicitImplementation]
public interface IUnlocksRenderPlayer { bool RenderPlayerUnlocked { get; } }
[RequireExplicitImplementation]
public interface ICreationActivity { Activity GetCreationActivity(); }
}

View File

@@ -329,10 +329,10 @@ namespace OpenRA
public Actor CreateActor(bool addToWorld, string name, TypeDictionary initDict)
{
var a = new Actor(this, name, initDict);
foreach (var t in a.TraitsImplementing<INotifyCreated>())
t.Created(a);
a.Created();
if (addToWorld)
Add(a);
return a;
}

View File

@@ -156,7 +156,8 @@ namespace OpenRA.Mods.Cnc.Activities
if (newStance > oldStance || forceAttack)
return;
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
target = Target.Invalid;
}

View File

@@ -72,7 +72,7 @@ namespace OpenRA.Mods.Cnc.Traits
leapToken = conditionManager.RevokeCondition(self, leapToken);
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
{
return new LeapAttack(self, newTarget, allowMove, forceAttack, this, info, targetLineColor);
}

View File

@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Cnc.Traits
public AttackTDGunboatTurreted(Actor self, AttackTDGunboatTurretedInfo info)
: base(self, info) { }
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
{
return new AttackTDGunboatTurretedActivity(self, newTarget, allowMove, forceAttack, targetLineColor);
}

View File

@@ -78,7 +78,7 @@ namespace OpenRA.Mods.Cnc.Traits
void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
{
return new ChargeAttack(this, newTarget, forceAttack, targetLineColor);
}

View File

@@ -238,7 +238,7 @@ namespace OpenRA.Mods.Cnc.Traits
{
public string OrderID { get { return "BeginMinefield"; } }
public int OrderPriority { get { return 5; } }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
{

View File

@@ -159,7 +159,7 @@ namespace OpenRA.Mods.Cnc.Traits
public string OrderID { get { return "PortableChronoTeleport"; } }
public int OrderPriority { get { return 5; } }
public bool IsQueued { get; protected set; }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
{

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits
{
base.Activate(self, order, manager);
attack.AttackTarget(order.Target, false, false, true);
attack.AttackTarget(order.Target, AttackSource.Default, false, false, true);
}
protected override void Created(Actor self)

View File

@@ -313,8 +313,13 @@ namespace OpenRA.Mods.Cnc.Traits
bool IsValidTarget(CPos xy)
{
// Don't teleport if there are no units in range (either all moved out of range, or none yet moved into range)
var unitsInRange = power.UnitsInRange(sourceLocation);
if (!unitsInRange.Any())
return false;
var canTeleport = false;
foreach (var unit in power.UnitsInRange(sourceLocation))
foreach (var unit in unitsInRange)
{
var targetCell = unit.Location + (xy - sourceLocation);
if (manager.Self.Owner.Shroud.IsExplored(targetCell) && unit.Trait<Chronoshiftable>().CanChronoshiftTo(unit, targetCell))

View File

@@ -47,12 +47,9 @@ namespace OpenRA.Mods.Cnc.Traits
bool IOccupySpaceInfo.SharesCell { get { return false; } }
// Used to determine if actor can spawn
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = false)
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = false)
{
if (!world.Map.Contains(cell))
return false;
return true;
return world.Map.Contains(cell);
}
}
@@ -191,7 +188,7 @@ namespace OpenRA.Mods.Cnc.Traits
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveIntoWorld(Actor self, int delay = 0) { return null; }
public Activity ReturnToCell(Actor self) { return null; }
public Activity MoveToTarget(Actor self, Target target,
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
public Activity MoveIntoTarget(Actor self, Target target) { return null; }

View File

@@ -129,7 +129,7 @@ namespace OpenRA.Mods.Common.Activities
if (aircraft.Info.CanHover && !skipHeightAdjustment && dat != aircraft.Info.CruiseAltitude)
{
if (dat <= aircraft.LandAltitude)
QueueChild(new TakeOff(self, target));
QueueChild(new TakeOff(self));
else
VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
@@ -140,7 +140,7 @@ namespace OpenRA.Mods.Common.Activities
}
else if (dat <= aircraft.LandAltitude)
{
QueueChild(new TakeOff(self, target));
QueueChild(new TakeOff(self));
return false;
}
@@ -158,7 +158,6 @@ namespace OpenRA.Mods.Common.Activities
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
var pos = aircraft.GetPosition();
var delta = checkTarget.CenterPosition - pos;
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
// Inside the target annulus, so we're done
var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(pos, maxRange);
@@ -167,12 +166,20 @@ namespace OpenRA.Mods.Common.Activities
return true;
var isSlider = aircraft.Info.CanSlide;
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing);
// Inside the minimum range, so reverse if we CanSlide
if (isSlider && insideMinRange)
// Inside the minimum range, so reverse if we CanSlide, otherwise face away from the target.
if (insideMinRange)
{
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
if (isSlider)
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
else
{
desiredFacing = Util.NormalizeFacing(desiredFacing + 128);
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, move);
}
return false;
}

View File

@@ -23,8 +23,10 @@ namespace OpenRA.Mods.Common.Activities
readonly Aircraft aircraft;
readonly AttackAircraft attackAircraft;
readonly Rearmable rearmable;
readonly AttackSource source;
readonly bool forceAttack;
readonly Color? targetLineColor;
readonly WDist strafeDistance;
Target target;
Target lastVisibleTarget;
@@ -34,10 +36,10 @@ namespace OpenRA.Mods.Common.Activities
bool useLastVisibleTarget;
bool hasTicked;
bool returnToBase;
int remainingTicksUntilTurn;
public FlyAttack(Actor self, Target target, bool forceAttack, Color? targetLineColor)
public FlyAttack(Actor self, AttackSource source, Target target, bool forceAttack, Color? targetLineColor)
{
this.source = source;
this.target = target;
this.forceAttack = forceAttack;
this.targetLineColor = targetLineColor;
@@ -46,6 +48,8 @@ namespace OpenRA.Mods.Common.Activities
attackAircraft = self.Trait<AttackAircraft>();
rearmable = self.TraitOrDefault<Rearmable>();
strafeDistance = attackAircraft.Info.StrafeRunLength;
// The target may become hidden between the initial order request and the first tick (e.g. if queued)
// Moving to any position (even if quite stale) is still better than immediately giving up
if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner))
@@ -99,6 +103,17 @@ namespace OpenRA.Mods.Common.Activities
lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes();
}
// The target may become hidden in the same tick the FlyAttack constructor is called,
// causing lastVisible* to remain uninitialized.
// Fix the fallback values based on the frozen actor properties
else if (target.Type == TargetType.FrozenActor && !lastVisibleTarget.IsValidFor(self))
{
lastVisibleTarget = Target.FromTargetPositions(target);
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
lastVisibleOwner = target.FrozenActor.Owner;
lastVisibleTargetTypes = target.FrozenActor.TargetTypes;
}
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
// Target is hidden or dead, and we don't have a fallback position to move towards
@@ -109,6 +124,14 @@ namespace OpenRA.Mods.Common.Activities
// and resume the activity after reloading if AbortOnResupply is set to 'false'
if (rearmable != null && !useLastVisibleTarget && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
{
// Attack moves never resupply
if (source == AttackSource.AttackMove)
return true;
// AbortOnResupply cancels the current activity (after resupplying) plus any queued activities
if (attackAircraft.Info.AbortOnResupply && NextActivity != null)
NextActivity.Cancel(self);
QueueChild(new ReturnToBase(self));
returnToBase = true;
return attackAircraft.Info.AbortOnResupply;
@@ -136,23 +159,15 @@ namespace OpenRA.Mods.Common.Activities
var minimumRange = attackAircraft.Info.AttackType == AirAttackType.Strafe ? WDist.Zero : attackAircraft.GetMinimumRangeVersusTarget(target);
// When strafing we must move forward for a minimum number of ticks after passing the target.
if (remainingTicksUntilTurn > 0)
{
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
remainingTicksUntilTurn--;
}
// Move into range of the target.
else if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
QueueChild(aircraft.MoveWithinRange(target, minimumRange, lastVisibleMaximumRange, target.CenterPosition, Color.Red));
// The aircraft must keep moving forward even if it is already in an ideal position.
else if (!aircraft.Info.CanHover || attackAircraft.Info.AttackType == AirAttackType.Strafe)
{
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
remainingTicksUntilTurn = attackAircraft.Info.AttackTurnDelay;
}
else if (attackAircraft.Info.AttackType == AirAttackType.Strafe)
QueueChild(new StrafeAttackRun(self, attackAircraft, target, strafeDistance != WDist.Zero ? strafeDistance : lastVisibleMaximumRange));
else if (attackAircraft.Info.AttackType == AirAttackType.Default && !aircraft.Info.CanHover)
QueueChild(new FlyAttackRun(self, target, lastVisibleMaximumRange));
// Turn to face the target if required.
else if (!attackAircraft.TargetInFiringArc(self, target, attackAircraft.Info.FacingTolerance))
@@ -173,7 +188,8 @@ namespace OpenRA.Mods.Common.Activities
if (newStance > oldStance || forceAttack)
return;
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
attackAircraft.ClearRequestedTarget();
}
@@ -189,4 +205,79 @@ namespace OpenRA.Mods.Common.Activities
}
}
}
class FlyAttackRun : Activity
{
Target target;
WDist exitRange;
bool targetIsVisibleActor;
public FlyAttackRun(Actor self, Target t, WDist exitRange)
{
ChildHasPriority = false;
target = t;
this.exitRange = exitRange;
}
protected override void OnFirstRun(Actor self)
{
QueueChild(new Fly(self, target, target.CenterPosition));
QueueChild(new Fly(self, target, exitRange, WDist.MaxValue, target.CenterPosition));
}
public override bool Tick(Actor self)
{
if (TickChild(self) || IsCanceling)
return true;
// Cancel the run if the target become invalid (e.g. killed) while visible
var targetWasVisibleActor = targetIsVisibleActor;
bool targetIsHiddenActor;
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
targetIsVisibleActor = target.Type == TargetType.Actor && !targetIsHiddenActor;
if (targetWasVisibleActor && !target.IsValidFor(self))
Cancel(self);
return false;
}
}
class StrafeAttackRun : Activity
{
Target target;
WDist exitRange;
readonly AttackAircraft attackAircraft;
public StrafeAttackRun(Actor self, AttackAircraft attackAircraft, Target t, WDist exitRange)
{
ChildHasPriority = false;
target = t;
this.attackAircraft = attackAircraft;
this.exitRange = exitRange;
}
protected override void OnFirstRun(Actor self)
{
QueueChild(new Fly(self, target, target.CenterPosition));
QueueChild(new Fly(self, target, exitRange, WDist.MaxValue, target.CenterPosition));
}
public override bool Tick(Actor self)
{
if (TickChild(self) || IsCanceling)
return true;
// Strafe attacks target the ground below the original target
// Update the position if we seen the target move; keep the previous one if it dies or disappears
bool targetIsHiddenActor;
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
attackAircraft.SetRequestedTarget(self, Target.FromTargetPositions(target), true);
return false;
}
}
}

View File

@@ -21,19 +21,13 @@ namespace OpenRA.Mods.Common.Activities
{
readonly Aircraft aircraft;
readonly IMove move;
Target fallbackTarget;
bool movedToTarget = false;
public TakeOff(Actor self, Target fallbackTarget)
public TakeOff(Actor self)
{
aircraft = self.Trait<Aircraft>();
move = self.Trait<IMove>();
this.fallbackTarget = fallbackTarget;
}
public TakeOff(Actor self)
: this(self, Target.Invalid) { }
protected override void OnFirstRun(Actor self)
{
if (aircraft.ForceLanding)
@@ -42,8 +36,7 @@ namespace OpenRA.Mods.Common.Activities
if (self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length >= aircraft.Info.MinAirborneAltitude)
return;
// We are taking off, so remove reservation and influence in ground cells.
aircraft.UnReserve();
// We are taking off, so remove influence in ground cells.
aircraft.RemoveInfluence();
if (aircraft.Info.TakeoffSounds.Length > 0)
@@ -73,24 +66,7 @@ namespace OpenRA.Mods.Common.Activities
return false;
}
// Only move to the fallback target if we don't have anything better to do
if (NextActivity == null && fallbackTarget.IsValidFor(self) && !movedToTarget)
{
QueueChild(new AttackMoveActivity(self, () => move.MoveToTarget(self, fallbackTarget, targetLineColor: Color.OrangeRed)));
movedToTarget = true;
return false;
}
return true;
}
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
{
if (ChildActivity != null)
foreach (var n in ChildActivity.TargetLineNodes(self))
yield return n;
else
yield return new TargetLineNode(fallbackTarget, Color.OrangeRed);
}
}
}

View File

@@ -225,7 +225,8 @@ namespace OpenRA.Mods.Common.Activities
if (newStance > oldStance || forceAttack)
return;
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
target = Target.Invalid;
}

View File

@@ -97,8 +97,14 @@ namespace OpenRA.Mods.Common.Activities
// Put back into world
self.World.AddFrameEndTask(w =>
{
if (self.IsDead)
return;
var cargo = carryall.Carryable;
var carryable = carryall.Carryable.Trait<Carryable>();
if (cargo == null)
return;
var carryable = cargo.Trait<Carryable>();
w.Add(cargo);
carryall.DetachCarryable(self);
carryable.UnReserve(cargo);

View File

@@ -138,7 +138,7 @@ namespace OpenRA.Mods.Common.Activities
case EnterState.Exiting:
{
QueueChild(move.MoveIntoWorld(self));
QueueChild(move.ReturnToCell(self));
lastState = EnterState.Finished;
return false;
}

View File

@@ -214,7 +214,7 @@ namespace OpenRA.Mods.Common.Activities
if (b != WVec.Zero && c != WVec.Zero)
{
var cosA = (int)(1024 * (b.LengthSquared + c.LengthSquared - a.LengthSquared) / (2 * b.Length * c.Length));
var cosA = (int)(512 * (b.LengthSquared + c.LengthSquared - a.LengthSquared) / b.Length / c.Length);
// Cost modifier varies between 0 and ResourceRefineryDirectionPenalty
return Math.Abs(harvInfo.ResourceRefineryDirectionPenalty / 2) + harvInfo.ResourceRefineryDirectionPenalty * cosA / 2048;
@@ -245,7 +245,7 @@ namespace OpenRA.Mods.Common.Activities
yield return n;
if (orderLocation != null)
yield return new TargetLineNode(Target.FromCell(self.World, orderLocation.Value), Color.Green);
yield return new TargetLineNode(Target.FromCell(self.World, orderLocation.Value), Color.Crimson);
else if (deliverActor != null)
yield return new TargetLineNode(Target.FromActor(deliverActor), Color.Green);
}

View File

@@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Activities
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
{
yield return new TargetLineNode(Target.FromCell(self.World, targetCell), Color.Green);
yield return new TargetLineNode(Target.FromCell(self.World, targetCell), Color.Crimson);
}
}
}

View File

@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Activities
ChildActivity.Cancel(self);
var attackBases = autoTarget.ActiveAttackBases;
foreach (var ab in attackBases)
QueueChild(ab.GetAttackActivity(self, target, false, false));
QueueChild(ab.GetAttackActivity(self, AttackSource.AttackMove, target, false, false));
// Make sure to continue moving when the attack activities have finished.
QueueChild(getInner());

View File

@@ -208,6 +208,13 @@ namespace OpenRA.Mods.Common.Activities
var nextCell = path[path.Count - 1];
// Something else might have moved us, so the path is no longer valid.
if (!Util.AreAdjacentCells(mobile.ToCell, nextCell))
{
path = EvalPath();
return null;
}
var containsTemporaryBlocker = WorldUtils.ContainsTemporaryBlocker(self.World, nextCell, self);
// Next cell in the move is blocked by another actor
@@ -331,9 +338,6 @@ namespace OpenRA.Mods.Common.Activities
public override bool Tick(Actor self)
{
if (Move.mobile.IsTraitDisabled)
return false;
var ret = InnerTick(self, Move.mobile);
if (moveFraction > MoveFractionTotal)
@@ -412,7 +416,7 @@ namespace OpenRA.Mods.Common.Activities
var nextCell = parent.PopPath(self);
if (nextCell != null)
{
if (IsTurn(mobile, nextCell.Value.First))
if (!mobile.IsTraitPaused && !mobile.IsTraitDisabled && IsTurn(mobile, nextCell.Value.First))
{
var nextSubcellOffset = map.Grid.OffsetOfSubCell(nextCell.Value.Second);
var ret = new MoveFirstHalf(

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
@@ -20,20 +21,18 @@ namespace OpenRA.Mods.Common.Activities
public class PickupUnit : Activity
{
readonly Actor cargo;
readonly IMove movement;
readonly Carryall carryall;
readonly IFacing carryallFacing;
readonly Carryable carryable;
readonly IFacing carryableFacing;
readonly BodyOrientation carryableBody;
readonly int delay;
enum PickupState { Intercept, LockCarryable, Pickup }
// TODO: Expose this to yaml
readonly WDist targetLockRange = WDist.FromCells(4);
PickupState state;
enum PickupState { Intercept, LockCarryable, Pickup }
PickupState state = PickupState.Intercept;
public PickupUnit(Actor self, Actor cargo, int delay)
{
@@ -43,16 +42,20 @@ namespace OpenRA.Mods.Common.Activities
carryableFacing = cargo.Trait<IFacing>();
carryableBody = cargo.Trait<BodyOrientation>();
movement = self.Trait<IMove>();
carryall = self.Trait<Carryall>();
carryallFacing = self.Trait<IFacing>();
state = PickupState.Intercept;
ChildHasPriority = false;
}
protected override void OnFirstRun(Actor self)
{
carryall.ReserveCarryable(self, cargo);
if (carryall.ReserveCarryable(self, cargo))
{
// Fly to the target and wait for it to be locked for pickup
// These activities will be cancelled and replaced by Land once the target has been locked
QueueChild(new Fly(self, Target.FromActor(cargo)));
QueueChild(new FlyCircle(self));
}
}
public override bool Tick(Actor self)
@@ -74,26 +77,21 @@ namespace OpenRA.Mods.Common.Activities
return true;
}
if (carryall.State != Carryall.CarryallState.Reserved)
return true;
// Wait until we are near the target before we try to lock it
var distSq = (cargo.CenterPosition - self.CenterPosition).HorizontalLengthSquared;
if (state == PickupState.Intercept && distSq <= targetLockRange.LengthSquared)
state = PickupState.LockCarryable;
switch (state)
if (state == PickupState.LockCarryable)
{
case PickupState.Intercept:
QueueChild(movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4)));
state = PickupState.LockCarryable;
return false;
case PickupState.LockCarryable:
if (!carryable.LockForPickup(cargo, self))
Cancel(self);
state = PickupState.Pickup;
return false;
case PickupState.Pickup:
var lockResponse = carryable.LockForPickup(cargo, self);
if (lockResponse == LockResponse.Failed)
Cancel(self);
else if (lockResponse == LockResponse.Success)
{
// Land at the target location
// Pickup position and facing are now known - swap the fly/wait activity with Land
ChildActivity.Cancel(self);
var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation));
QueueChild(new Land(self, Target.FromActor(cargo), -carryableBody.LocalToWorld(localOffset), carryableFacing.Facing));
@@ -104,11 +102,13 @@ namespace OpenRA.Mods.Common.Activities
// Remove our carryable from world
QueueChild(new AttachUnit(self, cargo));
QueueChild(new TakeOff(self));
return false;
state = PickupState.Pickup;
}
}
return true;
// Return once we are in the pickup state and the pickup activities have completed
return TickChild(self) && state == PickupState.Pickup;
}
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)

View File

@@ -187,10 +187,12 @@ namespace OpenRA.Mods.Common.Activities
{
if (wasRepaired || isHostInvalid || (!stayOnResupplier && aircraft.Info.TakeOffOnResupply))
{
if (rp != null)
QueueChild(move.MoveTo(rp.Location, repairableNear != null ? null : host.Actor));
if (self.CurrentActivity.NextActivity == null && rp != null)
QueueChild(move.MoveTo(rp.Location, repairableNear != null ? null : host.Actor, targetLineColor: Color.Green));
else
QueueChild(new TakeOff(self));
aircraft.UnReserve();
}
// Aircraft without TakeOffOnResupply remain on the resupplier until something else needs it

View File

@@ -53,6 +53,9 @@ namespace OpenRA.Mods.Common.Activities
{
self.World.AddFrameEndTask(w =>
{
if (self.IsDead)
return;
// Make sure the target hasn't changed while entering
// OnEnterComplete is only called if targetActor is alive
if (targetActor != enterActor)

View File

@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Activities
var move = actor.Trait<IMove>();
var pos = actor.Trait<IPositionable>();
pos.SetPosition(self, exitSubCell.Value.First, exitSubCell.Value.Second);
pos.SetPosition(actor, exitSubCell.Value.First, exitSubCell.Value.Second);
pos.SetVisualPosition(actor, spawn);
actor.CancelActivity();

View File

@@ -24,13 +24,13 @@ namespace OpenRA.Mods.Common
public int Value(World world) { return value; }
}
public class MoveIntoWorldDelayInit : IActorInit<int>
public class CreationActivityDelayInit : IActorInit<int>
{
[FieldFromYamlKey]
readonly int value = 0;
public MoveIntoWorldDelayInit() { }
public MoveIntoWorldDelayInit(int init) { value = init; }
public CreationActivityDelayInit() { }
public CreationActivityDelayInit(int init) { value = init; }
public int Value(World world) { return value; }
}

View File

@@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Effects
if (range == WDist.Zero)
return NoCells;
return Shroud.ProjectedCellsInRange(map, pos, range)
return Shroud.ProjectedCellsInRange(map, pos, WDist.Zero, range)
.ToArray();
}

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Orders
public string OrderID { get; private set; }
public int OrderPriority { get; private set; }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
{

View File

@@ -243,6 +243,7 @@ namespace OpenRA.Mods.Common.Orders
var plugInfo = activeVariant.PlugInfo;
var lineBuildInfo = activeVariant.LineBuildInfo;
var preview = activeVariant.Preview;
var owner = queue.Actor.Owner;
if (plugInfo != null)
{
@@ -251,7 +252,7 @@ namespace OpenRA.Mods.Common.Orders
footprint.Add(topLeft, MakeCellType(AcceptsPlug(topLeft, plugInfo)));
}
else if (lineBuildInfo != null)
else if (lineBuildInfo != null && owner.Shroud.IsExplored(topLeft))
{
// Linebuild for walls.
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
@@ -259,7 +260,7 @@ namespace OpenRA.Mods.Common.Orders
if (!Game.GetModifierKeys().HasModifier(Modifiers.Shift))
{
foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, actorInfo, buildingInfo))
foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, actorInfo, buildingInfo, owner))
{
var lineBuildable = world.IsCellBuildable(t.First, actorInfo, buildingInfo);
var lineCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, t.First);

View File

@@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.Orders
public string OrderID { get; private set; }
public int OrderPriority { get; private set; }
public bool? ForceAttack = null;
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public abstract bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor);
public abstract bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor);

View File

@@ -47,9 +47,11 @@ namespace OpenRA.Mods.Common.Scripting
if (entryLocation.HasValue)
{
var pi = ai.TraitInfoOrDefault<AircraftInfo>();
initDict.Add(new CenterPositionInit(owner.World.Map.CenterOfCell(entryLocation.Value) + new WVec(0, 0, pi != null ? pi.CruiseAltitude.Length : 0)));
initDict.Add(new LocationInit(entryLocation.Value));
var pi = ai.TraitInfoOrDefault<AircraftInfo>();
if (pi != null)
initDict.Add(new CenterPositionInit(owner.World.Map.CenterOfCell(entryLocation.Value) + new WVec(0, 0, pi.CruiseAltitude.Length)));
}
if (entryLocation.HasValue && nextLocation.HasValue)

View File

@@ -98,7 +98,7 @@ namespace OpenRA.Mods.Common.Scripting
Log.Write("lua", "{1} is not revealed for player {0}!", Self.Owner, targetActor);
foreach (var attack in attackBases)
attack.AttackTarget(target, true, allowMove, forceAttack);
attack.AttackTarget(target, AttackSource.Default, true, allowMove, forceAttack);
}
[Desc("Checks if the targeted actor is a valid target for this actor.")]

View File

@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Scripting
var pos = Self.CenterPosition;
mobile.SetPosition(Self, cell);
mobile.SetVisualPosition(Self, pos);
Self.QueueActivity(mobile.MoveIntoWorld(Self));
Self.QueueActivity(mobile.ReturnToCell(Self));
}
[ScriptActorPropertyActivity]

View File

@@ -10,6 +10,7 @@
#endregion
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
@@ -35,7 +36,13 @@ namespace OpenRA.Mods.Common.Scripting
public int PassengerCount { get { return cargo.Passengers.Count(); } }
[Desc("Teleport an existing actor inside this transport.")]
public void LoadPassenger(Actor a) { cargo.Load(Self, a); }
public void LoadPassenger(Actor a)
{
if (!a.IsIdle)
throw new LuaException("LoadPassenger requires the passenger to be idle.");
cargo.Load(Self, a);
}
[Desc("Remove the first actor from the transport. This actor is not added to the world.")]
public Actor UnloadPassenger() { return cargo.Unload(Self); }

View File

@@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Server
lastPing = Game.RunTime;
// Ignore client timeout in singleplayer games to make debugging easier
if (server.LobbyInfo.NonBotClients.Count() < 2 && !server.Dedicated)
if (server.LobbyInfo.NonBotClients.Count() < 2 && server.Type != ServerType.Dedicated)
foreach (var c in server.Conns.ToList())
server.SendOrderTo(c, "Ping", Game.RunTime.ToString());
else

View File

@@ -42,7 +42,20 @@ namespace OpenRA.Mods.Common
// Bot-controlled units aren't yet capable of understanding visibility changes
if (viewer.IsBot)
{
// Prevent that bot-controlled units endlessly fire at frozen actors.
// TODO: Teach the AI to support long range artillery units with units that provide line of sight
if (t.Type == TargetType.FrozenActor)
{
if (t.FrozenActor.Actor != null)
return Target.FromActor(t.FrozenActor.Actor);
// Original actor was killed
return Target.Invalid;
}
return t;
}
if (t.Type == TargetType.Actor)
{
@@ -68,8 +81,7 @@ namespace OpenRA.Mods.Common
return Target.FromActor(t.FrozenActor.Actor);
// Original actor was killed while hidden
if (t.Actor == null)
return Target.Invalid;
return Target.Invalid;
}
}

View File

@@ -17,6 +17,8 @@ namespace OpenRA.Mods.Common.Traits
{
public abstract class AffectsShroudInfo : ConditionalTraitInfo
{
public readonly WDist MinRange = WDist.Zero;
public readonly WDist Range = WDist.Zero;
[Desc("If >= 0, prevent cells that are this much higher than the actor from being revealed.")]
@@ -61,15 +63,16 @@ namespace OpenRA.Mods.Common.Traits
PPos[] ProjectedCells(Actor self)
{
var map = self.World.Map;
var range = Range;
if (range == WDist.Zero)
var minRange = Info.MinRange;
var maxRange = Range;
if (maxRange <= minRange)
return NoCells;
if (Info.Type == VisibilityType.Footprint)
{
// PERF: Reuse collection to avoid allocations.
footprint.UnionWith(self.OccupiesSpace.OccupiedCells()
.SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range, Info.MaxHeightDelta)));
.SelectMany(kv => Shroud.ProjectedCellsInRange(map, map.CenterOfCell(kv.First), minRange, maxRange, Info.MaxHeightDelta)));
var cells = footprint.ToArray();
footprint.Clear();
return cells;
@@ -79,7 +82,7 @@ namespace OpenRA.Mods.Common.Traits
if (Info.Type == VisibilityType.GroundPosition)
pos -= new WVec(WDist.Zero, WDist.Zero, self.World.Map.DistanceAboveTerrain(pos));
return Shroud.ProjectedCellsInRange(map, pos, range, Info.MaxHeightDelta)
return Shroud.ProjectedCellsInRange(map, pos, minRange, maxRange, Info.MaxHeightDelta)
.ToArray();
}

View File

@@ -158,7 +158,7 @@ namespace OpenRA.Mods.Common.Traits
bool IOccupySpaceInfo.SharesCell { get { return false; } }
// Used to determine if an aircraft can spawn landed
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
{
if (!world.Map.Contains(cell))
return false;
@@ -173,6 +173,7 @@ namespace OpenRA.Mods.Common.Traits
if (!checkTransientActors)
return true;
// Since aircraft don't share cells, we don't pass the subCell parameter
return !world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor);
}
@@ -190,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits
public class Aircraft : ITick, ISync, IFacing, IPositionable, IMove, IIssueOrder, IResolveOrder, IOrderVoice, IDeathActorInitModifier,
INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing, INotifyBecomingIdle,
IActorPreviewInitModifier, IIssueDeployOrder, IObservesVariables
IActorPreviewInitModifier, IIssueDeployOrder, IObservesVariables, ICreationActivity
{
static readonly Pair<CPos, SubCell>[] NoCells = { };
@@ -220,7 +221,7 @@ namespace OpenRA.Mods.Common.Traits
IEnumerable<CPos> landingCells = Enumerable.Empty<CPos>();
bool requireForceMove;
int moveIntoWorldDelay;
int creationActivityDelay;
public static WPos GroundPosition(Actor self)
{
@@ -251,7 +252,9 @@ namespace OpenRA.Mods.Common.Traits
SetPosition(self, init.Get<CenterPositionInit, WPos>());
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : Info.InitialFacing;
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
if (init.Contains<CreationActivityDelayInit>())
creationActivityDelay = init.Get<CreationActivityDelayInit, int>();
}
public WDist LandAltitude
@@ -309,8 +312,6 @@ namespace OpenRA.Mods.Common.Traits
notifyMoving = self.TraitsImplementing<INotifyMoving>().ToArray();
positionOffsets = self.TraitsImplementing<IAircraftCenterPositionOffset>().ToArray();
overrideAircraftLanding = self.TraitOrDefault<IOverrideAircraftLanding>();
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
}
void INotifyAddedToWorld.AddedToWorld(Actor self)
@@ -506,26 +507,15 @@ namespace OpenRA.Mods.Common.Traits
MayYieldReservation = true;
}
public void UnReserve(bool takeOff = false)
public void UnReserve()
{
if (reservation == null)
return;
// Move to the host's rally point if it has one
var rp = ReservedActor != null ? ReservedActor.TraitOrDefault<RallyPoint>() : null;
reservation.Dispose();
reservation = null;
ReservedActor = null;
MayYieldReservation = false;
if (takeOff && self.World.Map.DistanceAboveTerrain(CenterPosition).Length <= LandAltitude.Length)
{
if (rp != null)
self.QueueActivity(new TakeOff(self, Target.FromCell(self.World, rp.Location)));
else
self.QueueActivity(new TakeOff(self));
}
}
bool AircraftCanEnter(Actor a, TargetModifiers modifiers)
@@ -858,18 +848,15 @@ namespace OpenRA.Mods.Common.Traits
initialTargetPosition, targetLineColor);
}
public Activity MoveIntoWorld(Actor self, int delay = 0)
{
return new MoveIntoWorldActivity(self, delay);
}
public Activity ReturnToCell(Actor self) { return null; }
class MoveIntoWorldActivity : Activity
class AssociateWithAirfieldActivity : Activity
{
readonly Actor self;
readonly Aircraft aircraft;
readonly int delay;
public MoveIntoWorldActivity(Actor self, int delay = 0)
public AssociateWithAirfieldActivity(Actor self, int delay = 0)
{
this.self = self;
aircraft = self.Trait<Aircraft>();
@@ -1179,9 +1166,15 @@ namespace OpenRA.Mods.Common.Traits
inits.Add(new DynamicFacingInit(() => Facing));
}
Activity ICreationActivity.GetCreationActivity()
{
return new AssociateWithAirfieldActivity(self, creationActivityDelay);
}
public class AircraftMoveOrderTargeter : IOrderTargeter
{
readonly Aircraft aircraft;
readonly BuildingInfluence bi;
public string OrderID { get; protected set; }
public int OrderPriority { get { return 4; } }
@@ -1190,11 +1183,16 @@ namespace OpenRA.Mods.Common.Traits
public AircraftMoveOrderTargeter(Aircraft aircraft)
{
this.aircraft = aircraft;
bi = aircraft.self.World.WorldActor.TraitOrDefault<BuildingInfluence>();
OrderID = "Move";
}
public bool TargetOverridesSelection(TargetModifiers modifiers)
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
{
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
return true;
return modifiers.HasModifier(TargetModifiers.ForceMove);
}
@@ -1203,10 +1201,18 @@ namespace OpenRA.Mods.Common.Traits
if (target.Type != TargetType.Terrain || (aircraft.requireForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove)))
return false;
if (modifiers.HasModifier(TargetModifiers.ForceMove) && aircraft.Info.CanForceLand)
OrderID = "Land";
var location = self.World.Map.CellContaining(target.CenterPosition);
// Aircraft can be force-landed by issuing a force-move order on a clear terrain cell
// Cells that contain a blocking building are treated as regular force move orders, overriding
// selection for left-mouse orders
if (modifiers.HasModifier(TargetModifiers.ForceMove) && aircraft.Info.CanForceLand)
{
var building = bi.GetBuildingAt(location);
if (building == null || building.TraitOrDefault<Selectable>() == null || aircraft.CanLand(location, blockedByMobile: false))
OrderID = "Land";
}
var explored = self.Owner.Shroud.IsExplored(location);
cursor = self.World.Map.Contains(location) ?
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? "move") :

View File

@@ -17,15 +17,18 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
// TODO: Add CurleyShuffle (TD, TS), Circle (Generals Gunship-style)
public enum AirAttackType { Hover, Strafe }
public enum AirAttackType { Default, Hover, Strafe }
public class AttackAircraftInfo : AttackFollowInfo, Requires<AircraftInfo>
{
[Desc("Attack behavior. Currently supported types are Strafe (default) and Hover.")]
public readonly AirAttackType AttackType = AirAttackType.Strafe;
[Desc("Attack behavior. Currently supported types are:",
"Default: Attack while following the default movement rules.",
"Hover: Hover, even if the Aircraft can't hover while idle.",
"Strafe: Perform a fixed-length attack run on the target.")]
public readonly AirAttackType AttackType = AirAttackType.Default;
[Desc("Delay, in game ticks, before strafing aircraft turns to attack.")]
public readonly int AttackTurnDelay = 50;
[Desc("Distance the strafing aircraft makes to a target before turning for another pass. When set to WDist.Zero this defaults to the maximum armament range.")]
public readonly WDist StrafeRunLength = WDist.Zero;
[Desc("Does this actor cancel its attack activity when it needs to resupply? Setting this to 'false' will make the actor resume attack after reloading.")]
public readonly bool AbortOnResupply = true;
@@ -45,9 +48,9 @@ namespace OpenRA.Mods.Common.Traits
aircraftInfo = self.Info.TraitInfo<AircraftInfo>();
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
{
return new FlyAttack(self, newTarget, forceAttack, targetLineColor);
return new FlyAttack(self, source, newTarget, forceAttack, targetLineColor);
}
protected override bool CanAttack(Actor self, Target target)

View File

@@ -86,7 +86,7 @@ namespace OpenRA.Mods.Common.Traits
OnRemovedFromWorld(self);
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
{
throw new NotImplementedException("AttackBomber requires a scripted target");
}

View File

@@ -42,18 +42,24 @@ namespace OpenRA.Mods.Common.Traits
}
}
public class FallsToEarth : IEffectiveOwner
public class FallsToEarth : IEffectiveOwner, INotifyCreated
{
readonly FallsToEarthInfo info;
readonly Player effectiveOwner;
public FallsToEarth(ActorInitializer init, FallsToEarthInfo info)
{
init.Self.QueueActivity(false, new FallToEarth(init.Self, info));
this.info = info;
effectiveOwner = init.Contains<EffectiveOwnerInit>() ? init.Get<EffectiveOwnerInit, Player>() : init.Self.Owner;
}
// We return init.Self.Owner if there's no effective owner
bool IEffectiveOwner.Disguised { get { return true; } }
Player IEffectiveOwner.Owner { get { return effectiveOwner; } }
void INotifyCreated.Created(Actor self)
{
self.QueueActivity(false, new FallToEarth(self, info));
}
}
}

View File

@@ -21,6 +21,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public enum AttackSource { Default, AutoTarget, AttackMove }
public abstract class AttackBaseInfo : PausableConditionalTraitInfo
{
[Desc("Armament names")]
@@ -199,7 +201,7 @@ namespace OpenRA.Mods.Common.Traits
if (!order.Target.IsValidFor(self))
return;
AttackTarget(order.Target, order.Queued, true, forceAttack, Info.TargetLineColor);
AttackTarget(order.Target, AttackSource.Default, order.Queued, true, forceAttack, Info.TargetLineColor);
self.ShowTargetLines();
}
@@ -224,7 +226,7 @@ namespace OpenRA.Mods.Common.Traits
return order.OrderString == attackOrderName || order.OrderString == forceAttackOrderName ? Info.Voice : null;
}
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null);
public abstract Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null);
public bool HasAnyValidWeapons(Target t, bool checkForCenterTargetingWeapons = false)
{
@@ -391,7 +393,7 @@ namespace OpenRA.Mods.Common.Traits
&& a.Weapon.IsValidAgainst(t, self.World, self));
}
public void AttackTarget(Target target, bool queued, bool allowMove, bool forceAttack = false, Color? targetLineColor = null)
public void AttackTarget(Target target, AttackSource source, bool queued, bool allowMove, bool forceAttack = false, Color? targetLineColor = null)
{
if (IsTraitDisabled)
return;
@@ -399,7 +401,7 @@ namespace OpenRA.Mods.Common.Traits
if (!target.IsValidFor(self))
return;
var activity = GetAttackActivity(self, target, allowMove, forceAttack, targetLineColor);
var activity = GetAttackActivity(self, source, target, allowMove, forceAttack, targetLineColor);
self.QueueActivity(queued, activity);
OnResolveAttackOrder(self, activity, target, queued, forceAttack);
}
@@ -436,7 +438,7 @@ namespace OpenRA.Mods.Common.Traits
public string OrderID { get; private set; }
public int OrderPriority { get; private set; }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
bool CanTargetActor(Actor self, Target target, ref TargetModifiers modifiers, ref string cursor)
{

View File

@@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Traits
base.Tick(self);
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
{
return new AttackActivity(self, newTarget, allowMove, forceAttack, targetLineColor);
}
@@ -293,6 +293,17 @@ namespace OpenRA.Mods.Common.Traits
}
}
// The target may become hidden in the same tick the AttackActivity constructor is called,
// causing lastVisible* to remain uninitialized.
// Fix the fallback values based on the frozen actor properties
else if (target.Type == TargetType.FrozenActor && !lastVisibleTarget.IsValidFor(self))
{
lastVisibleTarget = Target.FromTargetPositions(target);
lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target);
lastVisibleOwner = target.FrozenActor.Owner;
lastVisibleTargetTypes = target.FrozenActor.TargetTypes;
}
var maxRange = lastVisibleMaximumRange;
var minRange = lastVisibleMinimumRange;
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
@@ -353,7 +364,8 @@ namespace OpenRA.Mods.Common.Traits
if (newStance > oldStance || forceAttack)
return;
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
attack.ClearRequestedTarget();
}

View File

@@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits
return TargetInFiringArc(self, target, Info.FacingTolerance);
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
{
return new Activities.Attack(self, newTarget, allowMove, forceAttack, targetLineColor);
}

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.Traits
public AttackOmni(Actor self, AttackOmniInfo info)
: base(self, info) { }
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
{
return new SetTarget(this, newTarget, allowMove, forceAttack, targetLineColor);
}

View File

@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
return prefix + "-blocked";
}
public override bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
public override bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
// Custom order generators always override selection
return true;

View File

@@ -102,10 +102,10 @@ namespace OpenRA.Mods.Common.Traits
}
// Prepare for transport pickup
public override bool LockForPickup(Actor self, Actor carrier)
public override LockResponse LockForPickup(Actor self, Actor carrier)
{
if (state == State.Locked || !WantsTransport)
return false;
if ((state == State.Locked && Carrier != carrier) || !WantsTransport)
return LockResponse.Failed;
// Last chance to change our mind...
var delta = self.World.Map.CenterOfCell(Destination.Value) - self.CenterPosition;
@@ -113,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits
{
// Cancel pickup
MovementCancelled(self);
return false;
return LockResponse.Failed;
}
return base.LockForPickup(self, carrier);

View File

@@ -113,7 +113,8 @@ namespace OpenRA.Mods.Common.Traits
protected override void OnFirstRun(Actor self)
{
QueueChild(new PickupUnit(self, cargo, 0));
if (!cargo.IsDead)
QueueChild(new PickupUnit(self, cargo, 0));
}
public override bool Tick(Actor self)

View File

@@ -127,6 +127,9 @@ namespace OpenRA.Mods.Common.Traits
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyOwnerChanged
{
public readonly IEnumerable<AttackBase> ActiveAttackBases;
readonly bool allowMovement;
[Sync]
int nextScanTime = 0;
@@ -187,6 +190,8 @@ namespace OpenRA.Mods.Common.Traits
stance = self.Owner.IsBot || !self.Owner.Playable ? info.InitialStanceAI : info.InitialStance;
PredictedStance = stance;
allowMovement = Info.AllowMovement && self.TraitOrDefault<IMove>() != null;
}
protected override void Created(Actor self)
@@ -243,6 +248,11 @@ namespace OpenRA.Mods.Common.Traits
attacker = passenger.Transport;
}
// Don't fire at an invisible enemy when we can't move to reveal it
var allowMove = allowMovement && Stance > UnitStance.Defend;
if (!allowMove && !attacker.CanBeViewedByPlayer(self.Owner))
return;
// Not a lot we can do about things we can't hurt... although maybe we should automatically run away?
var attackerAsTarget = Target.FromActor(attacker);
if (!ActiveAttackBases.Any(a => a.HasAnyValidWeapons(attackerAsTarget)))
@@ -254,7 +264,6 @@ namespace OpenRA.Mods.Common.Traits
Aggressor = attacker;
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
Attack(self, Target.FromActor(Aggressor), allowMove);
}
@@ -263,7 +272,7 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled || Stance < UnitStance.Defend)
return;
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
var allowMove = allowMovement && Stance > UnitStance.Defend;
var allowTurn = Info.AllowTurning && Stance > UnitStance.HoldFire;
ScanAndAttack(self, allowMove, allowTurn);
}
@@ -312,12 +321,12 @@ namespace OpenRA.Mods.Common.Traits
void Attack(Actor self, Target target, bool allowMove)
{
foreach (var ab in ActiveAttackBases)
ab.AttackTarget(target, false, allowMove);
ab.AttackTarget(target, AttackSource.AutoTarget, false, allowMove);
}
public bool HasValidTargetPriority(Actor self, Player owner, BitSet<TargetableType> targetTypes)
{
if (Stance <= UnitStance.ReturnFire)
if (owner == null || Stance <= UnitStance.ReturnFire)
return false;
return activeTargetPriorities.Any(ati =>

View File

@@ -72,9 +72,6 @@ namespace OpenRA.Mods.Common.Traits
int scanInterval;
bool firstTick = true;
// MCVs that the bot already knows about. Any MCV not on this list needs to be given an order.
List<Actor> activeMCVs = new List<Actor>();
public McvManagerBotModule(Actor self, McvManagerBotModuleInfo info)
: base(info)
{
@@ -140,18 +137,10 @@ namespace OpenRA.Mods.Common.Traits
void DeployMcvs(IBot bot, bool chooseLocation)
{
activeMCVs.RemoveAll(unitCannotBeOrdered);
var newMCVs = world.ActorsHavingTrait<Transforms>()
.Where(a => a.Owner == player &&
a.IsIdle &&
Info.McvTypes.Contains(a.Info.Name) &&
!activeMCVs.Contains(a));
.Where(a => a.Owner == player && a.IsIdle && Info.McvTypes.Contains(a.Info.Name));
foreach (var a in newMCVs)
activeMCVs.Add(a);
foreach (var mcv in activeMCVs)
foreach (var mcv in newMCVs)
DeployMcv(bot, mcv, chooseLocation);
}
@@ -202,15 +191,8 @@ namespace OpenRA.Mods.Common.Traits
cells = cells.Shuffle(world.LocalRandom);
foreach (var cell in cells)
{
if (!world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
continue;
if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell))
continue;
return cell;
}
if (world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
return cell;
return null;
};
@@ -228,8 +210,7 @@ namespace OpenRA.Mods.Common.Traits
return new List<MiniYamlNode>()
{
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter)),
new MiniYamlNode("ActiveMCVs", FieldSaver.FormatValue(activeMCVs.Select(a => a.ActorID).ToArray()))
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter))
};
}
@@ -241,14 +222,6 @@ namespace OpenRA.Mods.Common.Traits
var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
if (initialBaseCenterNode != null)
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
var activeMCVsNode = data.FirstOrDefault(n => n.Key == "ActiveMCVs");
if (activeMCVsNode != null)
{
activeMCVs.Clear();
activeMCVs.AddRange(FieldLoader.GetValue<uint[]>("ActiveMCVs", activeMCVsNode.Value.Value)
.Select(a => world.GetActorById(a)));
}
}
}
}

View File

@@ -263,7 +263,7 @@ namespace OpenRA.Mods.Common.Traits
var attackForce = RegisterNewSquad(bot, SquadType.Assault);
foreach (var a in unitsHangingAroundTheBase)
if (!a.Info.HasTraitInfo<AircraftInfo>())
if (!a.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(a.Info.Name))
attackForce.Units.Add(a);
unitsHangingAroundTheBase.Clear();
@@ -279,7 +279,7 @@ namespace OpenRA.Mods.Common.Traits
// TODO: This should use common names & ExcludeFromSquads instead of hardcoding TraitInfo checks
var ownUnits = activeUnits
.Where(unit => unit.IsIdle && unit.Info.HasTraitInfo<AttackBaseInfo>()
&& !unit.Info.HasTraitInfo<AircraftInfo>() && !unit.Info.HasTraitInfo<HarvesterInfo>()).ToList();
&& !unit.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(unit.Info.Name) && !unit.Info.HasTraitInfo<HarvesterInfo>()).ToList();
if (!allEnemyBaseBuilder.Any() || ownUnits.Count < Info.SquadSize)
return;
@@ -288,7 +288,7 @@ namespace OpenRA.Mods.Common.Traits
{
// Don't rush enemy aircraft!
var enemies = World.FindActorsInCircle(b.CenterPosition, WDist.FromCells(Info.RushAttackScanRadius))
.Where(unit => IsEnemyUnit(unit) && unit.Info.HasTraitInfo<AttackBaseInfo>() && !unit.Info.HasTraitInfo<AircraftInfo>()).ToList();
.Where(unit => IsEnemyUnit(unit) && unit.Info.HasTraitInfo<AttackBaseInfo>() && !unit.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(unit.Info.Name)).ToList();
if (AttackOrFleeFuzzy.Rush.CanAttack(ownUnits, enemies))
{
@@ -357,8 +357,14 @@ namespace OpenRA.Mods.Common.Traits
{
new MiniYamlNode("Squads", "", Squads.Select(s => new MiniYamlNode("Squad", s.Serialize())).ToList()),
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter)),
new MiniYamlNode("UnitsHangingAroundTheBase", FieldSaver.FormatValue(unitsHangingAroundTheBase.Select(a => a.ActorID).ToArray())),
new MiniYamlNode("ActiveUnits", FieldSaver.FormatValue(activeUnits.Select(a => a.ActorID).ToArray())),
new MiniYamlNode("UnitsHangingAroundTheBase", FieldSaver.FormatValue(unitsHangingAroundTheBase
.Where(a => !unitCannotBeOrdered(a))
.Select(a => a.ActorID)
.ToArray())),
new MiniYamlNode("ActiveUnits", FieldSaver.FormatValue(activeUnits
.Where(a => !unitCannotBeOrdered(a))
.Select(a => a.ActorID)
.ToArray())),
new MiniYamlNode("RushTicks", FieldSaver.FormatValue(rushTicks)),
new MiniYamlNode("AssignRolesTicks", FieldSaver.FormatValue(assignRolesTicks)),
new MiniYamlNode("AttackForceTicks", FieldSaver.FormatValue(attackForceTicks)),
@@ -380,7 +386,7 @@ namespace OpenRA.Mods.Common.Traits
{
unitsHangingAroundTheBase.Clear();
unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue<uint[]>("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value.Value)
.Select(a => self.World.GetActorById(a)));
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
}
var activeUnitsNode = data.FirstOrDefault(n => n.Key == "ActiveUnits");
@@ -388,7 +394,7 @@ namespace OpenRA.Mods.Common.Traits
{
activeUnits.Clear();
activeUnits.AddRange(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value.Value)
.Select(a => self.World.GetActorById(a)));
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
}
var rushTicksNode = data.FirstOrDefault(n => n.Key == "RushTicks");

View File

@@ -40,9 +40,10 @@ namespace OpenRA.Mods.Common.Traits
{
readonly World world;
readonly Player player;
readonly Dictionary<SupportPowerInstance, int> waitingPowers = new Dictionary<SupportPowerInstance, int>();
readonly Dictionary<string, SupportPowerDecision> powerDecisions = new Dictionary<string, SupportPowerDecision>();
readonly List<SupportPowerInstance> stalePowers = new List<SupportPowerInstance>();
SupportPowerManager supportPowerManager;
Dictionary<SupportPowerInstance, int> waitingPowers = new Dictionary<SupportPowerInstance, int>();
Dictionary<string, SupportPowerDecision> powerDecisions = new Dictionary<string, SupportPowerDecision>();
public SupportPowerBotModule(Actor self, SupportPowerBotModuleInfo info)
: base(info)
@@ -108,6 +109,13 @@ namespace OpenRA.Mods.Common.Traits
bot.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false) { SuppressVisualFeedback = true });
}
}
// Remove stale powers
stalePowers.AddRange(waitingPowers.Keys.Where(wp => !supportPowerManager.Powers.ContainsKey(wp.Key)));
foreach (var p in stalePowers)
waitingPowers.Remove(p);
stalePowers.Clear();
}
/// <summary>Scans the map in chunks, evaluating all actors in each.</summary>
@@ -208,8 +216,14 @@ namespace OpenRA.Mods.Common.Traits
var waitingPowersNode = data.FirstOrDefault(n => n.Key == "WaitingPowers");
if (waitingPowersNode != null)
{
foreach (var n in waitingPowersNode.Value.Nodes)
waitingPowers[supportPowerManager.Powers[n.Key]] = FieldLoader.GetValue<int>("WaitingPowers", n.Value.Value);
{
SupportPowerInstance instance;
if (supportPowerManager.Powers.TryGetValue(n.Key, out instance))
waitingPowers[instance] = FieldLoader.GetValue<int>("WaitingPowers", n.Value.Value);
}
}
}
}
}

View File

@@ -49,8 +49,7 @@ namespace OpenRA.Mods.Common.Traits
readonly List<string> queuedBuildRequests = new List<string>();
IBotRequestPauseUnitProduction[] requestPause;
List<Actor> idleUnits = new List<Actor>();
int idleUnitCount;
int ticks;
@@ -68,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits
void IBotNotifyIdleBaseUnits.UpdatedIdleBaseUnits(List<Actor> idleUnits)
{
this.idleUnits = idleUnits;
idleUnitCount = idleUnits.Count;
}
void IBotTick.BotTick(IBot bot)
@@ -88,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits
}
foreach (var q in Info.UnitQueues)
BuildUnit(bot, q, idleUnits.Count < Info.IdleBaseUnitsMaximum);
BuildUnit(bot, q, idleUnitCount < Info.IdleBaseUnitsMaximum);
}
}
@@ -218,7 +217,7 @@ namespace OpenRA.Mods.Common.Traits
return new List<MiniYamlNode>()
{
new MiniYamlNode("QueuedBuildRequests", FieldSaver.FormatValue(queuedBuildRequests.ToArray())),
new MiniYamlNode("IdleUnits", FieldSaver.FormatValue(idleUnits.Select(a => a.ActorID).ToArray()))
new MiniYamlNode("IdleUnitCount", FieldSaver.FormatValue(idleUnitCount))
};
}
@@ -234,13 +233,9 @@ namespace OpenRA.Mods.Common.Traits
queuedBuildRequests.AddRange(FieldLoader.GetValue<string[]>("QueuedBuildRequests", queuedBuildRequestsNode.Value.Value));
}
var idleUnitsNode = data.FirstOrDefault(n => n.Key == "IdleUnits");
if (idleUnitsNode != null)
{
idleUnits.Clear();
idleUnits.AddRange(FieldLoader.GetValue<uint[]>("IdleUnits", idleUnitsNode.Value.Value)
.Select(a => world.GetActorById(a)));
}
var idleUnitCountNode = data.FirstOrDefault(n => n.Key == "IdleUnitCount");
if (idleUnitCountNode != null)
idleUnitCount = FieldLoader.GetValue<int>("IdleUnitCount", idleUnitCountNode.Value.Value);
}
}
}

View File

@@ -60,7 +60,7 @@ namespace OpenRA.Mods.Common.Traits
world.IsCellBuildable(t, ai, bi, toIgnore));
}
public static IEnumerable<Pair<CPos, Actor>> GetLineBuildCells(World world, CPos cell, ActorInfo ai, BuildingInfo bi)
public static IEnumerable<Pair<CPos, Actor>> GetLineBuildCells(World world, CPos cell, ActorInfo ai, BuildingInfo bi, Player owner)
{
var lbi = ai.TraitInfo<LineBuildInfo>();
var topLeft = cell; // 1x1 assumption!
@@ -81,9 +81,10 @@ namespace OpenRA.Mods.Common.Traits
if (dirs[d] != 0)
continue;
// Continue the search if the cell is empty or not visible
var c = topLeft + i * vecs[d];
if (world.IsCellBuildable(c, ai, bi))
continue; // Cell is empty; continue search
if (world.IsCellBuildable(c, ai, bi) || !owner.Shroud.IsExplored(c))
continue;
// Cell contains an actor. Is it the type we want?
connectors[d] = world.ActorMap.GetActorsAt(c)

View File

@@ -122,7 +122,7 @@ namespace OpenRA.Mods.Common.Traits
public string OrderID { get { return "SetRallyPoint"; } }
public int OrderPriority { get { return 0; } }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public bool ForceSet { get; private set; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)

View File

@@ -10,6 +10,7 @@
#endregion
using System;
using OpenRA.Mods.Common.Activities;
using OpenRA.Primitives;
using OpenRA.Traits;
@@ -18,10 +19,16 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Reserve landing places for aircraft.")]
class ReservableInfo : TraitInfo<Reservable> { }
public class Reservable : ITick, INotifyOwnerChanged, INotifySold, INotifyActorDisposing
public class Reservable : ITick, INotifyOwnerChanged, INotifySold, INotifyActorDisposing, INotifyCreated
{
Actor reservedFor;
Aircraft reservedForAircraft;
RallyPoint rallyPoint;
void INotifyCreated.Created(Actor self)
{
rallyPoint = self.TraitOrDefault<RallyPoint>();
}
void ITick.Tick(Actor self)
{
@@ -41,7 +48,7 @@ namespace OpenRA.Mods.Common.Traits
public IDisposable Reserve(Actor self, Actor forActor, Aircraft forAircraft)
{
if (reservedForAircraft != null && reservedForAircraft.MayYieldReservation)
reservedForAircraft.UnReserve(true);
UnReserve(self);
reservedFor = forActor;
reservedForAircraft = forAircraft;
@@ -71,17 +78,27 @@ namespace OpenRA.Mods.Common.Traits
return res == null || res.reservedForAircraft == null || res.reservedForAircraft.MayYieldReservation || res.reservedFor == forActor;
}
private void UnReserve()
void UnReserve(Actor self)
{
if (reservedForAircraft != null)
reservedForAircraft.UnReserve(true);
{
if (reservedForAircraft.GetActorBelow() == self)
{
if (rallyPoint != null)
reservedFor.QueueActivity(reservedForAircraft.MoveTo(rallyPoint.Location, null, targetLineColor: Color.Green));
else
reservedFor.QueueActivity(new TakeOff(reservedFor));
}
reservedForAircraft.UnReserve();
}
}
void INotifyActorDisposing.Disposing(Actor self) { UnReserve(); }
void INotifyActorDisposing.Disposing(Actor self) { UnReserve(self); }
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UnReserve(); }
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UnReserve(self); }
void INotifySold.Selling(Actor self) { UnReserve(); }
void INotifySold.Sold(Actor self) { UnReserve(); }
void INotifySold.Selling(Actor self) { UnReserve(self); }
void INotifySold.Sold(Actor self) { UnReserve(self); }
}
}

View File

@@ -158,8 +158,12 @@ namespace OpenRA.Mods.Common.Traits
{
readonly TransformsIntoAircraft aircraft;
public bool TargetOverridesSelection(TargetModifiers modifiers)
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
{
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
return true;
return modifiers.HasModifier(TargetModifiers.ForceMove);
}

View File

@@ -158,8 +158,12 @@ namespace OpenRA.Mods.Common.Traits
{
readonly TransformsIntoMobile mobile;
readonly bool rejectMove;
public bool TargetOverridesSelection(TargetModifiers modifiers)
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
{
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
return true;
return modifiers.HasModifier(TargetModifiers.ForceMove);
}

View File

@@ -221,6 +221,11 @@ namespace OpenRA.Mods.Common.Traits
if (captures == null)
return false;
// HACK: Make sure the target is not moving and at its normal position with respect to the cell grid
var enterMobile = target.TraitOrDefault<Mobile>();
if (enterMobile != null && enterMobile.IsMovingBetweenCells)
return false;
if (progressWatchers.Any() || targetManager.progressWatchers.Any())
{
currentTargetTotal = captures.Info.CaptureDelay;

View File

@@ -229,7 +229,7 @@ namespace OpenRA.Mods.Common.Traits
}
return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, blockedByMobile: false))
&& CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait<IPositionable>().CanEnterCell(c, null, immediate)));
&& CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => !p.IsDead && p.Trait<IPositionable>().CanEnterCell(c, null, immediate)));
}
public bool CanLoad(Actor self, Actor a)
@@ -258,7 +258,7 @@ namespace OpenRA.Mods.Common.Traits
internal void UnreserveSpace(Actor a)
{
if (!reserves.Contains(a))
if (!reserves.Contains(a) || self.IsDead)
return;
reservedWeight -= GetWeight(a);
@@ -409,11 +409,11 @@ namespace OpenRA.Mods.Common.Traits
// If not initialized then this will be notified in the first tick
if (initialized)
{
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
npe.OnPassengerEntered(self, a);
foreach (var nec in a.TraitsImplementing<INotifyEnteredCargo>())
nec.OnEnteredCargo(a, self);
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
npe.OnPassengerEntered(self, a);
}
var p = a.Trait<Passenger>();
@@ -510,11 +510,11 @@ namespace OpenRA.Mods.Common.Traits
{
c.Trait<Passenger>().Transport = self;
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
npe.OnPassengerEntered(self, c);
foreach (var nec in c.TraitsImplementing<INotifyEnteredCargo>())
nec.OnEnteredCargo(c, self);
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
npe.OnPassengerEntered(self, c);
}
initialized = true;

View File

@@ -9,7 +9,7 @@
*/
#endregion
using OpenRA.Mods.Common.Activities;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
@@ -35,6 +35,13 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new Carryable(init.Self, this); }
}
public enum LockResponse { Success, Pending, Failed }
public interface IDelayCarryallPickup
{
bool TryLockForPickup(Actor self, Actor carrier);
}
public class Carryable : ConditionalTrait<CarryableInfo>
{
ConditionManager conditionManager;
@@ -42,6 +49,9 @@ namespace OpenRA.Mods.Common.Traits
int carriedToken = ConditionManager.InvalidConditionToken;
int lockedToken = ConditionManager.InvalidConditionToken;
Mobile mobile;
IDelayCarryallPickup[] delayPickups;
public Actor Carrier { get; private set; }
public bool Reserved { get { return state != State.Free; } }
public CPos? Destination { get; protected set; }
@@ -57,6 +67,8 @@ namespace OpenRA.Mods.Common.Traits
protected override void Created(Actor self)
{
conditionManager = self.Trait<ConditionManager>();
mobile = self.TraitOrDefault<Mobile>();
delayPickups = self.TraitsImplementing<IDelayCarryallPickup>().ToArray();
base.Created(self);
}
@@ -111,18 +123,28 @@ namespace OpenRA.Mods.Common.Traits
}
// Prepare for transport pickup
public virtual bool LockForPickup(Actor self, Actor carrier)
public virtual LockResponse LockForPickup(Actor self, Actor carrier)
{
if (state == State.Locked)
return false;
if (state == State.Locked && Carrier != carrier)
return LockResponse.Failed;
state = State.Locked;
Carrier = carrier;
if (delayPickups.Any(d => d.IsTraitEnabled() && !d.TryLockForPickup(self, carrier)))
return LockResponse.Pending;
if (lockedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(Info.LockedCondition))
lockedToken = conditionManager.GrantCondition(self, Info.LockedCondition);
if (state != State.Locked)
{
state = State.Locked;
Carrier = carrier;
return true;
if (lockedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(Info.LockedCondition))
lockedToken = conditionManager.GrantCondition(self, Info.LockedCondition);
}
// Make sure we are not moving and at our normal position with respect to the cell grid
if (mobile != null && mobile.IsMovingBetweenCells)
return LockResponse.Pending;
return LockResponse.Success;
}
}
}

View File

@@ -375,7 +375,7 @@ namespace OpenRA.Mods.Common.Traits
public string OrderID { get { return "DeliverUnit"; } }
public int OrderPriority { get { return 6; } }
public bool IsQueued { get; protected set; }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public CarryallDeliverUnitTargeter(AircraftInfo aircraftInfo, CarryallInfo info)
{

View File

@@ -58,6 +58,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Undeploy before the actor tries to move?")]
public readonly bool UndeployOnMove = false;
[Desc("Undeploy before the actor is picked up by a Carryall?")]
public readonly bool UndeployOnPickup = false;
[VoiceReference]
public readonly string Voice = "Action";
@@ -67,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
public enum DeployState { Undeployed, Deploying, Deployed, Undeploying }
public class GrantConditionOnDeploy : PausableConditionalTrait<GrantConditionOnDeployInfo>, IResolveOrder, IIssueOrder,
INotifyDeployComplete, IIssueDeployOrder, IOrderVoice, IWrapMove
INotifyDeployComplete, IIssueDeployOrder, IOrderVoice, IWrapMove, IDelayCarryallPickup
{
readonly Actor self;
readonly bool checkTerrainType;
@@ -137,6 +140,17 @@ namespace OpenRA.Mods.Common.Traits
return activity;
}
bool IDelayCarryallPickup.TryLockForPickup(Actor self, Actor carrier)
{
if (!Info.UndeployOnPickup || deployState == DeployState.Undeployed || IsTraitDisabled)
return true;
if (deployState == DeployState.Deployed && !IsTraitPaused)
Undeploy();
return false;
}
IEnumerable<IOrderTargeter> IIssueOrder.Orders
{
get

View File

@@ -40,8 +40,9 @@ namespace OpenRA.Mods.Common.Traits
bool IOccupySpaceInfo.SharesCell { get { return false; } }
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
{
// Since crates don't share cells and GetAvailableSubCell only returns SubCell.Full or SubCell.Invalid, we ignore the subCell parameter
return GetAvailableSubCell(world, cell, ignoreActor, checkTransientActors) != SubCell.Invalid;
}

View File

@@ -90,12 +90,12 @@ namespace OpenRA.Mods.Common.Traits
protected override void TraitEnabled(Actor self)
{
self.World.ActorMap.UpdatePosition(self, self.OccupiesSpace);
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
}
protected override void TraitDisabled(Actor self)
{
self.World.ActorMap.UpdatePosition(self, self.OccupiesSpace);
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
}
}
}

View File

@@ -50,9 +50,7 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled || self.Owner.WinState == WinState.Lost || !self.World.Map.Contains(self.Location))
return;
var r = self.World.SharedRandom.Next(1, 100);
if (r <= 100 - Info.SuccessRate)
if (self.World.SharedRandom.Next(100) >= Info.SuccessRate)
return;
var cp = self.CenterPosition;
@@ -60,48 +58,47 @@ namespace OpenRA.Mods.Common.Traits
if ((inAir && !Info.EjectInAir) || (!inAir && !Info.EjectOnGround))
return;
var pilot = self.World.CreateActor(false, Info.PilotActor.ToLowerInvariant(),
new TypeDictionary { new OwnerInit(self.Owner), new LocationInit(self.Location) });
var pilotPositionable = pilot.TraitOrDefault<IPositionable>();
var pilotCell = self.Location;
var pilotSubCell = pilotPositionable.GetAvailableSubCell(pilotCell);
if (pilotSubCell == SubCell.Invalid)
self.World.AddFrameEndTask(w =>
{
if (!Info.AllowUnsuitableCell)
{
pilot.Dispose();
return;
var pilotInfo = self.World.Map.Rules.Actors[Info.PilotActor.ToLowerInvariant()];
var pilotPositionable = pilotInfo.TraitInfo<IPositionableInfo>();
if (!pilotPositionable.CanEnterCell(self.World, null, self.Location))
return;
}
pilotSubCell = SubCell.Any;
}
if (inAir)
{
self.World.AddFrameEndTask(w =>
var td = new TypeDictionary
{
pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell);
var dropPosition = pilot.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - pilot.CenterPosition.Z);
pilotPositionable.SetVisualPosition(pilot, dropPosition);
new OwnerInit(self.Owner),
new LocationInit(self.Location),
};
w.Add(pilot);
});
Game.Sound.Play(SoundType.World, Info.ChuteSound, cp);
}
else
{
self.World.AddFrameEndTask(w =>
// If airborne, offset the spawn location so the pilot doesn't drop on another infantry's head
var spawnPos = cp;
if (inAir)
{
w.Add(pilot);
pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell);
var subCell = self.World.ActorMap.FreeSubCell(self.Location);
if (subCell != SubCell.Invalid)
{
td.Add(new SubCellInit(subCell));
spawnPos = self.World.Map.CenterOfSubCell(self.Location, subCell) + new WVec(0, 0, spawnPos.Z);
}
}
td.Add(new CenterPositionInit(spawnPos));
var pilot = self.World.CreateActor(true, Info.PilotActor.ToLowerInvariant(), td);
if (!inAir)
{
var pilotMobile = pilot.TraitOrDefault<Mobile>();
if (pilotMobile != null)
pilotMobile.Nudge(pilot, pilot, true);
});
}
}
else
Game.Sound.Play(SoundType.World, Info.ChuteSound, cp);
});
}
}
}

View File

@@ -96,6 +96,9 @@ namespace OpenRA.Mods.Common.Traits
public void GiveLevels(int numLevels, bool silent = false)
{
if (MaxLevel == 0)
return;
var newLevel = Math.Min(Level + numLevels, MaxLevel);
GiveExperience(nextLevel[newLevel - 1].First - experience, silent);
}
@@ -105,6 +108,9 @@ namespace OpenRA.Mods.Common.Traits
if (amount < 0)
throw new ArgumentException("Revoking experience is not implemented.", "amount");
if (MaxLevel == 0)
return;
experience = (experience + amount).Clamp(0, nextLevel[MaxLevel - 1].First);
while (Level < MaxLevel && experience >= nextLevel[Level].First)

View File

@@ -130,8 +130,6 @@ namespace OpenRA.Mods.Common.Traits
mobile = self.Trait<Mobile>();
resLayer = self.World.WorldActor.Trait<ResourceLayer>();
claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>();
self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null)));
}
void INotifyCreated.Created(Actor self)
@@ -141,6 +139,8 @@ namespace OpenRA.Mods.Common.Traits
conditionManager = self.TraitOrDefault<ConditionManager>();
UpdateCondition(self);
self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null)));
// Note: This is queued in a FrameEndTask because otherwise the activity is dropped/overridden while moving out of a factory.
if (Info.SearchOnCreation)
self.World.AddFrameEndTask(w => self.QueueActivity(new FindAndDeliverResources(self)));
@@ -379,7 +379,7 @@ namespace OpenRA.Mods.Common.Traits
public string OrderID { get { return "Harvest"; } }
public int OrderPriority { get { return 10; } }
public bool IsQueued { get; protected set; }
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
{

View File

@@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits
bool IOccupySpaceInfo.SharesCell { get { return false; } }
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
{
// IPositionable*Info*.CanEnterCell is only ever used for things like exiting production facilities,
// all places relevant for husks check IPositionable.CanEnterCell instead, so we can safely set this to true.

View File

@@ -82,7 +82,10 @@ namespace OpenRA.Mods.Common.Traits
// initialized and used by CanEnterCell
Locomotor locomotor;
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
/// <summary>
/// Note: If the target <paramref name="cell"/> has any free subcell, the value of <paramref name="subCell"/> is ignored.
/// </summary>
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
{
// PERF: Avoid repeated trait queries on the hot path
if (locomotor == null)
@@ -93,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits
return false;
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
return locomotor.CanMoveFreelyInto(self, cell, ignoreActor, check);
return locomotor.CanMoveFreelyInto(self, cell, subCell, ignoreActor, check);
}
public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any)
@@ -139,12 +142,15 @@ namespace OpenRA.Mods.Common.Traits
}
}
public class Mobile : PausableConditionalTrait<MobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, ITick,
public class Mobile : PausableConditionalTrait<MobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, ITick, ICreationActivity,
IFacing, IDeathActorInitModifier, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove, IActorPreviewInitModifier, INotifyBecomingIdle
{
readonly Actor self;
readonly Lazy<IEnumerable<int>> speedModifiers;
readonly int moveIntoWorldDelay;
readonly bool returnToCellOnCreation;
readonly bool returnToCellOnCreationRecalculateSubCell = true;
readonly int creationActivityDelay;
#region IMove CurrentMovementTypes
MovementType movementTypes;
@@ -160,8 +166,11 @@ namespace OpenRA.Mods.Common.Traits
var oldValue = movementTypes;
movementTypes = value;
if (value != oldValue)
{
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
foreach (var n in notifyMoving)
n.MovementTypeChanged(self, value);
}
}
}
#endregion
@@ -177,6 +186,11 @@ namespace OpenRA.Mods.Common.Traits
IWrapMove[] moveWrappers;
bool requireForceMove;
public bool IsMovingBetweenCells
{
get { return FromCell != ToCell; }
}
#region IFacing
[Sync]
public int Facing
@@ -210,7 +224,9 @@ namespace OpenRA.Mods.Common.Traits
{
if (FromCell == ToCell)
return new[] { Pair.New(FromCell, FromSubCell) };
if (CanEnterCell(ToCell))
// HACK: Should be fixed properly, see https://github.com/OpenRA/OpenRA/pull/17292 for an explanation
if (Info.LocomotorInfo.SharesCell)
return new[] { Pair.New(ToCell, ToSubCell) };
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
@@ -226,7 +242,10 @@ namespace OpenRA.Mods.Common.Traits
ToSubCell = FromSubCell = info.LocomotorInfo.SharesCell ? init.World.Map.Grid.DefaultSubCell : SubCell.FullCell;
if (init.Contains<SubCellInit>())
{
FromSubCell = ToSubCell = init.Get<SubCellInit, SubCell>();
returnToCellOnCreationRecalculateSubCell = false;
}
if (init.Contains<LocationInit>())
{
@@ -234,14 +253,19 @@ namespace OpenRA.Mods.Common.Traits
SetVisualPosition(self, init.World.Map.CenterOfSubCell(FromCell, FromSubCell));
}
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
Facing = oldFacing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
// Sets the visual position to WPos accuracy
// Use LocationInit if you want to insert the actor into the ActorMap!
// Sets the initial visual position
// Unit will move into the cell grid (defined by LocationInit) as its initial activity
if (init.Contains<CenterPositionInit>())
SetVisualPosition(self, init.Get<CenterPositionInit, WPos>());
{
oldPos = init.Get<CenterPositionInit, WPos>();
SetVisualPosition(self, oldPos);
returnToCellOnCreation = true;
}
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
if (init.Contains<CreationActivityDelayInit>())
creationActivityDelay = init.Get<CreationActivityDelayInit, int>();
}
protected override void Created(Actor self)
@@ -254,7 +278,6 @@ namespace OpenRA.Mods.Common.Traits
Locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
.Single(l => l.Info.Name == Info.Locomotor);
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
base.Created(self);
}
@@ -295,7 +318,7 @@ namespace OpenRA.Mods.Common.Traits
public void Nudge(Actor self, Actor nudger, bool force)
{
if (IsTraitDisabled || IsTraitPaused)
if (IsTraitDisabled || IsTraitPaused || requireForceMove)
return;
// Initial fairly braindead implementation.
@@ -455,7 +478,7 @@ namespace OpenRA.Mods.Common.Traits
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
{
return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors);
return Info.CanEnterCell(self.World, self, cell, ToSubCell, ignoreActor, checkTransientActors);
}
#endregion
@@ -567,27 +590,27 @@ namespace OpenRA.Mods.Common.Traits
return WrapMove(new Follow(self, target, minRange, maxRange, initialTargetPosition, targetLineColor));
}
public Activity MoveIntoWorld(Actor self, int delay = 0)
public Activity ReturnToCell(Actor self)
{
return new MoveIntoWorldActivity(self, delay);
return new ReturnToCellActivity(self);
}
class MoveIntoWorldActivity : Activity
class ReturnToCellActivity : Activity
{
readonly Actor self;
readonly Mobile mobile;
readonly bool recalculateSubCell;
CPos cell;
SubCell subCell;
WPos pos;
int delay;
public MoveIntoWorldActivity(Actor self, int delay = 0)
public ReturnToCellActivity(Actor self, int delay = 0, bool recalculateSubCell = false)
{
this.self = self;
mobile = self.Trait<Mobile>();
IsInterruptible = false;
this.delay = delay;
this.recalculateSubCell = recalculateSubCell;
}
protected override void OnFirstRun(Actor self)
@@ -603,7 +626,7 @@ namespace OpenRA.Mods.Common.Traits
cell = mobile.ToCell;
subCell = mobile.ToSubCell;
if (subCell == SubCell.Any)
if (recalculateSubCell)
subCell = mobile.Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
// TODO: solve/reduce cell is full problem
@@ -890,13 +913,22 @@ namespace OpenRA.Mods.Common.Traits
}
}
Activity ICreationActivity.GetCreationActivity()
{
return returnToCellOnCreation ? new ReturnToCellActivity(self, creationActivityDelay, returnToCellOnCreationRecalculateSubCell) : null;
}
class MoveOrderTargeter : IOrderTargeter
{
readonly Mobile mobile;
readonly LocomotorInfo locomotorInfo;
readonly bool rejectMove;
public bool TargetOverridesSelection(TargetModifiers modifiers)
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
{
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
return true;
return modifiers.HasModifier(TargetModifiers.ForceMove);
}

View File

@@ -131,6 +131,16 @@ namespace OpenRA.Mods.Common.Traits
if (specificCargoToken == ConditionManager.InvalidConditionToken && Info.CargoConditions.TryGetValue(cargo.Info.Name, out specificCargoCondition))
specificCargoToken = conditionManager.GrantCondition(self, specificCargoCondition);
}
// Allow scripted / initial actors to move from the unload point back into the cell grid on unload
// This is handled by the RideTransport activity for player-loaded cargo
if (self.IsIdle)
{
// IMove is not used anywhere else in this trait, there is no benefit to caching it from Created.
var move = self.TraitOrDefault<IMove>();
if (move != null)
self.QueueActivity(move.ReturnToCell(self));
}
}
void INotifyExitedCargo.OnExitedCargo(Actor self, Actor cargo)

View File

@@ -117,7 +117,7 @@ namespace OpenRA.Mods.Common.Traits
if (string.IsNullOrEmpty(segmentType))
segmentType = actorInfo.Name;
foreach (var t in BuildingUtils.GetLineBuildCells(w, targetLocation, actorInfo, buildingInfo))
foreach (var t in BuildingUtils.GetLineBuildCells(w, targetLocation, actorInfo, buildingInfo, order.Player))
{
if (t.First == targetLocation)
continue;

View File

@@ -28,14 +28,6 @@ namespace OpenRA.Mods.Common.Traits
public int OrderCount;
public int EarnedThisMinute
{
get
{
return resources != null ? resources.Earned - earnedAtBeginningOfMinute : 0;
}
}
public int Experience
{
get
@@ -44,10 +36,12 @@ namespace OpenRA.Mods.Common.Traits
}
}
public Queue<int> EarnedSamples = new Queue<int>(100);
int earnedAtBeginningOfMinute;
// Low resolution (every 30 seconds) record of earnings, covering the entire game
public List<int> IncomeSamples = new List<int>(100);
public int Income;
public int DisplayIncome;
public Queue<int> ArmySamples = new Queue<int>(100);
public List<int> ArmySamples = new List<int>(100);
public int KillsCost;
public int DeathsCost;
@@ -59,40 +53,63 @@ namespace OpenRA.Mods.Common.Traits
public int BuildingsDead;
public int ArmyValue;
// High resolution (every second) record of earnings, limited to the last minute
readonly Queue<int> earnedSeconds = new Queue<int>(60);
int lastIncome;
int lastIncomeTick;
int ticks;
int replayTimestep;
bool armyGraphDisabled;
bool incomeGraphDisabled;
public PlayerStatistics(Actor self) { }
void INotifyCreated.Created(Actor self)
{
resources = self.TraitOrDefault<PlayerResources>();
experience = self.TraitOrDefault<PlayerExperience>();
}
void UpdateEarnedThisMinute()
{
EarnedSamples.Enqueue(EarnedThisMinute);
earnedAtBeginningOfMinute = resources != null ? resources.Earned : 0;
if (EarnedSamples.Count > 100)
EarnedSamples.Dequeue();
}
void UpdateArmyThisMinute()
{
ArmySamples.Enqueue(ArmyValue);
if (ArmySamples.Count > 100)
ArmySamples.Dequeue();
incomeGraphDisabled = resources == null;
}
void ITick.Tick(Actor self)
{
var timestep = self.World.IsReplay ? replayTimestep : self.World.Timestep;
ticks++;
if (timestep * self.World.WorldTick % 60000 == 0)
var timestep = self.World.IsReplay ? replayTimestep : self.World.Timestep;
if (ticks * timestep >= 30000)
{
UpdateEarnedThisMinute();
UpdateArmyThisMinute();
ticks = 0;
if (!armyGraphDisabled && (ArmyValue != 0 || self.Owner.WinState == WinState.Undefined))
ArmySamples.Add(ArmyValue);
else
armyGraphDisabled = true;
if (!incomeGraphDisabled && (Income != 0 || self.Owner.WinState == WinState.Undefined))
IncomeSamples.Add(Income);
else
incomeGraphDisabled = true;
}
if (resources == null)
return;
var tickDelta = self.World.WorldTick - lastIncomeTick;
if (tickDelta * timestep >= 1000)
{
lastIncomeTick = self.World.WorldTick;
var lastEarned = earnedSeconds.Count > 59 ? earnedSeconds.Dequeue() : 0;
lastIncome = DisplayIncome = Income;
Income = resources.Earned - lastEarned;
earnedSeconds.Enqueue(resources.Earned);
}
else
DisplayIncome = int2.Lerp(lastIncome, Income, tickDelta * timestep, 1000);
}
public void ResolveOrder(Actor self, Order order)
@@ -126,8 +143,11 @@ namespace OpenRA.Mods.Common.Traits
if (w.IsReplay)
replayTimestep = w.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
UpdateEarnedThisMinute();
UpdateArmyThisMinute();
if (!armyGraphDisabled)
ArmySamples.Add(ArmyValue);
if (!incomeGraphDisabled)
IncomeSamples.Add(Income);
}
}

View File

@@ -74,7 +74,7 @@ namespace OpenRA.Mods.Common.Traits
td.Add(new CenterPositionInit(spawn));
td.Add(new FacingInit(initialFacing));
if (exitinfo != null)
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
td.Add(new CreationActivityDelayInit(exitinfo.ExitDelay));
}
self.World.AddFrameEndTask(w =>
@@ -130,7 +130,7 @@ namespace OpenRA.Mods.Common.Traits
self.NotifyBlocker(self.Location + s.ExitCell);
return mobileInfo == null ||
mobileInfo.CanEnterCell(self.World, self, self.Location + s.ExitCell, self);
mobileInfo.CanEnterCell(self.World, self, self.Location + s.ExitCell, ignoreActor: self);
}
}
}

View File

@@ -127,7 +127,7 @@ namespace OpenRA.Mods.Common.Traits
td.Add(new LocationInit(exit));
td.Add(new CenterPositionInit(spawn));
td.Add(new FacingInit(initialFacing));
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
td.Add(new CreationActivityDelayInit(exitinfo.ExitDelay));
}
self.World.AddFrameEndTask(w =>

View File

@@ -29,6 +29,13 @@ namespace OpenRA.Mods.Common.Traits.Render
[SequenceReference("Image")]
public readonly string EndSequence = "end";
[PaletteReference("IsPlayerPalette")]
[Desc("Custom palette name.")]
public readonly string Palette = null;
[Desc("Custom palette is a player palette BaseName.")]
public readonly bool IsPlayerPalette = false;
[Desc("Damage types that this should be used for (defined on the warheads).",
"Leave empty to disable all filtering.")]
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
@@ -54,7 +61,8 @@ namespace OpenRA.Mods.Common.Traits.Render
var rs = self.Trait<RenderSprites>();
anim = new Animation(self.World, info.Image);
rs.Add(new AnimationWithOffset(anim, null, () => !isSmoking));
rs.Add(new AnimationWithOffset(anim, null, () => !isSmoking),
info.Palette, info.IsPlayerPalette);
}
void INotifyDamage.Damaged(Actor self, AttackInfo e)

View File

@@ -93,6 +93,7 @@ namespace OpenRA.Mods.Common.Traits.Render
protected override void Created(Actor self)
{
rsm = self.TraitOrDefault<IRenderInfantrySequenceModifier>();
idleDelay = self.World.SharedRandom.Next(Info.MinIdleDelay, Info.MaxIdleDelay);
base.Created(self);
}

View File

@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Traits
if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down)
{
if (!activated)
if (!activated && world.Map.Contains(cell))
{
targetCell = cell;
targetLocation = mi.Location;
@@ -119,7 +119,10 @@ namespace OpenRA.Mods.Common.Traits
IEnumerable<IRenderable> IOrderGenerator.RenderAboveShroud(WorldRenderer wr, World world) { yield break; }
string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { return cursor; }
string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
return world.Map.Contains(cell) ? cursor : "generic-blocked";
}
bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; }

View File

@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<SupportPowerInstance> GetPowersForActor(Actor a)
{
if (a.Owner != Self.Owner || !a.Info.HasTraitInfo<SupportPowerInfo>())
if (Powers.Count == 0 || a.Owner != Self.Owner || !a.Info.HasTraitInfo<SupportPowerInfo>())
return NoInstances;
return a.TraitsImplementing<SupportPower>()
@@ -162,7 +162,16 @@ namespace OpenRA.Mods.Common.Traits
public int RemainingTime;
public int TotalTime;
public bool Active { get; private set; }
public bool Disabled { get { return (!prereqsAvailable && !manager.DevMode.AllTech) || !instancesEnabled || oneShotFired; } }
public bool Disabled
{
get
{
return manager.Self.Owner.WinState == WinState.Lost ||
(!prereqsAvailable && !manager.DevMode.AllTech) ||
!instancesEnabled ||
oneShotFired;
}
}
public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } }
public bool Ready { get { return Active && RemainingTime == 0; } }

View File

@@ -0,0 +1,20 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Tag trait for updating the 'Oil Derrick' count economy statistic.")]
public class UpdatesDerrickCountInfo : TraitInfo<UpdatesDerrickCount> { }
public class UpdatesDerrickCount { }
}

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