Compare commits

...

530 Commits

Author SHA1 Message Date
Matthias Mailänder
86a714a588 Enable itch app auto updates on Windows. 2020-09-06 19:48:10 +02:00
ABrandau
c01c39954a Update Harpy Rotor 2020-09-01 18:57:43 +02:00
Matthias Mailänder
c48eb572e3 Remove MuzzleSplitFacings and expose MuzzleSequence to testing. 2020-09-01 18:53:43 +02:00
Paul Chote
b03ab1212f Remove install-core target. 2020-09-01 18:48:54 +02:00
Paul Chote
8a9b5e7e01 Split a separate "compat" macOS package that uses the system mono. 2020-09-01 18:48:54 +02:00
Paul Chote
9b90e4f25a Remove Coverity references. 2020-09-01 18:48:54 +02:00
Paul Chote
8a4401bdcb Fix Appveyor GeoIP download. 2020-09-01 18:48:54 +02:00
Paul Chote
d52e90cf23 Improve .travis.yml compatibility for non-OpenRA deploys. 2020-09-01 18:48:54 +02:00
Paul Chote
14ef0a7740 Remove markdown/html pages from windows install. 2020-09-01 18:48:54 +02:00
Paul Chote
c1f79b348a Generate platform-specific artwork from common source files. 2020-09-01 18:48:54 +02:00
Paul Chote
62166a50d9 Move itch upload script. 2020-09-01 18:48:54 +02:00
Brent Gardner
283b330403 Workaround for GLES 2.0 hardware 2020-09-01 18:00:51 +02:00
teinarss
2cf6b74295 Refactoring on GetEventBounds in Widget 2020-09-01 17:56:12 +02:00
abcdefg30
1a9f707d18 Remove the last reference to DedicatedLoop 2020-08-30 18:12:48 +02:00
Aigamo
06a1c88e86 Replace 0x7FFFFFFF with int.MaxValue 2020-08-29 12:37:20 +01:00
ycanardeau
f358b566b1 Replace NetFrameNumber >= 1 with GameStarted 2020-08-29 12:37:20 +01:00
reaperrr
15fc27d142 Use cached selected in SelectionDecorationsBase 2020-08-28 12:24:07 +02:00
reaperrr
ad20597d74 Cache hue picker sprite in HueSliderWidget 2020-08-27 21:17:37 +02:00
reaperrr
6d409a7c97 Cache indicator sprite in ResourceBarWidget at initialization 2020-08-27 21:17:37 +02:00
reaperrr
36d5ae5421 Cache SupportPowersWidget offsets at initialization
As well as overlay font.
2020-08-27 21:17:37 +02:00
tovl
db9744ea7f Let TS aircraft turn slower when circling. 2020-08-27 21:15:38 +02:00
tovl
5e62fe86fc Add IdleSpeed to aircraft trait. 2020-08-27 21:15:38 +02:00
Paul Chote
6cfa27c33b Replace per-color font caches with tinted rendering. 2020-08-24 18:38:08 +02:00
Taryn Hill
a405969199 docs: link to docs.openra.net instead of wiki.openra.net in readme for generated trait docs 2020-08-23 11:20:02 +02:00
Paul Praet
9a6f3b4c05 Reset Ready status of players in Lobby when options change
Addresses #11274
2020-08-23 00:05:55 +03:00
Matthias Mailänder
7be059a79b Download our cached version after they blocked Travis CI... 2020-08-22 13:06:08 +02:00
reaperrr
29b55de042 Cache rectangles and font in ProductionTabsWidget 2020-08-21 18:06:18 +02:00
reaperrr
235fb19aa8 Cache overlay traits in ProductionPaletteWidget
Instead of looking this up every Draw tick,
cache and update it only when a non-null new
CurrentQueue is set (as the overlays can only change
at that time).
2020-08-21 18:06:18 +02:00
reaperrr
c0f54fa4fc Cache offsets in ProductionPaletteWidget
At least those that never change.
2020-08-21 18:06:18 +02:00
reaperrr
cdc216aca0 Fix airborne husk target types
Doesn't really make a difference, since it only matters
for effect warheads and those already could target both
air and ground actors, but GroundActor was still wrong.
2020-08-20 20:46:58 +02:00
reaperrr
4505053618 Simplify CreateEffectWarhead code
Simplified and streamlined code,
based on past feedback and suggestions.

Note: The new methods will move to
Warhead later, once they're used by more
than one warhead.
2020-08-20 20:46:58 +02:00
abcdefg30
e0d53126d6 Remove the Light Tank husk 2020-08-20 19:17:39 +02:00
abcdefg30
12ff1dd14c Add RAGL balance changes 2020-08-20 19:17:39 +02:00
abcdefg30
50db3152f6 Fix the windows uninstaller name 2020-08-20 17:33:48 +02:00
teinarss
9c4fd0e3d3 Use Null-Propagation Operator 2020-08-19 18:11:07 +01:00
teinarss
8d27d22100 Use discard syntax 2020-08-19 18:11:07 +01:00
teinarss
27f1a7ab27 Use out var syntax 2020-08-19 18:11:07 +01:00
teinarss
d52e4793fe Refactor classes to structs 2020-08-19 11:54:29 +02:00
teinarss
544ac6cb33 Fix crash after entering manage content 2020-08-19 11:40:43 +02:00
Paul Chote
06fbc1a6cf Hide TraitInfo.InstanceName from FieldLoader. 2020-08-17 20:13:32 +02:00
abcdefg30
edab10e6a6 Make the Phase Transport uncloak during repair by mechanics 2020-08-16 21:01:16 +02:00
abcdefg30
dd99fc93e4 Uncloak during resupply when "UncloakOn: Dock" is defined 2020-08-16 21:01:16 +02:00
abcdefg30
dbe824d4e5 Correct "offseted" to "offset" 2020-08-16 15:02:56 +02:00
Paul Chote
6e73d7f5c2 Tidy MapEditorLogic ctor. 2020-08-16 14:17:45 +02:00
Paul Chote
960056d300 Fix mod switcher icon handling. 2020-08-16 14:17:45 +02:00
Paul Chote
3efac3287e Fix Session.Deserialize error handling. 2020-08-16 14:17:45 +02:00
Paul Chote
c4b4a8c8a5 Fix incorrect ramp fill in Map.Resize. 2020-08-16 14:17:45 +02:00
Paul Chote
b833f033bf Remove redundant check from LabelWithHighlightWidget. 2020-08-16 14:17:45 +02:00
Paul Chote
ad75e2be89 Remove redundant check from SupportPowerInstance. 2020-08-16 14:17:45 +02:00
Orb
7ee4fbeb0d New Money Settings 2020-08-15 21:02:01 +01:00
Paul Chote
9886f0ca9a Fix Harvester crash when multiple resource renderers are used. 2020-08-15 18:43:40 +02:00
Paul Chote
46cf56d6ff Remove editor-specific resource rendering.
Mods must manually move their *ResourceRenderer definitions from
World onto BaseWorld to restore resource rendering in the editor.
2020-08-15 18:43:40 +02:00
Matthias Mailänder
e7af295b5e Allow news per mod. 2020-08-15 16:21:21 +01:00
Matthias Mailänder
9d179d9a1a Initialize the font only once and make it configurable. 2020-08-15 16:13:21 +01:00
Matthias Mailänder
15010f9567 Fix production palette ignoring yaml font overrides. 2020-08-15 16:13:21 +01:00
Oliver Brakmann
a7f4f6c1cf Use LeaveMapAtClosestEdge for scripted MiGs on Intervention 2020-08-15 13:38:57 +01:00
Oliver Brakmann
3eeb677f14 Add LeaveMapAtClosestEdge idle behaviour for Aircraft 2020-08-15 13:38:57 +01:00
Paul Chote
ef69a3de66 Use nameof() in PaletteReference. 2020-08-15 13:41:45 +02:00
abcdefg30
0aa5e07252 Send the 'minefieldStart' along with a 'PlaceMinefield' order 2020-08-15 11:34:00 +01:00
abcdefg30
07d58337f1 Let order generators account for selection changes 2020-08-15 11:34:00 +01:00
abcdefg30
b5e3f25418 Fix CashTrickler crashing without IOccupiesSpace 2020-08-15 11:21:39 +01:00
teinarss
19b02875c7 Use Tuple syntax 2020-08-15 10:37:10 +01:00
Taryn Hill
8a74f6ea18 change whitespace-only lines to empty lines in Lua scripts 2020-08-14 15:08:14 +02:00
abcdefg30
a847f3eafa Fix actors not yet in the world improperly updating power state 2020-08-14 11:46:38 +02:00
Curtis Shmyr
a751f074e7 Added actor parameter to Lua UnloadPassenger 2020-08-10 10:38:35 +02:00
Paul Chote
75cb5c2166 Convert turret facings to WAngle relative to the body. 2020-08-09 19:43:53 +02:00
Paul Chote
70a86bed7a Optimize WRot negation.
The conjugate of a quaternion just negates the x/y/z components, so
there is no need to recalculate from scratch and throw away precision
by forcing a quat->euler->quat round trip.
2020-08-09 19:43:53 +02:00
dnqbob
f67f8ed05e WithLandingCraftAnimation can stop on all movement 2020-08-09 13:22:56 +02:00
Mustafa Alperen Seki
1ae53220d6 Add CurrentMuzzleFacing to FireClusterWarhead. 2020-08-09 13:18:39 +02:00
Matthias Mailänder
c546cb552e Make the fonts configurable. 2020-08-09 13:14:32 +02:00
Matthias Mailänder
10f8836d7b Fix a null reference exception. 2020-08-09 13:14:32 +02:00
dnqbob
7ecd4124ce Make "RepairableNear" public, helpful for modder 2020-08-08 13:20:39 +02:00
abcdefg30
54cd77be8e Add Tiberium near the transformable tree in cnc64gdi01 2020-08-05 11:45:11 +02:00
Mustafa Alperen Seki
43388cb7fc Don't check HasAdequateRefineryCount if no RefineryTypes defined. 2020-08-05 11:29:22 +02:00
Mustafa Alperen Seki
4cc5104fde Unhardcode AI's MinimumRefineryCount numbers. 2020-08-05 11:29:22 +02:00
Curtis Shmyr
d519cabae3 Add actor experience to the Lua API 2020-08-03 18:35:50 +02:00
abcdefg30
9852e29835 Fix subs targeting naval structures by default 2020-08-03 17:50:36 +02:00
Matthias Mailänder
3a427c3630 Add a sequence reference attribute to label fallbacks. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
336656e8f7 Remove superflous warning as null is a valid value here. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
06ad9666e8 Replace burns with more modular and testable trait combinations. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
728e0c6600 If it defaults to the actor type, then it shouldn't be required. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
657e690bdd Add an image override. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
2d36d0a659 Reflect in naming that negative SelfHealing is a thing. 2020-08-02 22:15:13 +02:00
Matthias Mailänder
c42fd5d2e2 Add IsDecoration as a configurable option. 2020-08-02 22:15:13 +02:00
Trevor Nichols
10bf97eff6 Reduce severity of editorconfig and prefer multiline braces 2020-08-02 14:37:39 +02:00
Matthias Mailänder
ea9992247d Reorder string placeholders. 2020-08-02 12:00:53 +02:00
Matthias Mailänder
b90fecff76 Remove the prefixed method name. 2020-08-02 12:00:53 +02:00
Matthias Mailänder
a4fc9fea3b Unify AIUtils.BotDebug prefixes. 2020-08-02 12:00:53 +02:00
abcdefg30
b7c7eff2a2 Fix the position of the red cross in RA 2020-08-02 11:56:15 +02:00
Stuart McHattie
8c10dc406a Prevent Mac's Cmd+Q from exiting the game
This operation can be devastating if you were trying to assign a control group using Cmd + 1 but accidentally catch the Q key during gameplay.
2020-07-28 23:12:39 +01:00
Stuart McHattie
05c3861426 Add myself to the authors list 2020-07-28 23:12:39 +01:00
Matthias Mailänder
1ef5db8896 Document the built in hitshape types. 2020-07-28 23:42:26 +02:00
Andre Mohren
006a87692a Removed unused imports. 2020-07-28 18:22:51 +02:00
Oliver Brakmann
e019b70420 Fix actor previews for actors with types written in capital letters 2020-07-26 10:57:18 +02:00
Smittytron
70ec5b0344 Add flash to mission paranukes 2020-07-26 10:52:06 +02:00
abcdefg30
5401ace540 Add a death animation to Visceroids 2020-07-24 18:13:11 +02:00
abcdefg30
ab9081c852 Force enable Visceroids in the default rules 2020-07-24 18:13:11 +02:00
Matthias Mailänder
3a9b35980c Revert "Reduce order latency locally"
This reverts commit 20e5219cf4.
2020-07-21 21:58:31 +02:00
Matthias Mailänder
150439d215 Revert "Change where we send orders"
This reverts commit 616d9421d6.
2020-07-21 21:58:31 +02:00
Matthias Mailänder
b01a534a98 Revert "Refactor the OrderManager and world tick loop, improves input latency"
This reverts commit f642cead44.
2020-07-21 21:58:31 +02:00
Trevor Nichols
758b0b08d0 Add to .editorconfig additional naming rules to match this codebase's style 2020-07-21 16:15:30 +02:00
teinarss
f87ba1d8a4 Update SP maps with transformable trees 2020-07-20 14:05:52 +02:00
teinarss
67fa7bdcc9 Add TransformsNearResources 2020-07-20 14:05:52 +02:00
Matthias Mailänder
0b03aca104 Fix linter for player palettes. 2020-07-19 10:44:55 +02:00
Paul Chote
3bf61f1043 Fix incorrect rotation calculation in ThrowsShrapnel. 2020-07-19 10:41:05 +02:00
Paul Chote
ac975f4139 Convert yaml-exposed facings to WAngle. 2020-07-19 10:41:05 +02:00
abcdefg30
6d12301f88 Fix the aircraft facing slider in the editor 2020-07-18 01:45:00 +02:00
Andre Mohren
914950c4a5 When zooming using the mousewheel, zoom towards the pointer location. 2020-07-17 20:12:10 +02:00
Paul Chote
b417b267dd Store quaternion components directly.
This avoids precision loss when combining rotations.
The equivalent Euler angles are calculated for external use
but the quaternion components are preferred for any further
internal calculations.
2020-07-17 15:02:32 +02:00
Paul Chote
aae497eff1 Pass pre-combined rotations into the rendering code. 2020-07-17 15:02:32 +02:00
Paul Chote
3c9db4c2ac Add WRot.Rotate to allow rotations to be combined. 2020-07-17 15:02:32 +02:00
Paul Chote
8c3793e7ea Rename WRot.Zero to WRot.None. 2020-07-17 15:02:32 +02:00
Oliver Brakmann
60a7f53491 Fix target lines appearing not long enough on screen
Also changes the Delay attribute from ticks to milliseconds.
2020-07-16 00:28:05 +02:00
Mustafa Alperen Seki
04bfd62f2f Fix FireClusterWarhead playing wrong Report sound. 2020-07-15 23:55:01 +02:00
Paul Chote
117b8b3653 Change tiberium colors to match the original game. 2020-07-13 14:02:02 +02:00
Paul Chote
70cb0d2924 Improve TS map importer and reimport classic maps. 2020-07-13 14:02:02 +02:00
Paul Chote
c5ea496c45 Add terrain lighting definitions to TS. 2020-07-13 14:02:02 +02:00
Paul Chote
01e955ca37 Implement TS-style terrain lighting. 2020-07-13 14:02:02 +02:00
Paul Chote
fdb66c769c Change terrain layers to track sequences instead of sprites. 2020-07-13 14:02:02 +02:00
Paul Chote
38b3fbbdbe Fix [RequireExplicitImplementation] failures on event properties. 2020-07-13 14:02:02 +02:00
Paul Chote
3bc5d2d02c Add INotifyEditorPlacementInfo interface.
This allows TraitInfos to act when the actor preview is placed
in the editor, returning arbitrary data which the editor stores
and gives back if the preview is removed.
2020-07-13 14:02:02 +02:00
Paul Chote
ac7eda8ca2 Add support for rendering tinted artwork. 2020-07-13 14:02:02 +02:00
reaperrr
baf58f53b3 Adapt SpreadDamageWH to ImpactOrientation
The ImpactOrientation needs to be computed from
point of impact to target if the target wasn't hit directly.

Also adapted warhead code to use WarheadArgs consistently,
as well as pass HitShape instead of just HitShapeInfo
(both needed for future and/or downstream features).
2020-07-12 19:52:55 +02:00
reaperrr
8513a83331 Add ImpactOrientation to WarheadArgs
Allows to pass the horizontal facing/yaw
and vertical angle/pitch of the carrier
projectile to warheads for further use.

Add ImpactPosition to WarheadArgs

InflictDamage doesn't pass the impact pos
directly, and the very point of WarheadArgs
is to avoid adding more and more arguments
to the warhead methods.
2020-07-12 19:52:55 +02:00
Orb
bf7fecff10 TD Balance Summer 2020 2020-07-12 19:31:59 +02:00
Oliver Brakmann
6e1f2f636c Increase GameOverDelay for missions from 1.5 to 3 secs 2020-07-12 15:06:55 +02:00
Oliver Brakmann
8b3db6f3d6 Allow granting a condition to Carryalls currently carrying units 2020-07-12 14:36:01 +02:00
Matthias Mailänder
39d0abe982 Remove bit rot. 2020-07-12 14:32:32 +02:00
teinarss
c2026dc254 Add Discord Rich Presence 2020-07-12 14:27:59 +02:00
Matthias Mailänder
cae6c28754 Change the prefix in front of replays. 2020-07-12 13:43:05 +02:00
Sly
3b99924799 Corrected spelling error 2020-07-10 14:48:22 +02:00
Sly
b2b548b103 Corrected spelling error 2020-07-10 14:46:40 +02:00
Pavel Penev
c2e3806a77 Added a FlashPaletteEffect to RA nukes and removed from D2k 2020-07-10 13:22:54 +02:00
Pavel Penev
25500a7dda Updated a stale copyright header 2020-07-10 13:22:54 +02:00
Pavel Penev
8c394a4cb5 Created FlashPaletteEffectWarhead and removed the hardcoded flashing from NukeLaunch
Not actually hardcoded, but there was a hard dependency in NukeLaunch on FlashPaletteEffect and explicit flashing checks.
2020-07-10 13:22:54 +02:00
Pavel Penev
2e7bd4de4b Fixed a bug with the NukeLaunch's Player palette 2020-07-10 13:22:54 +02:00
Matthias Mailänder
7261322e41 Add support for Visual Studio Code. 2020-07-10 12:33:57 +03:00
abcdefg30
cada396733 Fix a crash when previewing an actor using WithCrateBody in the editor 2020-07-09 20:31:03 +02:00
Chris
9f9709f058 Add python3 support to launch-game.sh 2020-07-09 20:21:48 +02:00
abcdefg30
26fc65209d Fix the bogus Actors iterator 2020-07-09 18:11:53 +02:00
Adam Mitchell
f642cead44 Refactor the OrderManager and world tick loop, improves input latency 2020-07-09 13:48:55 +02:00
Adam Mitchell
616d9421d6 Change where we send orders 2020-07-09 13:48:55 +02:00
Adam Mitchell
20e5219cf4 Reduce order latency locally 2020-07-09 13:48:55 +02:00
Vapre
9edda21b06 Avoid three time array lookup in TraitContainer.Actors. 2020-07-09 12:16:04 +02:00
Paul Chote
6d6822ca15 Update map inits in RA/TD/D2k. 2020-07-08 20:38:30 +02:00
Paul Chote
a2269e7ee7 Convert (Dynamic)FacingInit, (Dynamic)TurretFacingInit to WAngle. 2020-07-08 20:38:30 +02:00
Paul Chote
e8f443f4a9 Remove TurretFacingsInit and PlugsInit. 2020-07-08 20:38:30 +02:00
Ivaylo Draganov
67f8452178 Add button to override duplicate hotkey bindings 2020-07-08 19:59:07 +02:00
Ivaylo Draganov
47f6e407d9 Cancel hotkey rebind on Esc key press 2020-07-08 19:59:07 +02:00
abcdefg30
889e2152a4 Cache ProjectedCellBounds during load time 2020-07-08 18:37:50 +02:00
abcdefg30
173aae1f81 Directly check the map bounds instead of converting to PPos 2020-07-08 18:37:50 +02:00
abcdefg30
baed80983b Fix the launch-game.sh newlines not displaying in the console 2020-07-07 22:30:55 +02:00
abcdefg30
b00423dc76 Correct the crash dialog title 2020-07-07 22:30:55 +02:00
abcdefg30
376ed15079 Correct the support folder location in the crash dialog 2020-07-07 22:30:55 +02:00
abcdefg30
a6d8d6cd8e Use tabs as indentation consistently 2020-07-07 22:30:55 +02:00
Paul Chote
b3ee8b447e Reduce duplication between Map and PlayerRadarTerrain. 2020-07-07 22:12:22 +02:00
Paul Chote
5f588561b6 Rewrite TS minimap rendering:
* Rename LeftColor and RightColor to MinColor and MaxColor
  These are mapped from LowRadarColor and HighRadarColor in
  the original inis, and appear to be used to set the bounding
  values for selecting a random colour, NOT for left/right
  pixels (which caused noticeably wrong banding).

* Adjust brightness based on terrain height.
  MinHeightColorBrightness and MaxHeightColorBrightness
  were chosen by trial/error to match the original
  map preview rendering.
2020-07-07 22:12:22 +02:00
Smittytron
83c53e17e0 Allow repairable tech buildings in campaign-rules 2020-07-07 19:47:10 +02:00
Smittytron
c028488894 Add Soviet 09 2020-07-07 19:47:10 +02:00
abcdefg30
b066005f7e Fix AttackBomber marking aircraft that are not in the world as in range 2020-07-06 22:30:41 +02:00
abcdefg30
87e33a75c6 Fix a crash when calling an airstrike at the map edge 2020-07-06 22:30:41 +02:00
Matthias Mailänder
4961b0943b Add a text base spy disguise decoration. 2020-07-05 21:43:12 +02:00
Smittytron
eda1e9c266 Add Counterstrike mission Siberian Conflict 1: Fresh Tracks 2020-07-05 21:33:56 +02:00
abcdefg30
bc7bf174d8 Don't tick the announcement timer when not in low power
When entering low power it will be reset to 0 again anyway
2020-07-05 20:37:17 +02:00
abcdefg30
2e06d5790b Update the PowerState only when power is added or removed 2020-07-05 20:37:17 +02:00
abcdefg30
ab8790e8f1 Style/Readability changes 2020-07-05 20:37:17 +02:00
abcdefg30
7d630e63e7 Let Railgun implement ISync 2020-07-05 18:10:14 +02:00
Smittytron
77899191f3 Add Aftermath mission Situation Critical 2020-07-05 17:21:43 +02:00
Smittytron
6871873e93 Add Counterstrike mission Fall of Greece 1: Personal War 2020-07-05 15:59:18 +02:00
Smittytron
fc4bd131cd Add Aftermath mission: Production Disruption 2020-07-05 15:58:10 +02:00
Pavel Penev
d866286f82 Added back the DescAttributes on projectiles' Inaccuracy fields 2020-07-05 13:04:18 +02:00
Pavel Penev
76dfda164e Moved projectile inaccuracy calculations to Common.Util
Also moved the InaccuracyType enum there. This also quietly adds the RangeModifiers to the calculations for all projectiles, while they were only used on Bullet so far, which seemed very wrong.
2020-07-05 13:04:18 +02:00
Pavel Penev
134d47e48c Added InaccuracyType.Absolute to projectiles 2020-07-05 13:04:18 +02:00
Pavel Penev
a2dbd5e013 Changed weapons in D2k to use the new PerCellIncrement inaccuracy
Also adjusted the inaccuracy values. This should bring inaccuracy in D2k pretty much in line with the original game, with the potential liberty of decreased inaccuracy for the Deviator tank.
2020-07-05 13:04:18 +02:00
Pavel Penev
c27412c83a Added InaccuracyType enum and updated projectiles accordingly
Also updated the inaccuracy calculations to account for the new inaccuracy type - either based on distance up to a max defined inaccuracy at max range (old style) or based on distance with each cell (1024 range) increasing the inaccuracy with a set step.
2020-07-05 13:04:18 +02:00
Pavel Penev
4143aba595 Added syncing to some Railgun projectile fields 2020-07-05 13:04:18 +02:00
Ivaylo Draganov
39ccac4022 Refactor command bar buttons to use unified widget state names 2020-07-05 11:50:45 +01:00
Ivaylo Draganov
7943f4deb6 Unify widget state image suffixes (disabled, pressed, hover, focus)
- Add a property for arrows image collection (in drop-downs, scrollbars
and production tabs)
- Add a property for separators image collection (in drop-downs)
- Add hover and disable states to the drop-down separator
- Unify button, textfield and checkbox state suffixes
2020-07-05 11:50:45 +01:00
Curtis Shmyr
2dda2d7689 Added lua IsCloaked actor property 2020-07-04 21:06:52 +02:00
Matthias Mailänder
b8f2a14ea0 Automatically upload release to itch.io using butler. 2020-07-04 20:59:10 +02:00
Oliver Brakmann
595809f090 Fix Carryalls waiting for actors on transit-only tiles 2020-07-04 16:47:17 +02:00
abcdefg30
477db9cd4a Fix travis builds not failing when errors occur during static checking 2020-07-03 21:29:25 +01:00
teinarss
67ff292d62 Refactor WorldRenderer to use less allocations 2020-07-03 17:41:41 +02:00
darkademic
ae882b85a9 Make AI randomly scan map for targets for its air squads. 2020-07-02 23:05:49 +02:00
abcdefg30
5e92915095 Fix the cargo unload regression with scripted transports
The activity is not interruptible, so we'd continue after the Move regardless
2020-07-02 21:14:35 +02:00
Smittytron
95809db03c Move Harvester overrides to campaign-rules.yaml 2020-07-02 19:29:37 +02:00
Smittytron
6d5a5121bc Remove worthless function from Allies08 2020-07-02 19:29:37 +02:00
Smittytron
86992751c7 Update Soviet-08a 2020-07-02 19:29:37 +02:00
Smittytron
2eba8b6c37 Add Soviet-08b 2020-07-02 19:29:37 +02:00
Smittytron
e1523e939d Utilize Panic function with added OnDamaged trigger 2020-06-28 17:30:24 +02:00
Smittytron
15a92f443d Replace paraprop power with scripted drops in Soviet01 2020-06-28 17:30:24 +02:00
KorGgenT
b57c68e392 Fix units walk over tunnels and under bridges. 2020-06-28 17:21:54 +02:00
Matthias Mailänder
e95fcb6bc0 Don't let blossom tree poof all at the same time. 2020-06-28 13:43:33 +02:00
Matthias Mailänder
6581fcb6a7 Add a random interval to idle animations. 2020-06-28 13:43:33 +02:00
tovl
6551337bd8 Make TS walkers AlwaysTurnInPlace. 2020-06-28 00:11:26 +02:00
tovl
137df52fdd Make infantry AlwaysTurnInPlace. 2020-06-28 00:11:26 +02:00
tovl
b79aa7eb6a Add AlwaysTurnInPlace option to Mobile. 2020-06-28 00:11:26 +02:00
Alfred Lang
1c8c49dc8e Fix do allow AI do place plugbuildings like RBG Tower on GDI Component Tower 2020-06-27 10:35:06 +02:00
Andrew Odintsov
f0c808d2fc Replace FloodFill with IEditorAction implementation 2020-06-24 19:08:54 +02:00
Curtis Shmyr
26d9ae88df Replace TargetAndAttack with a global aircraft attack function 2020-06-23 20:09:42 +02:00
Curtis Shmyr
3c7f119bb1 Add a damage parameter to Lua OnDamage callback 2020-06-23 19:58:31 +02:00
Ian T. Jacobsen
fea35923f0 Minefield now shows red when out of map bounds and Minelayer now does not get stuck at edge of map
Made it impossible to detect enemy units again

Fix whitespace
2020-06-21 20:16:57 +02:00
Paul Chote
9627776318 Add pitch and roll to TS aircraft. 2020-06-21 18:05:40 +02:00
Paul Chote
6dcde3af72 Allow voxel-based aircraft to pitch and roll. 2020-06-21 18:05:40 +02:00
Paul Chote
43717a89b5 Add Orientation getter to IFacing. 2020-06-21 18:05:40 +02:00
Matthias Mailänder
c7ba359688 Add drop pod reinforcements. 2020-06-21 17:28:24 +02:00
Matthias Mailänder
dc3dbf6d85 Remove uneccessary parentheses. 2020-06-21 17:28:24 +02:00
Smittytron
71664c85ff Fix TargetTypes regression in SnipeWeapon 2020-06-21 12:34:43 +02:00
Nikita Pozdeev
cb41be113a Fix map editor radar ignoring color from terrain 2020-06-20 14:37:23 +02:00
Matthias Mailänder
4fe7daa85e Fix juggernaut preview being disabled by default. 2020-06-19 21:30:34 +02:00
abcdefg30
f75afc6ee4 Fix a compilation error in WithInfantryBody.cs 2020-06-19 20:01:04 +02:00
abcdefg30
67fd71ab92 Add a ProjectedCellLayer and use it in Shroud.cs 2020-06-19 18:31:51 +02:00
abcdefg30
385e70552e Create CellLayerBase 2020-06-19 18:31:51 +02:00
Matthias Mailänder
1e2c67bfca Increase UPnP device discovery timeout. 2020-06-19 18:00:27 +02:00
Paul Chote
56739f87fb Allow plugs to be configured in the map editor. 2020-06-19 17:57:56 +02:00
Paul Chote
571eb7614f Support multiple turrets in the editor. 2020-06-19 17:57:56 +02:00
Paul Chote
c6c3a8c60d Make ActorPreview and EditorActorPreview wrap ActorReference. 2020-06-19 17:57:56 +02:00
Paul Chote
ae7cfa56b7 Restrict IActorPreviewInitInfo to ActorInit. 2020-06-19 17:57:56 +02:00
Paul Chote
b856613194 Add ISingleInstanceInit interface.
Inits that are logically singletons (e.g. actor
location or owner) should implement this interface
to avoid runtime inconsistencies.

Duplicate instances are rejected at init-time,
allowing simpler queries when they are used.
2020-06-19 17:57:56 +02:00
Paul Chote
86305879cb Parse Enum *ValueInit as string values, not integers. 2020-06-19 17:28:01 +02:00
Paul Chote
e5a1a8a706 Replace deprecated API usage. 2020-06-19 17:28:01 +02:00
Paul Chote
27602a4a97 Add WAngle-compatible airstrike/paratrooper APIs. 2020-06-19 17:28:01 +02:00
Paul Chote
a98e460257 Expose WAngle to Lua API and deprecate old Facing. 2020-06-19 17:28:01 +02:00
Paul Chote
0349435650 Remove deprecated Paratrooper API methods. 2020-06-19 17:28:01 +02:00
Paul Chote
c3fbdca18f Add yellow-shirt technician. 2020-06-19 17:09:13 +02:00
Paul Chote
acb5245a28 Restore correct palette and voices for RA civilians.
A new C11 infantry has been added to use the custom
c3 sprite that was originally added back when only
c1 and c2 were used in OpenRA.
2020-06-19 17:09:13 +02:00
Paul Chote
23561cd76b Add custom palette support to WithInfantryBody. 2020-06-19 17:09:13 +02:00
Paul Chote
d3ab3d7d78 Move IndexedPlayerPalette to Mods.Common and add a non-player version. 2020-06-19 17:09:13 +02:00
Matthias Mailänder
5b870be83f Let the 2nd civilian panic when his mate gets electrocuted. 2020-06-19 16:50:28 +02:00
Matthias Mailänder
6130d5622c Add a panic function to the Lua API. 2020-06-19 16:50:28 +02:00
abcdefg30
318c4e3456 Remove InitialStanceAI overrides from campaign missions 2020-06-19 16:46:34 +02:00
abcdefg30
ab701449e2 Change Subs to use the Defend stance by default 2020-06-19 16:46:34 +02:00
abcdefg30
9a3447d863 Fix airstrike and paratroopers power not removing cameras at the map edge 2020-06-19 16:04:54 +02:00
matjaeck
5280637adf Fix PickupUnit not validating cargo on first run. 2020-06-19 15:59:14 +02:00
Matthias Mailänder
3bce55ac44 Cancel the attack when no traits are active. 2020-06-19 13:43:03 +02:00
Curtis Shmyr
a3f79503ed Fix Lua DisplaySystemMessage writing twice 2020-06-16 13:11:02 +02:00
Curtis Shmyr
02d462a82c Fix copying public key to clipboard if already authed 2020-06-13 19:15:27 +02:00
Smittytron
3d17328d0d Fix regression and cleanup scu35ea 2020-06-12 23:50:44 +02:00
reaperrr
0e81abc21b Fix weapons not accounting for Air
If a weapon was aiming at a target position rather
than an actor target, it would always check target types
of the terrain below, ignoring altitude (and therefore ignoring
"InvalidTargets: Air").
2020-06-12 21:00:53 +02:00
Paul Chote
803b930405 Change IFacing.TurnSpeed to WAngle. 2020-06-12 18:35:41 +02:00
Paul Chote
6adf45bcb4 Convert IFacing.Facing and TurnSpeed to WAngle. 2020-06-12 18:35:41 +02:00
Matthias Mailänder
01417c88c5 Add missing actor reference for lint testing. 2020-06-10 19:07:14 +02:00
abcdefg30
60bbbe0d93 Fix the Death Hand launch notification playing for the player not enemy 2020-06-10 18:57:08 +02:00
Matthias Mailänder
888dfd3654 Stop the boat to remove it immediately. 2020-06-09 22:40:32 +02:00
Matthias Mailänder
12de56ff62 Fix crash notication "AlertBeep" not found. 2020-06-09 22:40:32 +02:00
Matthias Mailänder
b7cee41c54 Fix TD gunboat not updating actor map influence. 2020-06-09 22:40:32 +02:00
Paul Chote
10aac03f75 Add CompositeActorInit and simplify chronoshift inits. 2020-06-08 19:18:38 +02:00
Paul Chote
0eb0041f90 Allow ActorInits to target a specific trait by matching the @ suffix. 2020-06-08 19:18:38 +02:00
Paul Chote
b38018af9c Replace IActorInit with an abstract class.
A shared ValueActorInit<T> is introduced to reduce duplication
in the most common init cases, and an ActorInitActorReference
allow actors to be referenced by map.yaml name.
2020-06-08 19:18:38 +02:00
Unknown
4df5ac0385 Change default to ground-attack 2020-06-07 10:31:44 -05:00
Unknown
a7476bc303 Improve detail + double fire rate/half damage 2020-06-07 10:31:44 -05:00
Unknown
cc4b3cb361 Add APC ground attack sprite turret 2020-06-07 10:31:44 -05:00
Ivaylo Draganov
31a965b29a Add suffix to player name in shroud selector 2020-06-06 14:40:48 +01:00
Ivaylo Draganov
7a213338a2 Add helper method to add suffix to player name label 2020-06-06 14:40:48 +01:00
abcdefg30
fb27a25e52 Fix a crash with support powers and units without selection decorations 2020-06-06 13:46:27 +01:00
abcdefg30
534b09ae4a Fix bots not working after adminship was transferred 2020-06-03 18:30:18 +02:00
abcdefg30
341a9f370c Fix a crash in Evacuation 2020-06-02 22:53:40 +02:00
Matthias Mailänder
607d9b2d5c Fix index out of bounds exception for off world aircraft. 2020-06-02 19:25:53 +01:00
abcdefg30
507ce40ad2 Fix a crash in LegacyBridgeLayer 2020-06-02 19:22:19 +01:00
reaperrr
f58c3aed32 Use OccupiesSpace to save more trait look-ups
This time in Locomotor.IsBlockedBy.
2020-06-02 20:08:34 +02:00
reaperrr
55e85bd9ca Save Mobile look-up in BasePathSearch
By casting to OccupiesSpace and then
looking up Info.LocomotorInfo directly.
2020-06-02 20:08:34 +02:00
reaperrr
4bf614c5cd Use OccupiesSpace to avoid Mobile look-up in Move
While individual trait look-ups may be cheap,
if a large army that is currently standing still gets
its first move-including order, the look-ups of dozens
or even hundreds of actors may happen on the same tick.

Therefore this may help reducing that first-order lag spike,
at least a little bit.
2020-06-02 20:08:34 +02:00
abcdefg30
96b06c75d1 Make Resupply display target lines for all queued move activities 2020-06-02 18:23:54 +02:00
Matthias Mailänder
d261648ab0 Fix BeingCapturedCondition getting revoked from the wrong actor. 2020-06-02 10:45:49 +02:00
Paul Chote
7b81b9e806 Bullet Facing -> WAngle. 2020-06-01 21:34:32 +02:00
Paul Chote
a93aea3e4e AreaBeam Facing -> WAngle. 2020-06-01 21:34:32 +02:00
Paul Chote
2cfacc2c7d ProjectileArgs facing -> WAngle. 2020-06-01 21:34:32 +02:00
Paul Chote
6d6b21a0eb Convert Aircraft.Facing to WAngle. 2020-06-01 20:25:38 +02:00
Matthias Mailänder
7a78c37851 Add .NET Coding Conventions 2020-05-31 13:27:03 +01:00
Ivaylo Draganov
b8a9f41892 Add missing trait descriptions for cursors and unify the language 2020-05-31 00:12:04 +02:00
Ivaylo Draganov
227567dfe1 Formatting: wrap and indent long argument lists 2020-05-31 00:12:04 +02:00
Ivaylo Draganov
d5ff5c672b Add configurable cursors for entering allied actor targeters 2020-05-31 00:12:04 +02:00
Ivaylo Draganov
393f6eca3a Add configurable target cursors to various traits 2020-05-31 00:12:04 +02:00
Paul Chote
d193ef856e Fix harvest animation facing glitch. 2020-05-30 19:58:03 +02:00
abcdefg30
44d3691fa1 Assign Player.IsBot before calling INotifyCreated.Created 2020-05-30 19:47:29 +02:00
abcdefg30
27d0465891 Remove workarounds for querying the PlayerActor in Created 2020-05-30 19:47:29 +02:00
abcdefg30
52a9fcef3c Rename "Created" to "Initialize" and let it handle adding to the world 2020-05-30 19:47:29 +02:00
abcdefg30
7386816f52 Manually construct the PlayerActor to fix crashes during actor creation 2020-05-30 19:47:29 +02:00
abcdefg30
9c0075b233 Move hardcoded PlayerActorTypes to shared const variables 2020-05-30 19:47:29 +02:00
abcdefg30
e4c5700baf Remove an unused using 2020-05-30 19:47:29 +02:00
reaperrr
e1b7df8b6a Use OccupiesSpace to avoid Mobile look-ups
in PathFinder.FindUnitPath and FindUnitPathToRange.
2020-05-30 04:05:29 -05:00
Paul Chote
c999b2d778 Convert QuantizeFacing to WAngle facings. 2020-05-28 21:23:51 +02:00
Paul Chote
bfb6c671fb Change QuantizeFacing to return a facing instead of an index. 2020-05-28 21:23:51 +02:00
Paul Chote
7c6ec577dc Rewrite ActorInit queries. 2020-05-28 19:04:53 +02:00
Paul Chote
626b40f31b Account for ramps in terrain height calculations. 2020-05-28 09:41:55 -05:00
Paul Chote
5af12440ba Replace MapGrid.CellCorners with a new CellRamp struct. 2020-05-28 09:41:55 -05:00
Paul Chote
4614f6febe Cache cell ramps to avoid repeated tileset lookups. 2020-05-28 09:41:55 -05:00
Pavel Penev
1354ffc32e Added multiple production speedup to D2k
Based on the specification in issue 18051.
2020-05-27 10:32:35 +02:00
Pavel Penev
3723939c99 Adjusted D2k build times to match the original game
Removed custom production queue speedups and custom actor build time slowdowns and adjusted BuildDurations based on the specifications in issue 18051.
2020-05-27 10:32:35 +02:00
Pavel Penev
e099739e13 Reduced HitShape radius of D2k units to minimum
Since they are only used to hold the armor type anyway.
2020-05-27 10:28:25 +02:00
Pavel Penev
21a48cc41d Switched D2k to use the new DamageCalculationType
This brings D2k in line with the damage model of the original game.
2020-05-27 10:28:25 +02:00
Pavel Penev
4740266308 Added DamageCalculationType enum to SpreadDamageWarhead 2020-05-27 10:28:25 +02:00
Pavel Penev
78139413d7 Equalized the DoImpact methods in damage warheads 2020-05-27 10:28:25 +02:00
Pavel Penev
f0578a75f4 Cleaned up DamageWarhead
Reordered methods and fixed access modifiers. Also removed unused using statements from warheads.
2020-05-27 10:28:25 +02:00
abcdefg30
7a0e55a02a Restore trailing whitespaces to windows batch scripts 2020-05-26 22:57:11 +02:00
reaperrr
f132bac80d Add Tree armor type and remove tree-only warheads
This simplifies #12467.
Using a tree-exclusive amor type is far more efficient
than adding more warheads, which cost performance
due to their huge Spread

This also restores the 100% efficiency vs. trees for
some of the incendiary nuke warheads (which have
reduced efficiency vs. Wood since #13643).

Note: Atomic had two tree-only warheads with a Delay
of 15, I believe the first one to be a copy-paste error
and moved the damage to the regular SpreadDamage
with a Delay of 10.
2020-05-26 22:51:29 +02:00
reaperrr
109ea4fe5b Fix Barrel explosion damage 2020-05-26 22:46:26 +02:00
Matthias Mailänder
f33feafd0e Add TurnOnIdle. 2020-05-25 13:07:19 +02:00
reaperrr
9195356e3a Upgrade DepthCharge effect setup
- med_explosion instead of small_explosion on surface hit
- only play explosion on surfaced subs
- only play h2obomb2.aud when hitting a submerged sub
2020-05-24 13:26:40 +02:00
reaperrr
eff91108f4 Make RA ships show both explosion and splash
...when destroyed, to imply sinking.
2020-05-24 13:26:40 +02:00
reaperrr
e40c0516e6 Adapt RA warheads to new target types 2020-05-24 13:26:40 +02:00
reaperrr
c580a94ab7 Fix RA weapon ValidTargets 2020-05-24 13:26:40 +02:00
reaperrr
6a545bb942 Streamline RA target types
No more sharing of target types between terrain
and actors (except bridges), removed 'Ground(Actor)'
from WaterActors (was only used by weapons/warheads,
which can just list both ground- and water types.
2020-05-24 13:26:40 +02:00
Matthias Mailänder
672bd2d9fe Don't crash when putting the trait on the World actor. 2020-05-24 12:36:26 +02:00
Ivaylo Draganov
3ab4a584ab Remove trailing white-space from various files 2020-05-23 11:38:44 +02:00
Ivaylo Draganov
150d02ac0d Remove trailing white-space from lua files 2020-05-23 11:38:44 +02:00
Ivaylo Draganov
6d26f60904 Remove trailing white-space from yaml files 2020-05-23 11:38:44 +02:00
abcdefg30
b42276953f Fix a crash when a harvester is rebuilt in cnc64gdi01 2020-05-22 21:25:58 +02:00
abcdefg30
7c290b9f76 Fix not all harvesters in cnc64gdi01 being rebuilt 2020-05-22 21:25:58 +02:00
abcdefg30
51fe1d6629 Let AI autotargeting in D2k ignore sandworms 2020-05-22 20:40:36 +02:00
Matthias Mailänder
8f558d2b47 Add a bullet bounce sound. 2020-05-21 14:44:13 +02:00
abcdefg30
3f5fadf2e9 Move stray update rules into the correct subfolder 2020-05-21 14:08:14 +02:00
abcdefg30
07c16cee1d Add TargetTypes to HealActorsCrateAction 2020-05-21 14:08:14 +02:00
abcdefg30
24130dfcdc Add an update rule for the RenameHealCrateAction rename 2020-05-21 14:08:14 +02:00
abcdefg30
15a2341a91 Rename HealUnitsCrateAction to HealActorsCrateAction 2020-05-21 14:08:14 +02:00
Paul Chote
86f61298e6 Replace ITraitInfo interface with TraitInfo class. 2020-05-21 13:01:04 +02:00
abcdefg30
3cd7ec3878 Make the TS Test AI build aircraft 2020-05-21 10:05:05 +02:00
abcdefg30
8b13d3e4c7 Fix resupply not displaying target lines correctly 2020-05-19 22:11:20 +02:00
Ivaylo Draganov
327d451abc Add trim_trailing_whitespace to .editorconfig 2020-05-18 17:39:24 +02:00
atlimit8
2dac16ee02 add SquadManagerBotModuleInfo.IgnoredEnemyTargetTypes 2020-05-17 22:02:32 +01:00
abcdefg30
d4b92a19d7 Remove bogus weapon override defintions from TD campaign maps 2020-05-17 13:39:51 +01:00
tovl
e0357596f5 Correct aircraft repulsion direction when outside of the map. 2020-05-17 12:46:17 +02:00
atlimit8
1ef27d18c1 check name for Actor.GrantCondition() 2020-05-17 12:33:29 +02:00
Matthias Mailänder
1d2d8ed107 Don't hard-code the transparent background color. 2020-05-16 22:28:52 +01:00
reaperrr
1bf01bc214 Remove WaterStructure TargetType from RA SYRD/SPEN
- only used for auto-targeting
- inconsistent with their fakes (which didn't have this)
- unnecessary, since the 'Ship' target type covers all
  surface water actors we want to be auto-targetable by default,
  while 'Structure' is enough to add syard/spen in AttackAnything.
2020-05-15 08:22:02 +02:00
reaperrr
0015deca47 Fix TS Tiberium Fiend target types
Was missing Creep, preventing actors from
auto-targeting it (unlike all other critters).
2020-05-15 08:22:02 +02:00
reaperrr
ddfdc6e90f Clean up TD weapon ValidTargets
- Missiles can now force-fire on water like other weapons
- Superweapons can now target empty water
- made Chemspray null InvalidTargets to avoid yaml-merge issues
- Improved APCGun effect warhead perf by ignoring actors
- removed stale Tiberium weapon mission overrides
2020-05-15 08:22:02 +02:00
abcdefg30
5db2ad54f2 Fix a scripting error in nod04b 2020-05-14 22:40:44 +01:00
teinarss
b8a5750529 Add map.contains check to CanStayInCell 2020-05-14 20:06:21 +02:00
Andrew Odintsov
98d5b8c7cc Remove redundant call to OnTextEdited 2020-05-14 11:01:48 +02:00
Andrew Odintsov
3f34154a1e Add OnTextEdited call to RemoveSelectedText
This would allow expty text box to be processed and disable filtering
2020-05-14 11:01:48 +02:00
thisisjacob
3119f831b3 Added notice for entering vehicles in docs 2020-05-13 17:25:49 -05:00
thisisjacob
8a07b762a2 Changed documentation for OnEnterComplete 2020-05-13 17:25:49 -05:00
Matthias Mailänder
551ab2fc59 Cache the footprint LINQ for performance. 2020-05-12 20:53:05 -05:00
Zimmermann Gyula
99957e57b9 Update the default mods. 2020-05-12 20:53:05 -05:00
Matthias Mailänder
be2c59bc6e Add upgrade rule to convert ranges to footprints. 2020-05-12 20:53:05 -05:00
Zimmermann Gyula
57f9a49b66 Use footprints in ChronoshiftPower. 2020-05-12 20:53:05 -05:00
Zimmermann Gyula
54bd0eb99d Use footprints in GrantExternalConditionPower. 2020-05-12 20:53:05 -05:00
Zimmermann Gyula
485faac294 Implement SupportPower.CellsMatching. 2020-05-12 20:53:05 -05:00
Andrew Odintsov
d531d6f3ef Simplify groupActors condition 2020-05-12 15:06:50 +02:00
Andrew Odintsov
3a9fdb82f5 Add IsInWorld check for controlled groups 2020-05-12 15:06:50 +02:00
reaperrr
5024ae1156 TS ClusterMissile typo fix 2020-05-10 15:20:25 +02:00
abcdefg30
0135dd9ed3 Use inheritance to shorten the desert shellmap custom rules 2020-05-09 14:36:50 -05:00
abcdefg30
a6e9b86bbe Remove AnnounceOnKill Tanya overwrites from the desert shellmap 2020-05-09 14:36:50 -05:00
abcdefg30
237c4444b5 Remove DeathSounds overwrites from the desert shellmap 2020-05-09 14:36:50 -05:00
reaperrr
a68467292e Use TargetType.Invalid checks instead of IsValidFor 2020-05-09 17:59:23 +02:00
reaperrr
978c69d0c3 Make Warhead.IsValidTarget method protected
IsValidTarget is never called from outside warheads.
2020-05-09 17:59:23 +02:00
reaperrr
3eabc59921 Make resource warheads AirThreshold-aware 2020-05-09 17:59:23 +02:00
reaperrr
2b3d99fac2 Sanitize resource warheads
- Fix potential crash due to invalid target (no CenterPosition)
- Fix potential crash on multiple ResourceLayers
2020-05-09 17:59:23 +02:00
reaperrr
2bdefe0e9e Move AirThreshold to Warhead
To reduce duplication and for later use in more warheads.
2020-05-09 17:59:23 +02:00
reaperrr
de81fc2aca Minor CreateEffectWarhead optimization
Palette only matters if we actually display an explosion.
2020-05-09 17:59:23 +02:00
reaperrr
b514e0a6e7 D2k yaml comment removal
Naming the warhead for its purpose is cleaner
than comments, in my opinion.
2020-05-09 17:56:08 +02:00
reaperrr
715dfa4541 Use args in FireClusterWarhead methods
Instead of directly passing damage modifiers.
2020-05-09 17:56:08 +02:00
reaperrr
ac57a37224 DamageWarhead polish
Use BitSet.IsEmpty.
2020-05-09 17:56:08 +02:00
Paul Chote
afd620b092 Reimplement ClassicQuantizeFacing using a look-up-table. 2020-05-09 10:40:50 -05:00
Paul Chote
552bceb07c SpriteEffect facing -> WAngle. 2020-05-09 10:20:23 -05:00
Paul Chote
fe58ed1283 Animation facing -> WAngle. 2020-05-09 10:20:23 -05:00
Paul Chote
361e2d463c ISpriteSequence facing -> WAngle. 2020-05-09 10:20:23 -05:00
atlimit8
259c8d2c98 Merge ConditionManager trait directly into Actor 2020-05-09 15:46:11 +02:00
abcdefg30
e12c1dc9aa Retire the "release-20190314" path 2020-05-09 13:08:10 +01:00
abcdefg30
7fb49e383d Update and fix the latest update path and rules 2020-05-09 13:08:10 +01:00
Matthias Mailänder
1df3e28253 Initialize RangeModifiers. 2020-05-08 20:58:27 +02:00
Paul Chote
bacec2689d Remove error message duplication. 2020-05-08 15:17:44 +02:00
jrb0001
bf397591f9 Implement IPv6 support for server and direct connect 2020-05-08 15:17:44 +02:00
Matthias Mailänder
bd1a936c7a Add the armed technician. 2020-05-08 09:42:14 +02:00
Matthias Mailänder
0871d6e321 Setup the Firestorm civilian voices. 2020-05-08 09:42:14 +02:00
abcdefg30
265d296db6 Add infiltration sounds support to InfiltrateForSupportPowerReset 2020-05-05 15:16:09 -05:00
Matthias Mailänder
5ec136b57c Don't play the make animation on the wall sprite body. 2020-05-05 13:23:58 +02:00
atlimit8
f1e8f9c9d0 Fixed PNG frame count calculation
This fixes the order of operations and rounding issue, making it columns * rows.
2020-05-02 23:14:31 +02:00
Paul Chote
f03841c4e4 Hide selection decorations when spectators zoom too far out. 2020-05-02 14:30:04 -05:00
Pavel Penev
0ae58ff0ea Adjusted damage spread ranges on weapons in D2k to match the original game 2020-05-02 20:22:01 +01:00
Pavel Penev
390c1899ca Changed weapons in D2k to have a linear damage falloff to match the original game 2020-05-02 20:22:01 +01:00
Matthias Mailänder
89aa6d1e4e Don't attack during buildup. 2020-05-02 20:07:09 +01:00
Matthias Mailänder
24638b02a9 Fix turret build sounds. 2020-05-02 20:07:09 +01:00
Matthias Mailänder
4b0ab6ab37 Add the crumble overlay to the turrets. 2020-05-02 20:07:09 +01:00
Matthias Mailänder
1f02d9f141 Add the make animation for the turrets. 2020-05-02 20:07:09 +01:00
Matthias Mailänder
0103c38c13 Don't change the animation when it's not your turn. 2020-05-02 20:07:09 +01:00
Matthias Mailänder
3cc76f91b4 Fix the make animation for the silos. 2020-05-02 20:07:09 +01:00
abcdefg30
10dc248f07 Remove unused usings from BaseBuilderBotModule 2020-05-02 14:05:44 -05:00
abcdefg30
d1f89c6217 Update the Desc of GrantCondition 2020-05-02 14:05:44 -05:00
abcdefg30
76ba4fc32d Use a modular AI in soviet05 2020-05-02 14:05:44 -05:00
abcdefg30
37f90fff44 Throw LuaExceptions instead of InvalidDataExceptions in ConditionProperties 2020-05-02 14:05:44 -05:00
abcdefg30
9fa6da3bc7 Add PlayerConditionProperties 2020-05-02 14:05:44 -05:00
abcdefg30
aa8cf237ab Make Greece the owner of all starting actors in soviet05 2020-05-02 14:05:44 -05:00
Matthias Mailänder
c131728aa4 Give more context when crashing during .png sheet loading. 2020-05-02 13:26:06 -05:00
Paul Chote
2abd137494 Remove OpenRA.PostProcess.exe.
The LAA flag is now set when packaging
Windows release installers.
2020-05-02 17:26:54 +02:00
Paul Chote
90815ace59 Add a decoration glyph for friendly units in tunnels. 2020-05-02 16:16:16 +02:00
Matthias Mailänder
53d916d7f1 Add some basic error handling to png metadata writing. 2020-05-02 04:13:34 -05:00
reaperrr
2b4035979b Make all warheads use WarheadArgs
Instead of passing damageModifiers directly.
2020-05-02 03:09:18 +03:00
Matthias Mailänder
38cdc93010 Default the optional effect sequence to null 2020-05-01 19:43:36 +03:00
Matthias Mailänder
346dad3898 Remove trailing spaces. 2020-05-01 19:43:36 +03:00
Matthias Mailänder
42256bc262 Add an out of bounds check. 2020-05-01 19:41:06 +03:00
Matthias Mailänder
70babb4067 Validate the target before querying it's center position. 2020-05-01 16:53:29 +02:00
Matthias Mailänder
3603e6373d Check for invalid targets. 2020-04-30 01:42:19 -05:00
Matthias Mailänder
bd1760682f Rename WithNukeLaunch* traits to WithSupportPower*. 2020-04-30 01:37:05 -05:00
Matthias Mailänder
52d0490f95 Supersede INotifyNuke 2020-04-30 01:37:05 -05:00
Matthias Mailänder
b3b0aa75ae Add new interfaces for support powers. 2020-04-30 01:37:05 -05:00
Paul Chote
e42d177920 Include selection decorations when calculating ScreenMap bounds. 2020-04-30 00:09:57 +02:00
Paul Chote
c2156af7b0 Restore missing minelayer pips. 2020-04-29 22:59:48 +02:00
dnqbob
86394eb56c "FindEnemy" functions ignore hidden actors
(helped by reaperrr and abcdefg30)
2020-04-28 15:35:02 +02:00
dnqbob
2d7790f5e4 StateBase.cs modified:
1. Optimize & move "ammo" related function from "AirStates.cs" to StateBase.cs

2. Optimize & move "IsRearm" function from "AirStates.cs" to StateBase.cs, name changed to "IsRearming"

(optimized by reaperrr)
2020-04-28 15:35:02 +02:00
Matthias Mailänder
3df43529a6 Document BitSet<TargetableType> 2020-04-27 16:06:32 +02:00
Matthias Mailänder
a1c9b27057 Add InfiltrateForSupportPowerReset 2020-04-27 16:06:32 +02:00
abcdefg30
089dd233a5 Correct the support dir location in ExtractSettingsDocsCommand 2020-04-26 12:38:11 +01:00
Matthias Mailänder
86a7a0bd6c Move Render*Circle traits to their base traits. 2020-04-26 10:30:50 +02:00
Paul Chote
7ebca36a9c Disable debug callbacks on Intel HD 4000. 2020-04-25 21:03:43 +02:00
Paul Chote
d5aed5a88a Expose GL Profile in settings menu. 2020-04-25 21:03:43 +02:00
Paul Chote
dac1f270ce Restore legacy OpenGL 2.1 support. 2020-04-25 21:03:43 +02:00
Paul Chote
839be24053 Replace PreferGLES settings flag with GLProfile enum. 2020-04-25 21:03:43 +02:00
Paul Chote
91c4179f05 Split GLProfile from GLFeatures. 2020-04-25 21:03:43 +02:00
Matthias Mailänder
a4b427bfac Clarify AttackPanicChance and add PanicChance. 2020-04-24 18:38:54 +02:00
reaperrr
250f5bec18 Misc yaml style fixes 2020-04-24 18:22:35 +02:00
reaperrr
336e2a10e0 Fixed RA STNK turret not using fudged facings 2020-04-24 18:22:35 +02:00
reaperrr
b5e9b7635e MoveClassicFacingFudge update rule 2020-04-24 18:22:35 +02:00
reaperrr
0df7fa1596 Add sequence update rule support 2020-04-24 18:22:35 +02:00
reaperrr
c10487d635 Move ClassicFacingFudge support to Mods.Cnc
This moves the TD/RA-specific re-mapping of sprite facings
and coordinates to Mods.Cnc.
2020-04-24 18:22:35 +02:00
abcdefg30
bc9b3bef74 Fix a crash when completing objectives in Allies06 out of order 2020-04-23 21:56:46 +01:00
abcdefg30
e57462e7ca Make attack moving and guarding use a grouped order 2020-04-21 01:35:40 -05:00
abcdefg30
78bf27709f Add basic support for grouped orders 2020-04-21 01:35:40 -05:00
Matthias Mailänder
fc84cd9204 Add an is in world check to fix a crash. 2020-04-21 01:15:43 -05:00
Matthias Mailänder
e361f7b246 The category field has been pluralized. 2020-04-19 15:21:10 +02:00
Matthias Mailänder
b0497b7505 Fix double whitespace. 2020-04-19 15:21:10 +02:00
Zimmermann Gyula
a894e31fa5 Remove now obsolete tileset palette entry. 2020-04-19 15:21:10 +02:00
Matthias Mailänder
dd062adec2 Add descriptions as those are not obvious in this context. 2020-04-18 13:56:26 -05:00
Matthias Mailänder
d187575a2c Make support power icons configurable and testable. 2020-04-18 13:56:26 -05:00
teinarss
85096c4ba2 Update CoordinateTest to be compatible with new nunit version. 2020-04-18 11:36:25 -05:00
teinarss
e13fd693c3 Add Nuget packages for all dependencies 2020-04-18 11:36:25 -05:00
Mustafa Alperen Seki
cc35512472 Add a trait to reveal the whole map when conditions are met. 2020-04-18 10:49:25 -05:00
abcdefg30
4e548291ce Treat transit-only tiles as invalid locations for minelayers 2020-04-18 13:35:06 +01:00
abcdefg30
3ba86f329f Remove Game.HasInputFocus 2020-04-17 22:26:03 -05:00
Mustafa Alperen Seki
5b34af0f12 Change all instances of ToLower() to ToLowerInvariant() 2020-04-17 17:01:42 -05:00
Adam Mitchell
0a9eb1ff83 Fix units stuck on transit-only when resupplying 2020-04-17 11:13:46 +02:00
abcdefg30
942dd0e5f7 Adapt the utility commands to import crates as well 2020-04-17 10:52:59 +02:00
abcdefg30
400102f3d3 Remove a TODO about grey nod colors 2020-04-17 10:52:59 +02:00
abcdefg30
9ccdeb3d36 Set the wcrate and scrate sequences up 2020-04-17 10:52:59 +02:00
abcdefg30
7e0f0dd2d2 Add missing money crates to TD campaign missions 2020-04-17 10:52:59 +02:00
abcdefg30
d920cbb7f6 Move money crates to a default in the shared campaign rules 2020-04-17 10:52:59 +02:00
Paul Chote
417677e6f4 Work around and explain color picker conversion issue. 2020-04-17 10:41:08 +02:00
Paul Chote
33f3038316 Fix map-specific factions remaining selected when changing map. 2020-04-16 16:49:00 +02:00
Paul Chote
429dbe3e0c Block profiles with revoked keys from joining auth-only servers. 2020-04-16 16:43:10 +02:00
abcdefg30
471fc44751 Add more engineers to the wave in nod07
Otherwise buildings will only be damaged
2020-04-16 13:21:33 +02:00
abcdefg30
1b8e346307 Fix APC reinforcements in nod07a and b 2020-04-16 13:21:33 +02:00
abcdefg30
a456583a08 Fix a crash in nod07b 2020-04-16 13:21:33 +02:00
Paul Chote
a63c17baab Disable IP tooltip in skirmish games. 2020-04-15 23:16:24 +02:00
Paul Chote
9c4faddc0f Switch GeoIP database from MaxMind to IP2Location.
The IP2Location data is licensed under CC BY-SA, which
allows us to distribute the database with releases.
2020-04-15 23:16:24 +02:00
Paul Chote
6828c4c1e9 Fix long player locations overrunning the player tooltip. 2020-04-15 23:16:24 +02:00
Matthias Mailänder
80131e7ec0 Group readonly fields. 2020-04-15 21:42:50 +02:00
Matthias Mailänder
ac381a6f58 Allow multiple ResourceRenderer traits. 2020-04-15 21:42:50 +02:00
Matthias Mailänder
521b516bf9 Mark suggested fields as readonly. 2020-04-15 21:42:50 +02:00
netnazgul
6a825f8e60 Modify preset colors to not get flagged by color validator 2020-04-14 18:31:18 +02:00
Paul Chote
f0a243ca10 Fix mine layer desync.
World.FogObscures depends on the local RenderPlayer and should not
be used from simulation code!
2020-04-12 23:06:55 +02:00
Matthias Mailänder
e5457a3390 Allow wall renderers in mod code. 2020-04-11 16:29:00 +02:00
Matthias Mailänder
47e21f8bef Remove unused using. 2020-04-11 16:29:00 +02:00
Matthias Mailänder
331b854e4e Add a lint check for production bar types. 2020-04-10 21:00:26 +02:00
Matthias Mailänder
274bc9cbba Add missing sequence reference. 2020-04-10 20:56:54 +02:00
Matthias Mailänder
827f8d95b4 Remove unused using. 2020-04-10 20:56:54 +02:00
Matthias Mailänder
2946dd35d5 Spaces to tabs. 2020-04-10 20:14:39 +02:00
Matthias Mailänder
74d884787d Remove trailing tabs/spaces. 2020-04-09 22:32:05 +02:00
Matthias Mailänder
5516e16fb8 Make the default player color configurable in mod.yaml 2020-04-09 22:32:05 +02:00
reaperrr
cadf4eb322 Limit TS fona to temperate theater
Their art wasn't drawn with snow terrain in mind,
so no point in allowing to place them on snow maps.
2020-04-07 22:04:16 +02:00
Matthias Mailänder
269249e86e Fix the fona sequence definitions. 2020-04-07 22:04:16 +02:00
Matthias Mailänder
30f87d2308 Port some Translucent=yes from Art.ini 2020-04-07 22:00:31 +02:00
Zimmermann Gyula
8b7e72b95e Add three additional blending modes. 2020-04-07 22:00:31 +02:00
abcdefg30
1e64048956 Cache PlayerResources and unit cost in Resupply 2020-04-07 21:27:16 +02:00
abcdefg30
8512e696f5 Add Creeps as enemy in all D2k missions 2020-04-07 21:19:08 +02:00
abcdefg30
6a03a9ec5f Fix a yaml error in GDI08a 2020-04-07 20:59:17 +02:00
netnazgul
5e04c99d57 Fix tile errors on the map "Pie of Animosity" 2020-04-07 20:53:28 +02:00
Mustafa Alperen Seki
82f15491c0 Allow Engineers in RA to enter undamaged (Camo) PillBoxes. 2020-04-03 04:09:43 -05:00
Mustafa Alperen Seki
101843fbb7 Make EngineerRepairable conditional. 2020-04-03 04:09:43 -05:00
Zimmermann Gyula
9e534f3804 Add damagetypes to repairing. 2020-03-31 01:10:51 -05:00
Punsho
ca3cfc0184 RA Balance patch 2020-03-29 21:57:12 +02:00
Paul Chote
d62fb901e2 Fix actors with no footprint leaving stale data when deleted. 2020-03-29 21:10:45 +02:00
Matthias Mailänder
73a2b59c2c Add additional notification support to infiltration. 2020-03-29 12:00:16 -05:00
Paul Chote
2c7a56625c Move selectableActor check inside InputOverridesSelection. 2020-03-29 13:20:10 +02:00
Ivaylo Draganov
e2572b214f Adjust spacing and width in editor category dropdown 2020-03-28 20:41:03 +00:00
abcdefg30
d22cd3a74f Adjust the map visibility panel height 2020-03-28 20:41:03 +00:00
abcdefg30
0c8fcedfdf Start with randomized wind strength 2020-03-28 19:46:47 +01:00
Paul Chote
99009c37ce Fix and simplify WeatherOverlay:
- Fix rendering issues
- Track particles in world pixels instead of screen pixels
- Removed un/underused fade in/out support
- Update wind once per tick instead of once per particle
- Make Particle struct readonly
2020-03-28 19:46:47 +01:00
Paul Chote
d9f5771778 Make the right edge of the airfield transitable. 2020-03-28 19:13:14 +01:00
Paul Chote
d35b5070fb Fix minelayers leaking enemy mine positions through the fog. 2020-03-28 18:49:07 +01:00
Paul Chote
02f41f9afc Fix SpriteEffect updating twice in the first tick. 2020-03-28 17:12:25 +01:00
abcdefg30
c797aa1d5e Change syrf to syrd on the desert shellmap 2020-03-27 19:24:22 +01:00
Paul Chote
b2f7f67756 Fix and simplify ScrollPanelWidget thumb rect calculation. 2020-03-26 16:54:46 +01:00
dnqbob
09014ab6d5 transformation can pass exp to new actor 2020-03-26 02:04:44 -05:00
Matthias Mailänder
8f8747d65e Always show the building fake tags 2020-03-26 00:52:24 -05:00
Ivaylo Draganov
be19e137e2 Align lobby bits in the player tab in TD 2020-03-25 13:01:49 +01:00
Paul Chote
3155291064 Restore ability to configure RMB orders + RMB panning. 2020-03-25 12:36:21 +01:00
Paul Chote
a5b22e6a36 Remove text caching from CncLoadScreen.
We have repeatedly failed at invalidating these
cached values when things change, so the small perf
win is not worth the hassle.
2020-03-25 12:20:14 +01:00
Paul Chote
9c251e8b6a Fix detection circle line rendering. 2020-03-24 20:59:46 +01:00
Matthias Mailänder
6056568182 Remove unused terrain type. 2020-03-24 19:48:54 +01:00
Zimmermann Gyula
7b7c1da18d Add a shared parallel production queue. 2020-03-24 13:35:15 -05:00
Paul Chote
fb5b4b3547 Rename Defense button tooltip to Support. 2020-03-24 13:13:18 -05:00
Paul Chote
19918d485e Disable plugs when there are no sockets to place them. 2020-03-24 13:13:18 -05:00
Paul Chote
45c6c6ba10 Fix Waste Refinery bib. 2020-03-24 13:13:18 -05:00
Paul Chote
0e436bc686 Move plugs and superweapons to Building queue. 2020-03-24 13:13:18 -05:00
Ivaylo Draganov
b0dfea0a09 Adjust the stroke of the muted indicator glyph 2020-03-24 16:56:49 +01:00
Paul Chote
2c4e6c4188 Remove special-case rollover rendering. 2020-03-24 00:07:10 -05:00
Paul Chote
9f3254dbd1 Implement isometric selection boxes for TS structures. 2020-03-24 00:07:10 -05:00
Paul Chote
88cdad4189 Add support for polygon selection shapes. 2020-03-24 00:07:10 -05:00
Paul Chote
4ba50a4379 Remove IEquatable from ActorBoundsPair. 2020-03-24 00:07:10 -05:00
Paul Chote
2b6c104011 Update RA decorations. 2020-03-24 00:07:10 -05:00
Paul Chote
4b446d100e Update D2k decorations. 2020-03-24 00:07:10 -05:00
Paul Chote
f9ca2114a9 Update TS decorations. 2020-03-24 00:07:10 -05:00
Paul Chote
afc9c6ef85 Update TD decorations. 2020-03-24 00:07:10 -05:00
Paul Chote
ac200f6173 Rework decoration renderable traits:
- Removed implicit pip definitions and IPips interface.
  New decoration traits have been added to render them.
  Pip types are no longer hardcoded in OpenRA.Game.

- Decoration rendering is now managed by SelectionDecorations(Base),
  which allows us to remove assumptions about the selection box
  geometry from the decoration traits.

- RenderNameTag has been replaced by WithNameTagDecoration, which is
  an otherwise normal decoration trait.

- Unify the configuration and reduce duplication between traits.

- Removed hardcoded references to specific selection box renderables.

- Remove legacy cruft.
2020-03-24 00:07:10 -05:00
Paul Chote
73a78eadb1 Move Interactable and Selectable to Mods.Common. 2020-03-24 00:07:10 -05:00
Matthias Mailänder
c5139fb6c2 Remove the hard-coded ban of placing buildings on resources. 2020-03-23 23:48:33 -05:00
Paul Chote
9faf9aa1b9 Replace deprecated native OpenAL with OpenAL Soft on macOS. 2020-03-23 11:13:31 +01:00
unknown
3c2e9be248 Add gdi09ea 2020-03-21 21:09:52 +01:00
Matthias Mailänder
5b59f6612f Remove .lua scripts from the .NET solution file. 2020-03-21 17:30:26 +00:00
abcdefg30
3d69363f35 Add support for destroying enemy carryalls 2020-03-21 10:58:17 +01:00
abcdefg30
b580b4fd33 Add support for an announcement function for carryall reinforcements 2020-03-21 10:58:17 +01:00
abcdefg30
74f86d70f8 Add Ordos06a 2020-03-21 10:58:17 +01:00
abcdefg30
3959104f9b Let VS2019 remove a duplicate line from the solution 2020-03-21 10:58:17 +01:00
Paul Chote
1ff037a257 Remove invalid caching from GCOT. 2020-03-20 17:43:24 +01:00
abcdefg30
32700df117 Fix the settings tooltip container being overwritten ingame 2020-03-20 16:06:06 +01:00
Matthias Mailänder
b4edec215e Fix spy ignoring the target's faction. 2020-03-19 23:11:38 +01:00
Michael Silber
dffa1e45f4 Add gdi08a 2020-03-17 19:04:36 +01:00
Paul Chote
df3b6dde34 Update macOS launcher to fix "View Logs" button. 2020-03-16 20:15:58 +01:00
Matthias Mailänder
834bbf467e Make GlobalLightingPaletteEffect public 2020-03-16 11:10:30 +01:00
Paul Chote
4d4f94208e Cache CandidateMovementCells within the same tick. 2020-03-12 17:07:14 +01:00
Paul Chote
416713de0c Fix infantry switching subcells and blocking eachother while moving. 2020-03-11 15:40:12 +01:00
Paul Chote
c523ca8efe Fix FreeSubCell ignoring preferred subcell requests. 2020-03-11 15:40:12 +01:00
Paul Chote
9acea56108 Fix pathing across transit-only cells. 2020-03-11 15:40:12 +01:00
Paul Chote
44a7422375 Fix variable naming in Locomotor. 2020-03-11 15:40:12 +01:00
abcdefg30
0d0e7eb179 Fix aircraft not taking off properly 2020-03-08 17:20:39 +01:00
abcdefg30
ea6c840343 Fix the panic chance calculation in ScaredyCat 2020-03-08 16:33:23 +01:00
abcdefg30
d2db0913ac Fix the missle jamming chance calculation 2020-03-08 16:33:23 +01:00
Punsho
3721dae74d Making missiles properly go over terrrain and track air units 2020-03-07 13:00:28 +01:00
Matthias Mailänder
9050a2447b Add remappable support to production icons. 2020-03-04 22:02:30 +01:00
1431 changed files with 54678 additions and 36028 deletions

View File

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

2
.gitignore vendored
View File

@@ -26,7 +26,7 @@ mods/*/*.pdb
/*.exe
/*.exe.config
thirdparty/download/*
*.mmdb.gz
IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
# backup files by various editors
*~

View File

@@ -3,6 +3,8 @@
language: csharp
mono: 6.4.0
os: linux
dist: xenial
jobs:
include:
@@ -12,17 +14,13 @@ jobs:
if: tag IS present
osx_image: xcode10
cache:
directories:
- thirdparty/download
addons:
apt:
packages:
- lua5.1
- dpkg
- zsync
- markdown
- imagemagick
# Environment variables
env:
@@ -34,25 +32,28 @@ env:
# call OpenRA to check for YAML errors
# Run the NUnit tests
script:
- travis_retry make all-dependencies
- make all
- test "$TRAVIS_OS_NAME" == "linux" && make check || echo "Skipping check"
- test "$TRAVIS_OS_NAME" == "linux" && make check-scripts || echo "Skipping scripts check"
- test "$TRAVIS_OS_NAME" == "linux" && make test || echo "Skipping tests"
- test "$TRAVIS_OS_NAME" == "linux" && make nunit || echo "Skipping nunit tests"
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
make check || travis_terminate 1;
make check-scripts || travis_terminate 1;
make test || travis_terminate 1;
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult OpenRA.Test.dll || travis_terminate 1;
fi
# Only watch the development branch and tagged release.
branches:
only:
- /^release-.*$/
- /^playtest-.*$/
- /^pkgtest-.*$/
- /^devtest-.*$/
- /^prep-.*$/
- bleed
# Notify developers when build passed/failed.
notifications:
irc:
if: repo = OpenRA/OpenRA
template:
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
channels:
@@ -61,26 +62,31 @@ notifications:
skip_join: true
before_deploy:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
sudo dpkg -i nsis-common_3.04-1_all.deb;
sudo dpkg -i nsis_3.04-1_amd64.deb;
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
fi;
- export PATH=$PATH:$HOME/usr/bin
echo ${TRAVIS_REPO_SLUG};
if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
fi;
fi
- export PATH=${PATH}:${HOME}/usr/bin
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
- cd packaging
- mkdir build
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
- if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
./upload-itch.sh ${TRAVIS_TAG} ${PWD}/build/;
fi
deploy:
provider: releases
api_key:
secure: "g/LU11f+mjqv+lj0sR1UliHwogXL4ofJUwoG5Dbqlvdf5UTLWytw/OWSCv8RGyuh10miyWeaoqHh1cn2C1IFhUEqN1sSeKKKOWOTvJ2FR5mzi9uH3d/MOBzG5icQ7Qh0fZ1YPz5RaJJhYu6bmfvA/1gD49GoaX2kxQL4J5cEBgg="
token: ${GH_DEPLOY_API_KEY}
file_glob: true
file: build/*
skip_cleanup: true
on:
all_branches: true
tags: true
repo: OpenRA/OpenRA

7
.vscode/extensions.json vendored Normal file
View File

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

61
.vscode/launch.json vendored Normal file
View File

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

3
.vscode/settings.json vendored Normal file
View File

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

13
.vscode/tasks.json vendored Normal file
View File

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

15
AUTHORS
View File

@@ -40,6 +40,7 @@ Also thanks to:
* Biofreak
* Braxton Williams (Buddytex)
* Brendan Gluth (Mechanical_Man)
* Brent Gardner (bggardner)
* Bryan Wilbur
* Bugra Cuhadaroglu (BugraC)
* Chris Cameron (Vesuvian)
@@ -137,6 +138,7 @@ Also thanks to:
* Sebastien Kerguen (xanax)
* Shawn Collins (UberWaffe)
* Simon Verbeke (Saticmotion)
* Stuart McHattie (SDJMcHattie)
* Taryn Hill (Phrohdoh)
* Teemu Nieminen (Temeez)
* Tim Mylemans (gecko)
@@ -147,6 +149,7 @@ Also thanks to:
* Tristan Mühlbacher (MicroBit)
* UnknownProgrammer
* Vladimir Komarov (VrKomarov)
* Wojciech Walaszek (Voidwalker)
* Wuschel
Using GNU FreeFont distributed under the GNU GPL
@@ -160,9 +163,6 @@ FreeType License.
Using OpenAL Soft distributed under the GNU LGPL.
Using MaxMind GeoIP2 .NET API distributed under
the Apache 2.0 license.
Using SDL2-CS and OpenAL-CS created by Ethan
Lee and released under the zlib license.
@@ -182,6 +182,15 @@ Krueger and distributed under the GNU GPL terms.
Using rix0rrr.BeaconLib developed by Rico Huijbers
distributed under MIT License.
Using DiscordRichPresence developed by Lachee
distributed under MIT License.
Using Json.NET developed by James Newton-King
distributed under MIT License.
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.
Finally, special thanks goes to the original teams
at Westwood Studios and EA for creating the classic
games which OpenRA aims to reimagine.

File diff suppressed because one or more lines are too long

View File

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

174
Makefile
View File

@@ -3,15 +3,13 @@
# to compile, run:
# make [DEBUG=true]
#
# to check unit tests (requires NUnit version >= 2.6), run:
# make nunit [NUNIT_CONSOLE=<path-to/nunit[2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]
# Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths
# Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /
# Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)
# to compile using system libraries for native dependencies, run:
# make [DEBUG=true] TARGETPLATFORM=unix-generic
#
# to check the official mods for erroneous yaml files, run:
# make test
#
# to check the official mod dlls for StyleCop violations, run:
# to check the engine and official mod dlls for code style violations, run:
# make check
#
# to install, run:
@@ -22,6 +20,7 @@
#
# to install the engine and common mod files (omitting the default mods):
# make install-engine
# make install-dependencies
# make install-common-mod-files
#
# to uninstall, run:
@@ -40,11 +39,11 @@
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
NUNIT_LIBS_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
@@ -81,24 +80,31 @@ MSBUILD = msbuild -verbosity:m -nologo
# Enable 32 bit builds while generating the windows installer
WIN32 = false
# dependencies
ifndef TARGETPLATFORM
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_S),Darwin)
TARGETPLATFORM = osx-x64
else
ifeq ($(UNAME_M),x86_64)
TARGETPLATFORM = linux-x64
else
TARGETPLATFORM = unix-generic
endif
endif
endif
# program targets
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
# dependencies
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
os-dependencies = osx-dependencies
else
os-dependencies = linux-dependencies
endif
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
check: dependencies
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -p:Configuration=Debug
@@ -112,26 +118,6 @@ check: dependencies
@echo "Checking for incorrect conditional trait interface overrides..."
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
NUNIT_CONSOLE := $(shell test -f thirdparty/download/nunit3-console.exe && echo mono thirdparty/download/nunit3-console.exe || \
which nunit3-console 2>/dev/null || which nunit2-console 2>/dev/null || which nunit-console 2>/dev/null)
nunit: core
@echo
@echo "Checking unit tests..."
@if [ "$(NUNIT_CONSOLE)" = "" ] ; then \
echo 'nunit[3|2]-console not found!'; \
echo 'Was "make dependencies" called or is NUnit installed?'>&2; \
echo 'See "make help".'; \
exit 1; \
fi
@if $(NUNIT_CONSOLE) --help | head -n 1 | grep -E "NUnit version (1|2\.[0-5])";then \
echo 'NUnit version >= 2.6 required'>&2; \
echo 'Try "make dependencies" first to use NUnit from NuGet.'>&2; \
echo 'See "make help".'; \
exit 1; \
fi
@$(NUNIT_CONSOLE) --noresult OpenRA.Test.nunit
test: core
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@@ -148,51 +134,21 @@ test: core
########################## MAKE/INSTALL RULES ##########################
#
all: dependencies core
all: core
core:
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
else
@$(MSBUILD) -t:build -p:Configuration=Release
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@./fetch-geoip.sh
clean:
@ $(MSBUILD) -t:clean
@-$(RM_F) *.config
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.exe *.dll *.dll.config *.so *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_RF) ./*/bin ./*/obj
@-$(RM_RF) ./thirdparty/download
distclean: clean
cli-dependencies:
@./thirdparty/fetch-thirdparty-deps.sh
@ $(CP_R) thirdparty/download/*.dll .
@ $(CP_R) thirdparty/download/*.dll.config .
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
linux-dependencies: cli-dependencies linux-native-dependencies
linux-native-dependencies:
@./thirdparty/configure-native-deps.sh
windows-dependencies: cli-dependencies
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
else
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
endif
osx-dependencies: cli-dependencies
@./thirdparty/fetch-thirdparty-deps-osx.sh
@ $(CP_R) thirdparty/download/osx/*.dylib .
@ $(CP_R) thirdparty/download/osx/*.dll.config .
dependencies: $(os-dependencies)
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
@ $(MSBUILD) -t:clean
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
@echo "$(VERSION)" > VERSION
@@ -202,10 +158,34 @@ version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mo
rm $${i}.tmp; \
done
install: dependencies core install-core
install: core install-engine install-common-mod-files install-default-mods
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
install-dependencies:
ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dll "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), linux-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.so "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), osx-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dylib "$(DATA_INSTALL_DIR)"
endif
install-engine:
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
@@ -214,10 +194,13 @@ install-engine:
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
ifneq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
endif
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
@$(INSTALL_DATA) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP "$(DATA_INSTALL_DIR)/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
@@ -227,8 +210,9 @@ install-engine:
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) DiscordRPC.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
@@ -247,19 +231,16 @@ install-default-mods:
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
install-core: install-engine install-common-mod-files install-default-mods
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
install-linux-icons:
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
$(INSTALL_DATA) packaging/linux/icons/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(INSTALL_DATA) packaging/linux/icons/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(INSTALL_DATA) packaging/linux/icons/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
$(INSTALL_DATA) packaging/artwork/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(INSTALL_DATA) packaging/artwork/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(INSTALL_DATA) packaging/artwork/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
$(INSTALL_DATA) packaging/linux/icons/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
$(INSTALL_DATA) packaging/linux/icons/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
$(INSTALL_DATA) packaging/artwork/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
$(INSTALL_DATA) packaging/artwork/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
install-linux-desktop:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
@@ -353,23 +334,28 @@ uninstall:
help:
@echo 'to compile, run:'
@echo ' make [DEBUG=false]'
@echo ' make [DEBUG=true]'
@echo
@echo 'to check unit tests (requires NUnit version >= 2.6), run:'
@echo ' make nunit [NUNIT_CONSOLE=<path-to/nunit[3|2]-console>] [NUNIT_LIBS_PATH=<path-to-libs-dir>] [NUNIT_LIBS=<nunit-libs>]'
@echo ' Use NUNIT_CONSOLE if nunit[3|2]-console was not downloaded by `make dependencies` nor is it in bin search paths'
@echo ' Use NUNIT_LIBS_PATH if NUnit libs are not in search paths. Include trailing /'
@echo ' Use NUNIT_LIBS if NUnit libs have different names (such as including a prefix or suffix)'
@echo 'to compile using system libraries for native dependencies, run:'
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
@echo
@echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make test'
@echo
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make test'
@echo
@echo 'to install, run:'
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
@echo
@echo 'to install Linux startup scripts, desktop files and icons'
@echo ' make install-linux-shortcuts [DEBUG=false]'
@echo
@echo ' to install the engine and common mod files (omitting the default mods):'
@echo ' make install-engine'
@echo ' make install-dependencies'
@echo ' make install-common-mod-files'
@echo
@echo 'to uninstall, run:'
@echo ' make uninstall'
@echo
@@ -382,4 +368,4 @@ help:
.SUFFIXES:
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
.PHONY: check-scripts check test all core clean version install install-linux-shortcuts install-dependencies install-engine install-common-mod-files install-default-mods install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help

View File

@@ -185,8 +185,7 @@ namespace OpenRA.Activities
/// </summary>
internal void OnActorDisposeOuter(Actor self)
{
if (ChildActivity != null)
ChildActivity.OnActorDisposeOuter(self);
ChildActivity?.OnActorDisposeOuter(self);
OnActorDispose(self);
}
@@ -199,8 +198,7 @@ namespace OpenRA.Activities
if (!IsInterruptible)
return;
if (ChildActivity != null)
ChildActivity.Cancel(self);
ChildActivity?.Cancel(self);
// Directly mark activities that are queued and therefore didn't run yet as done
State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
@@ -243,11 +241,9 @@ namespace OpenRA.Activities
Console.WriteLine(GetType().ToString().Split('.').Last());
if (ChildActivity != null)
ChildActivity.PrintActivityTree(self, origin, level + 1);
ChildActivity?.PrintActivityTree(self, origin, level + 1);
if (NextActivity != null)
NextActivity.PrintActivityTree(self, origin, level);
NextActivity?.PrintActivityTree(self, origin, level);
}
}

View File

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

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
@@ -68,12 +69,35 @@ namespace OpenRA
{
get
{
// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
var facingValue = facing != null ? facing.Facing : 0;
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
return facing != null ? facing.Orientation : WRot.None;
}
}
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class ConditionState
{
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>();
}
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
int nextConditionToken = 1;
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
internal SyncHash[] SyncHashes { get; private set; }
readonly IFacing facing;
@@ -91,12 +115,21 @@ namespace OpenRA
internal Actor(World world, string name, TypeDictionary initDict)
{
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
.FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null)
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
var init = new ActorInitializer(this, initDict);
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
World = world;
ActorID = world.NextAID();
if (initDict.Contains<OwnerInit>())
Owner = init.Get<OwnerInit, Player>();
var ownerInit = init.GetOrDefault<OwnerInit>();
if (ownerInit != null)
Owner = ownerInit.Value(world);
if (name != null)
{
@@ -144,13 +177,40 @@ namespace OpenRA
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
}
internal void Created()
internal void Initialize(bool addToWorld = true)
{
created = true;
// Make sure traits are usable for condition notifiers
foreach (var t in TraitsImplementing<INotifyCreated>())
t.Created(this);
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
foreach (var provider in TraitsImplementing<IObservesVariables>())
{
foreach (var variableUser in provider.GetVariableObservers())
{
allObserverNotifiers.Add(variableUser.Notifier);
foreach (var variable in variableUser.Variables)
{
var cs = conditionStates.GetOrAdd(variable);
cs.Notifiers.Add(variableUser.Notifier);
// Initialize conditions that have not yet been granted to 0
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
conditionCache[variable] = cs.Tokens.Count;
}
}
}
// Update all traits with their initial condition state
foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache);
// TODO: Some traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead.
// The initial activity should run before any activities queued by INotifyCreated.Created
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
ICreationActivity creationActivity = null;
@@ -171,6 +231,9 @@ namespace OpenRA
activity.Queue(CurrentActivity);
CurrentActivity = activity;
}
if (addToWorld)
World.Add(this);
}
public void Tick()
@@ -233,7 +296,7 @@ namespace OpenRA
yield return r;
}
public Rectangle MouseBounds(WorldRenderer wr)
public Polygon MouseBounds(WorldRenderer wr)
{
foreach (var mb in mouseBounds)
{
@@ -242,7 +305,7 @@ namespace OpenRA
return bounds;
}
return Rectangle.Empty;
return Polygon.Empty;
}
public void QueueActivity(bool queued, Activity nextActivity)
@@ -266,8 +329,7 @@ namespace OpenRA
public void CancelActivity()
{
if (CurrentActivity != null)
CurrentActivity.Cancel(this);
CurrentActivity?.Cancel(this);
}
public override int GetHashCode()
@@ -319,8 +381,7 @@ namespace OpenRA
{
// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
// This should be done before the FrameEndTask to avoid dependency issues.
if (CurrentActivity != null)
CurrentActivity.OnActorDisposeOuter(this);
CurrentActivity?.OnActorDisposeOuter(this);
// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
WillDispose = true;
@@ -339,8 +400,7 @@ namespace OpenRA
World.TraitDict.RemoveActor(this);
Disposed = true;
if (luaInterface != null)
luaInterface.Value.OnActorDestroyed();
luaInterface?.Value.OnActorDestroyed();
});
}
@@ -454,6 +514,65 @@ namespace OpenRA
return new[] { CenterPosition };
}
#region Conditions
void UpdateConditionState(string condition, int token, bool isRevoke)
{
ConditionState conditionState = conditionStates.GetOrAdd(condition);
if (isRevoke)
conditionState.Tokens.Remove(token);
else
conditionState.Tokens.Add(token);
conditionCache[condition] = conditionState.Tokens.Count;
// Conditions may be granted or revoked before the state is initialized.
// These notifications will be processed after INotifyCreated.Created.
if (created)
foreach (var notify in conditionState.Notifiers)
notify(this, readOnlyConditionCache);
}
/// <summary>
/// Grants a specified condition if it is valid.
/// Otherwise, just returns InvalidConditionToken.
/// </summary>
/// <returns>The token that is used to revoke this condition.</returns>
public int GrantCondition(string condition)
{
if (string.IsNullOrEmpty(condition))
return InvalidConditionToken;
var token = nextConditionToken++;
conditionTokens.Add(token, condition);
UpdateConditionState(condition, token, false);
return token;
}
/// <summary>
/// Revokes a previously granted condition.
/// </summary>
/// <param name="token">The token ID returned by GrantCondition.</param>
/// <returns>The invalid token ID.</returns>
public int RevokeCondition(int token)
{
if (!conditionTokens.TryGetValue(token, out var condition))
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
conditionTokens.Remove(token);
UpdateConditionState(condition, token, true);
return InvalidConditionToken;
}
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
public bool TokenValid(int token)
{
return conditionTokens.ContainsKey(token);
}
#endregion
#region Scripting interface
Lazy<ScriptActorInterface> luaInterface;
@@ -471,8 +590,7 @@ namespace OpenRA
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
Actor a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
if (!left.TryGetClrValue(out Actor a) || !right.TryGetClrValue(out Actor b))
return false;
return a == b;

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Primitives;
namespace OpenRA
{
public class DefaultPlayer : IGlobalModData
{
public readonly Color Color = Color.FromAhsl(0, 0, 238);
}
}

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA

View File

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

View File

@@ -58,17 +58,15 @@ namespace OpenRA.FileSystem
return new Folder(resolvedPath);
// Zip files loaded from Folders (and *only* from Folders) can be read-write
IReadWritePackage readWritePackage;
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out readWritePackage))
if (ZipFileLoader.TryParseReadWritePackage(resolvedPath, out var readWritePackage))
return readWritePackage;
// Other package types can be loaded normally
IReadOnlyPackage package;
var s = GetStream(filename);
if (s == null)
return null;
if (context.TryParsePackage(s, filename, out package))
if (context.TryParsePackage(s, filename, out var package))
return package;
s.Dispose();

View File

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

View File

@@ -51,7 +51,6 @@ namespace OpenRA
public static Renderer Renderer;
public static Sound Sound;
public static bool HasInputFocus = false;
public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile;
@@ -62,13 +61,13 @@ namespace OpenRA
public static event Action OnShellmapLoaded = () => { };
public static OrderManager JoinServer(string host, int port, string password, bool recordReplay = true)
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
{
var connection = new NetworkConnection(host, port);
var connection = new NetworkConnection(endpoint);
if (recordReplay)
connection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(host, port, password, connection);
var om = new OrderManager(endpoint, password, connection);
JoinInner(om);
return om;
}
@@ -76,12 +75,12 @@ namespace OpenRA
static string TimestampedFilename(bool includemilliseconds = false)
{
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
return "OpenRA-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
return ModData.Manifest.Id + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
}
static void JoinInner(OrderManager om)
{
if (OrderManager != null) OrderManager.Dispose();
OrderManager?.Dispose();
OrderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager);
@@ -89,12 +88,12 @@ namespace OpenRA
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
}
// More accurate replacement for Environment.TickCount
@@ -105,14 +104,14 @@ namespace OpenRA
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static void RemoteDirectConnect(string host, int port)
public static void RemoteDirectConnect(ConnectionTarget endpoint)
{
OnRemoteDirectConnect(host, port);
OnRemoteDirectConnect(endpoint);
}
// Hacky workaround for orderManager visibility
@@ -155,8 +154,7 @@ namespace OpenRA
internal static void StartGame(string mapUID, WorldType type)
{
// Dispose of the old world before creating a new one.
if (worldRenderer != null)
worldRenderer.Dispose();
worldRenderer?.Dispose();
Cursor.SetCursor(null);
BeforeGameStart();
@@ -234,7 +232,7 @@ namespace OpenRA
LobbyInfoChanged += lobbyReady;
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
om = JoinServer(CreateLocalServer(mapUID), "");
}
public static bool IsHost
@@ -302,6 +300,7 @@ namespace OpenRA
Log.AddChannel("graphics", "graphics.log");
Log.AddChannel("geoip", "geoip.log");
Log.AddChannel("nat", "nat.log");
Log.AddChannel("client", "client.log");
var platforms = new[] { Settings.Game.Platform, "Default", null };
foreach (var p in platforms)
@@ -330,11 +329,9 @@ namespace OpenRA
Log.Write("graphics", "{0}", e);
Console.WriteLine("Renderer initialization failed. Check graphics.log for details.");
if (Renderer != null)
Renderer.Dispose();
Renderer?.Dispose();
if (Sound != null)
Sound.Dispose();
Sound?.Dispose();
}
}
@@ -355,8 +352,7 @@ namespace OpenRA
ExternalMods = new ExternalMods();
Manifest currentMod;
if (modID != null && Mods.TryGetValue(modID, out currentMod))
if (modID != null && Mods.TryGetValue(modID, out _))
{
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
@@ -367,8 +363,7 @@ namespace OpenRA
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
ExternalMod activeMod;
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out activeMod))
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
}
@@ -385,18 +380,15 @@ namespace OpenRA
LobbyInfoChanged = () => { };
ConnectionStateChanged = om => { };
BeforeGameStart = () => { };
OnRemoteDirectConnect = (a, b) => { };
OnRemoteDirectConnect = endpoint => { };
delayedActions = new ActionQueue();
Ui.ResetAll();
if (worldRenderer != null)
worldRenderer.Dispose();
worldRenderer?.Dispose();
worldRenderer = null;
if (server != null)
server.Shutdown();
if (OrderManager != null)
OrderManager.Dispose();
server?.Shutdown();
OrderManager?.Dispose();
if (ModData != null)
{
@@ -432,8 +424,7 @@ namespace OpenRA
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
Renderer.InitializeDepthBuffer(grid);
if (Cursor != null)
Cursor.Dispose();
Cursor?.Dispose();
Cursor = new CursorManager(ModData.CursorProvider);
@@ -442,13 +433,13 @@ namespace OpenRA
PerfHistory.Items["render_world"].HasNormalTick = false;
PerfHistory.Items["render_widgets"].HasNormalTick = false;
PerfHistory.Items["render_flip"].HasNormalTick = false;
PerfHistory.Items["terrain_lighting"].HasNormalTick = false;
JoinLocal();
try
{
if (discoverNat != null)
discoverNat.Wait();
discoverNat?.Wait();
}
catch (Exception e)
{
@@ -611,8 +602,7 @@ namespace OpenRA
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
}
if (benchmark != null)
benchmark.Tick(LocalTick);
benchmark?.Tick(LocalTick);
}
}
@@ -710,6 +700,7 @@ namespace OpenRA
PerfHistory.Items["render_world"].Tick();
PerfHistory.Items["render_widgets"].Tick();
PerfHistory.Items["render_flip"].Tick();
PerfHistory.Items["terrain_lighting"].Tick();
}
static void Loop()
@@ -836,12 +827,10 @@ namespace OpenRA
finally
{
// Ensure that the active replay is properly saved
if (OrderManager != null)
OrderManager.Dispose();
OrderManager?.Dispose();
}
if (worldRenderer != null)
worldRenderer.Dispose();
worldRenderer?.Dispose();
ModData.Dispose();
ChromeProvider.Deinitialize();
@@ -880,8 +869,7 @@ namespace OpenRA
public static void Disconnect()
{
if (OrderManager.World != null)
OrderManager.World.TraitDict.PrintReport();
OrderManager.World?.TraitDict.PrintReport();
OrderManager.Dispose();
CloseServer();
@@ -890,8 +878,7 @@ namespace OpenRA
public static void CloseServer()
{
if (server != null)
server.Shutdown();
server?.Shutdown();
}
public static T CreateObject<T>(string name)
@@ -899,12 +886,19 @@ namespace OpenRA
return ModData.ObjectCreator.CreateObject<T>(name);
}
public static void CreateServer(ServerSettings settings)
public static ConnectionTarget CreateServer(ServerSettings settings)
{
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
new IPEndPoint(IPAddress.Any, settings.ListenPort)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
return server.GetEndpointForLocalConnection();
}
public static int CreateLocalServer(string map)
public static ConnectionTarget CreateLocalServer(string map)
{
var settings = new ServerSettings()
{
@@ -913,9 +907,14 @@ namespace OpenRA
AdvertiseOnline = false
};
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Loopback, 0),
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
return server.Port;
return server.GetEndpointForLocalConnection();
}
public static bool IsCurrentWorld(World world)

View File

@@ -133,9 +133,7 @@ namespace OpenRA
/// <summary>Gets the player information for the specified runtime player instance.</summary>
public Player GetPlayer(OpenRA.Player runtimePlayer)
{
Player player;
playersByRuntime.TryGetValue(runtimePlayer, out player);
playersByRuntime.TryGetValue(runtimePlayer, out var player);
return player;
}

View File

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

View File

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

View File

@@ -24,8 +24,8 @@ namespace OpenRA.GameRules
public int[] DamageModifiers;
public int[] InaccuracyModifiers;
public int[] RangeModifiers;
public int Facing;
public Func<int> CurrentMuzzleFacing;
public WAngle Facing;
public Func<WAngle> CurrentMuzzleFacing;
public WPos Source;
public Func<WPos> CurrentSource;
public Actor SourceActor;
@@ -38,6 +38,8 @@ namespace OpenRA.GameRules
public WeaponInfo Weapon;
public int[] DamageModifiers = { };
public WPos? Source;
public WRot ImpactOrientation;
public WPos ImpactPosition;
public Actor SourceActor;
public Target WeaponTarget;
@@ -45,11 +47,22 @@ namespace OpenRA.GameRules
{
Weapon = args.Weapon;
DamageModifiers = args.DamageModifiers;
ImpactPosition = args.PassiveTarget;
Source = args.Source;
SourceActor = args.SourceActor;
WeaponTarget = args.GuidedTarget;
}
// For places that only want to update some of the fields (usually DamageModifiers)
public WarheadArgs(WarheadArgs args)
{
Weapon = args.Weapon;
DamageModifiers = args.DamageModifiers;
Source = args.Source;
SourceActor = args.SourceActor;
WeaponTarget = args.WeaponTarget;
}
// Default empty constructor for callers that want to initialize fields themselves
public WarheadArgs() { }
}
@@ -92,6 +105,12 @@ namespace OpenRA.GameRules
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
public readonly BitSet<TargetableType> InvalidTargets;
static readonly BitSet<TargetableType> TargetTypeAir = new BitSet<TargetableType>("Air");
[Desc("If weapon is not directly targeting an actor and targeted position is above this altitude,",
"the weapon will ignore terrain target types and only check TargetTypeAir for validity.")]
public readonly WDist AirThreshold = new WDist(128);
[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
"If multiple entries, their number needs to match Burst - 1.")]
public readonly int[] BurstDelays = { 5 };
@@ -118,8 +137,7 @@ namespace OpenRA.GameRules
static object LoadProjectile(MiniYaml yaml)
{
MiniYaml proj;
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
FieldLoader.Load(ret, proj);
@@ -155,6 +173,10 @@ namespace OpenRA.GameRules
if (target.Type == TargetType.Terrain)
{
var dat = world.Map.DistanceAboveTerrain(target.CenterPosition);
if (dat > AirThreshold)
return IsValidTarget(TargetTypeAir);
var cell = world.Map.CellContaining(target.CenterPosition);
if (!world.Map.Contains(cell))
return false;

View File

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

View File

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

View File

@@ -47,15 +47,13 @@ namespace OpenRA.Graphics
if (d.ContainsKey("X"))
{
int x;
Exts.TryParseIntegerInvariant(d["X"].Value, out x);
Exts.TryParseIntegerInvariant(d["X"].Value, out var x);
Hotspot = Hotspot.WithX(x);
}
if (d.ContainsKey("Y"))
{
int y;
Exts.TryParseIntegerInvariant(d["Y"].Value, out y);
Exts.TryParseIntegerInvariant(d["Y"].Value, out var y);
Hotspot = Hotspot.WithY(y);
}
}

View File

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

View File

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

View File

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

View File

@@ -15,9 +15,16 @@ using OpenRA.Primitives;
namespace OpenRA
{
public enum GLProfile
{
Modern,
Embedded,
Legacy
}
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -32,7 +39,10 @@ namespace OpenRA
Subtractive,
Multiply,
Multiplicative,
DoubleMultiplicative
DoubleMultiplicative,
LowAdditive,
Screen,
Translucent
}
public interface IPlatformWindow : IDisposable
@@ -46,6 +56,7 @@ namespace OpenRA
Size SurfaceSize { get; }
int DisplayCount { get; }
int CurrentDisplay { get; }
bool HasInputFocus { get; }
event Action<float, float, float, float> OnWindowScaleChanged;
@@ -60,6 +71,10 @@ namespace OpenRA
void SetHardwareCursor(IHardwareCursor cursor);
void SetRelativeMouseMode(bool mode);
void SetScaleModifier(float scale);
GLProfile GLProfile { get; }
GLProfile[] SupportedGLProfiles { get; }
}
public interface IGraphicsContext : IDisposable

View File

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

View File

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

View File

@@ -45,5 +45,21 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, a, b, c, d);
}
public void DrawSpriteWithTint(Sprite s, float3 location, float3 size, float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
}
}
}

View File

@@ -32,10 +32,11 @@ namespace OpenRA.Graphics
int ShadowZOffset { get; }
int[] Frames { get; }
Rectangle Bounds { get; }
bool IgnoreWorldTint { get; }
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, int facing);
Sprite GetShadow(int frame, int facing);
Sprite GetSprite(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing);
}
public interface ISpriteSequenceLoader
@@ -69,12 +70,10 @@ namespace OpenRA.Graphics
public ISpriteSequence GetSequence(string unitName, string sequenceName)
{
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
ISpriteSequence seq;
if (!unitSeq.Value.TryGetValue(sequenceName, out seq))
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
return seq;
@@ -87,8 +86,7 @@ namespace OpenRA.Graphics
public bool HasSequence(string unitName, string sequenceName)
{
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.ContainsKey(sequenceName);
@@ -96,8 +94,7 @@ namespace OpenRA.Graphics
public IEnumerable<string> Sequences(string unitName)
{
UnitSequences unitSeq;
if (!sequences.Value.TryGetValue(unitName, out unitSeq))
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
return unitSeq.Value.Keys;
@@ -114,8 +111,7 @@ namespace OpenRA.Graphics
var key = node.Value.ToLines(node.Key).JoinWith("|");
UnitSequences t;
if (sequenceCache.TryGetValue(key, out t))
if (sequenceCache.TryGetValue(key, out var t))
items.Add(node.Key, t);
else
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -107,7 +107,7 @@ namespace OpenRA.Graphics
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
nv += 6;
}
@@ -124,7 +124,26 @@ namespace OpenRA.Graphics
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, nv);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
nv += 6;
}
internal void DrawSpriteWithTint(Sprite s, float3 location, float paletteTextureIndex, float3 size, float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
nv += 6;
}
public void DrawSpriteWithTint(Sprite s, float3 location, PaletteReference pal, float3 size, float3 tint)
{
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
}
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
nv += 6;
}

View File

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

View File

@@ -118,8 +118,7 @@ namespace OpenRA.Graphics
public Sprite TileSprite(TerrainTile r, int? variant = null)
{
TheaterTemplate template;
if (!templates.TryGetValue(r.Type, out template))
if (!templates.TryGetValue(r.Type, out var template))
return missingTile;
if (r.Index >= template.Stride)

View File

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

View File

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

View File

@@ -19,6 +19,11 @@ namespace OpenRA.Graphics
[Flags]
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
public interface INotifyViewportZoomExtentsChanged
{
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
}
public static class ViewportExts
{
public static bool Includes(this ScrollDirection d, ScrollDirection s)
@@ -84,12 +89,22 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get { return minZoom; } }
public void AdjustZoom(float dz)
{
// Exponential ensures that equal positive and negative steps have the same effect
Zoom = (zoom * (float)Math.Exp(dz)).Clamp(unlockMinZoom ? unlockedMinZoom : minZoom, maxZoom);
}
public void AdjustZoom(float dz, int2 center)
{
var oldCenter = worldRenderer.Viewport.ViewToWorldPx(center);
AdjustZoom(dz);
var newCenter = worldRenderer.Viewport.ViewToWorldPx(center);
CenterLocation += oldCenter - newCenter;
}
public void ToggleZoom()
{
// Unlocked zooms always reset to the default zoom
@@ -237,6 +252,9 @@ namespace OpenRA.Graphics
Zoom = minZoom;
else
Zoom = Zoom.Clamp(minZoom, maxZoom);
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}
public CPos ViewToWorld(int2 view)
@@ -244,7 +262,6 @@ namespace OpenRA.Graphics
var world = worldRenderer.Viewport.ViewToWorldPx(view);
var map = worldRenderer.World.Map;
var candidates = CandidateMouseoverCells(world).ToList();
var tileSet = worldRenderer.World.Map.Rules.TileSet;
foreach (var uv in candidates)
{
@@ -253,18 +270,9 @@ namespace OpenRA.Graphics
var s = worldRenderer.ScreenPxPosition(p);
if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height)
{
var ramp = 0;
if (map.Contains(uv))
{
var ti = tileSet.GetTileInfo(map.Tiles[uv]);
if (ti != null)
ramp = ti.RampType;
}
var corners = map.Grid.CellCorners[ramp];
var pos = map.CenterOfCell(uv.ToCPos(map));
var screen = corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0];
var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset);
var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray();
if (screen.PolygonContains(world))
return uv.ToCPos(map);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ namespace OpenRA
}
/// <summary> Describes what is to be loaded in order to run a mod. </summary>
public class Manifest
public class Manifest : IDisposable
{
public readonly string Id;
public readonly IReadOnlyPackage Package;
@@ -96,8 +96,7 @@ namespace OpenRA
// TODO: Use fieldloader
MapFolders = YamlDictionary(yaml, "MapFolders");
MiniYaml packages;
if (yaml.TryGetValue("Packages", out packages))
if (yaml.TryGetValue("Packages", out var packages))
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
Rules = YamlList(yaml, "Rules");
@@ -217,9 +216,8 @@ namespace OpenRA
/// </summary>
public T Get<T>(ObjectCreator oc) where T : IGlobalModData
{
MiniYaml data;
var t = typeof(T);
if (!yaml.TryGetValue(t.Name, out data))
if (!yaml.TryGetValue(t.Name, out var data))
{
// Lazily create the default values if not explicitly defined.
return (T)oc.CreateBasic(t);
@@ -241,5 +239,14 @@ namespace OpenRA
return (T)module;
}
public void Dispose()
{
foreach (var module in modules)
{
var disposableModule = module as IDisposable;
disposableModule?.Dispose();
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA
{
public abstract class CellLayerBase<T> : IEnumerable<T>
{
public readonly Size Size;
public readonly MapGridType GridType;
protected readonly T[] entries;
protected readonly Rectangle bounds;
public CellLayerBase(Map map)
: this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { }
public CellLayerBase(MapGridType gridType, Size size)
{
Size = size;
bounds = new Rectangle(0, 0, Size.Width, Size.Height);
GridType = gridType;
entries = new T[size.Width * size.Height];
}
public virtual void CopyValuesFrom(CellLayerBase<T> anotherLayer)
{
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
throw new ArgumentException("Layers must have a matching size and shape (grid type).", "anotherLayer");
Array.Copy(anotherLayer.entries, entries, entries.Length);
}
/// <summary>Clears the layer contents with a known value</summary>
public void Clear(T clearValue)
{
for (var i = 0; i < entries.Length; i++)
entries[i] = clearValue;
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)entries).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

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

View File

@@ -230,9 +230,10 @@ namespace OpenRA
public CellLayer<TerrainTile> Tiles { get; private set; }
public CellLayer<ResourceTile> Resources { get; private set; }
public CellLayer<byte> Height { get; private set; }
public CellLayer<byte> Ramp { get; private set; }
public CellLayer<byte> CustomTerrain { get; private set; }
public ProjectedCellRegion ProjectedCellBounds { get; private set; }
public PPos[] ProjectedCells { get; private set; }
public CellRegion AllCells { get; private set; }
public List<CPos> AllEdgeCells { get; private set; }
@@ -301,10 +302,12 @@ namespace OpenRA
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
Height = new CellLayer<byte>(Grid.Type, size);
Ramp = new CellLayer<byte>(Grid.Type, size);
if (Grid.MaximumTerrainHeight > 0)
{
Height.CellEntryChanged += UpdateProjection;
Tiles.CellEntryChanged += UpdateProjection;
Tiles.CellEntryChanged += UpdateRamp;
}
Tiles.Clear(tileRef);
@@ -336,6 +339,7 @@ namespace OpenRA
Tiles = new CellLayer<TerrainTile>(Grid.Type, size);
Resources = new CellLayer<ResourceTile>(Grid.Type, size);
Height = new CellLayer<byte>(Grid.Type, size);
Ramp = new CellLayer<byte>(Grid.Type, size);
using (var s = Package.GetStream("map.bin"))
{
@@ -384,6 +388,7 @@ namespace OpenRA
if (Grid.MaximumTerrainHeight > 0)
{
Tiles.CellEntryChanged += UpdateRamp;
Tiles.CellEntryChanged += UpdateProjection;
Height.CellEntryChanged += UpdateProjection;
}
@@ -422,9 +427,23 @@ namespace OpenRA
foreach (var uv in AllCells.MapCoords)
CustomTerrain[uv] = byte.MaxValue;
// Cache initial ramp state
var tileset = Rules.TileSet;
foreach (var uv in AllCells)
{
var tile = tileset.GetTileInfo(Tiles[uv]);
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
}
AllEdgeCells = UpdateEdgeCells();
}
void UpdateRamp(CPos cell)
{
var tile = Rules.TileSet.GetTileInfo(Tiles[cell]);
Ramp[cell] = tile != null ? tile.RampType : (byte)0;
}
void InitializeCellProjection()
{
if (initializedCellProjection)
@@ -531,12 +550,8 @@ namespace OpenRA
return new[] { (PPos)uv };
// Odd-height ramps get bumped up a level to the next even height layer
if ((height & 1) == 1)
{
var ti = Rules.TileSet.GetTileInfo(Tiles[uv]);
if (ti != null && ti.RampType != 0)
height += 1;
}
if ((height & 1) == 1 && Ramp[uv] != 0)
height += 1;
var candidates = new List<PPos>();
@@ -647,12 +662,40 @@ namespace OpenRA
return dataStream.ToArray();
}
public (Color Left, Color Right) GetTerrainColorPair(MPos uv)
{
Color left, right;
var tileset = Rules.TileSet;
var type = tileset.GetTileInfo(Tiles[uv]);
if (type != null)
{
if (type.MinColor != type.MaxColor)
{
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
}
else
left = right = type.MinColor;
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
{
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
}
}
else
left = right = Color.Black;
return (left, right);
}
public byte[] SavePreview()
{
var tileset = Rules.TileSet;
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
var actors = ActorDefinitions.Where(a => actorTypes.Where(ai => ai.Name == a.Value.Value).Any());
var positions = new List<Pair<MPos, Color>>();
var positions = new List<(MPos Position, Color Color)>();
foreach (var actor in actors)
{
var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary());
@@ -686,24 +729,18 @@ namespace OpenRA
var stride = bitmapWidth * 4;
var pxStride = 4;
var minimapData = new byte[stride * height];
Color leftColor, rightColor;
(Color Left, Color Right) terrainColor = default((Color, Color));
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var uv = new MPos(x + Bounds.Left, y + Bounds.Top);
var actorsThere = positions.Where(ap => ap.First == uv);
if (actorsThere.Any())
{
leftColor = rightColor = actorsThere.First().Second;
}
else
{
// Cell contains terrain
var type = tileset.GetTileInfo(Tiles[uv]);
leftColor = type != null ? type.LeftColor : Color.Black;
rightColor = type != null ? type.RightColor : Color.Black;
}
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
if (actorColor.A == 0)
terrainColor = GetTerrainColorPair(uv);
if (isRectangularIsometric)
{
@@ -713,28 +750,31 @@ namespace OpenRA
if (x + dx > 0)
{
var z = y * stride + xOffset - pxStride;
minimapData[z++] = leftColor.R;
minimapData[z++] = leftColor.G;
minimapData[z++] = leftColor.B;
minimapData[z++] = leftColor.A;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
if (xOffset < stride)
{
var z = y * stride + xOffset;
minimapData[z++] = rightColor.R;
minimapData[z++] = rightColor.G;
minimapData[z++] = rightColor.B;
minimapData[z++] = rightColor.A;
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
}
else
{
var z = y * stride + pxStride * x;
minimapData[z++] = leftColor.R;
minimapData[z++] = leftColor.G;
minimapData[z++] = leftColor.B;
minimapData[z++] = leftColor.A;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
}
}
@@ -770,8 +810,9 @@ namespace OpenRA
bool ContainsAllProjectedCellsCovering(MPos uv)
{
// PERF: Checking the bounds directly here is the same as calling Contains((PPos)uv) but saves an allocation
if (Grid.MaximumTerrainHeight == 0)
return Contains((PPos)uv);
return Bounds.Contains(uv.U, uv.V);
// If the cell has no valid projection, then we're off the map.
var projectedCells = ProjectedCellsCovering(uv);
@@ -781,6 +822,7 @@ namespace OpenRA
foreach (var puv in projectedCells)
if (!Contains(puv))
return false;
return true;
}
@@ -805,7 +847,7 @@ namespace OpenRA
// (c) u, v coordinates run diagonally to the cell axes, and we define
// 1024 as the length projected onto the primary cell axis
// - 512 * sqrt(2) = 724
var z = Height.Contains(cell) ? 724 * Height[cell] : 0;
var z = Height.Contains(cell) ? 724 * Height[cell] + Grid.Ramps[Ramp[cell]].CenterHeightOffset : 0;
return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z);
}
@@ -813,15 +855,42 @@ namespace OpenRA
{
var index = (int)subCell;
if (index >= 0 && index < Grid.SubCellOffsets.Length)
return CenterOfCell(cell) + Grid.SubCellOffsets[index];
{
var center = CenterOfCell(cell);
var offset = Grid.SubCellOffsets[index];
var ramp = Ramp.Contains(cell) ? Ramp[cell] : 0;
if (ramp != 0)
{
var r = Grid.Ramps[ramp];
offset += new WVec(0, 0, r.HeightOffset(offset.X, offset.Y) - r.CenterHeightOffset);
}
return center + offset;
}
return CenterOfCell(cell);
}
public WDist DistanceAboveTerrain(WPos pos)
{
if (Grid.Type == MapGridType.Rectangular)
return new WDist(pos.Z);
// Apply ramp offset
var cell = CellContaining(pos);
var delta = pos - CenterOfCell(cell);
return new WDist(delta.Z);
var offset = pos - CenterOfCell(cell);
if (!Ramp.Contains(cell))
return new WDist(offset.Z);
var ramp = Ramp[cell];
if (ramp != 0)
{
var r = Grid.Ramps[ramp];
return new WDist(offset.Z + r.CenterHeightOffset - r.HeightOffset(offset.X, offset.Y));
}
return new WDist(offset.Z);
}
public WVec Offset(CVec delta, int dz)
@@ -899,13 +968,13 @@ namespace OpenRA
return projectedHeight[(MPos)puv];
}
public int FacingBetween(CPos cell, CPos towards, int fallbackfacing)
public WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing)
{
var delta = CenterOfCell(towards) - CenterOfCell(cell);
if (delta.HorizontalLengthSquared == 0)
return fallbackfacing;
return delta.Yaw.Facing;
return delta.Yaw;
}
public void Resize(int width, int height)
@@ -913,11 +982,13 @@ namespace OpenRA
var oldMapTiles = Tiles;
var oldMapResources = Resources;
var oldMapHeight = Height;
var oldMapRamp = Ramp;
var newSize = new Size(width, height);
Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]);
Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]);
Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]);
Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]);
MapSize = new int2(newSize);
var tl = new MPos(0, 0);
@@ -947,7 +1018,8 @@ namespace OpenRA
ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0);
}
ProjectedCellBounds = new ProjectedCellRegion(this, tl, br);
// PERF: This enumeration isn't going to change during the game
ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray();
}
public void FixOpenAreas()

View File

@@ -111,8 +111,7 @@ namespace OpenRA
}
catch (Exception e)
{
if (mapPackage != null)
mapPackage.Dispose();
mapPackage?.Dispose();
Console.WriteLine("Failed to load map: {0}", map);
Console.WriteLine("Details: {0}", e);
Log.Write("debug", "Failed to load map: {0}", map);
@@ -131,8 +130,7 @@ namespace OpenRA
// Enumerate map directories
foreach (var kv in modData.Manifest.MapFolders)
{
MapClassification packageClassification;
if (!Enum.TryParse(kv.Value, out packageClassification))
if (!Enum.TryParse(kv.Value, out MapClassification packageClassification))
continue;
if (!classification.HasFlag(packageClassification))
@@ -193,8 +191,7 @@ namespace OpenRA
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
if (queryFailed != null)
queryFailed();
queryFailed?.Invoke();
return;
}
@@ -214,8 +211,7 @@ namespace OpenRA
{
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
Log.Write("debug", "Exception: {0}", e);
if (queryFailed != null)
queryFailed();
queryFailed?.Invoke();
}
};
@@ -299,8 +295,7 @@ namespace OpenRA
Game.RunAfterTick(() =>
{
// Wait for any existing thread to exit before starting a new one.
if (previewLoaderThread != null)
previewLoaderThread.Join();
previewLoaderThread?.Join();
previewLoaderThread = new Thread(LoadAsyncInternal)
{

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ namespace OpenRA
public string Faction;
public bool LockColor = false;
public Color Color = Color.FromAhsl(0, 0, 238);
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
public bool LockSpawn = false;
public int Spawn = 0;

View File

@@ -0,0 +1,49 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Primitives;
namespace OpenRA
{
public sealed class ProjectedCellLayer<T> : CellLayerBase<T>
{
public ProjectedCellLayer(Map map)
: base(map) { }
public ProjectedCellLayer(MapGridType gridType, Size size)
: base(gridType, size) { }
// Resolve an array index from map coordinates.
int Index(PPos uv)
{
return uv.V * Size.Width + uv.U;
}
/// <summary>Gets or sets the layer contents using projected map coordinates.</summary>
public T this[PPos uv]
{
get
{
return entries[Index(uv)];
}
set
{
entries[Index(uv)] = value;
}
}
public bool Contains(PPos uv)
{
return bounds.Contains(uv.U, uv.V);
}
}
}

View File

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

View File

@@ -24,9 +24,8 @@ namespace OpenRA
public readonly byte TerrainType = byte.MaxValue;
public readonly byte Height;
public readonly byte RampType;
public readonly Color LeftColor;
public readonly Color RightColor;
public readonly Color MinColor;
public readonly Color MaxColor;
public readonly float ZOffset = 0.0f;
public readonly float ZRamp = 1.0f;
}
@@ -73,8 +72,7 @@ namespace OpenRA
tileInfo = new TerrainTileInfo[Size.X * Size.Y];
foreach (var node in nodes)
{
int key;
if (!int.TryParse(node.Key, out key) || key < 0 || key >= tileInfo.Length)
if (!int.TryParse(node.Key, out var key) || key < 0 || key >= tileInfo.Length)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
@@ -87,8 +85,7 @@ namespace OpenRA
var i = 0;
foreach (var node in nodes)
{
int key;
if (!int.TryParse(node.Key, out key) || key != i++)
if (!int.TryParse(node.Key, out var key) || key != i++)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
@@ -106,11 +103,11 @@ namespace OpenRA
// Fall back to the terrain-type color if necessary
var overrideColor = tileSet.TerrainInfo[tile.TerrainType].Color;
if (tile.LeftColor == default(Color))
tile.GetType().GetField("LeftColor").SetValue(tile, overrideColor);
if (tile.MinColor == default(Color))
tile.GetType().GetField("MinColor").SetValue(tile, overrideColor);
if (tile.RightColor == default(Color))
tile.GetType().GetField("RightColor").SetValue(tile, overrideColor);
if (tile.MaxColor == default(Color))
tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor);
return tile;
}
@@ -139,6 +136,8 @@ namespace OpenRA
public readonly string[] EditorTemplateOrder;
public readonly bool IgnoreTileSpriteOffsets;
public readonly bool EnableDepth = false;
public readonly float MinHeightColorBrightness = 1.0f;
public readonly float MaxHeightColorBrightness = 1.0f;
[FieldLoader.Ignore]
public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
@@ -215,8 +214,7 @@ namespace OpenRA
public byte GetTerrainIndex(string type)
{
byte index;
if (terrainIndexByType.TryGetValue(type, out index))
if (terrainIndexByType.TryGetValue(type, out var index))
return index;
throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type));
@@ -224,8 +222,7 @@ namespace OpenRA
public byte GetTerrainIndex(TerrainTile r)
{
TerrainTemplateInfo tpl;
if (!Templates.TryGetValue(r.Type, out tpl))
if (!Templates.TryGetValue(r.Type, out var tpl))
return defaultWalkableTerrainIndex;
if (tpl.Contains(r.Index))
@@ -240,8 +237,7 @@ namespace OpenRA
public TerrainTileInfo GetTileInfo(TerrainTile r)
{
TerrainTemplateInfo tpl;
if (!Templates.TryGetValue(r.Type, out tpl))
if (!Templates.TryGetValue(r.Type, out var tpl))
return null;
return tpl.Contains(r.Index) ? tpl[r.Index] : null;

View File

@@ -339,8 +339,7 @@ namespace OpenRA
{
if (n.Key == "Inherits" || n.Key.StartsWith("Inherits@", StringComparison.Ordinal))
{
MiniYaml parent;
if (!tree.TryGetValue(n.Value.Value, out parent))
if (!tree.TryGetValue(n.Value.Value, out var parent))
throw new YamlException(
"{0}: Parent type `{1}` not found".F(n.Location, n.Value.Value));
@@ -428,9 +427,8 @@ namespace OpenRA
foreach (var key in allKeys)
{
MiniYamlNode existingNode, overrideNode;
existingDict.TryGetValue(key, out existingNode);
overrideDict.TryGetValue(key, out overrideNode);
existingDict.TryGetValue(key, out var existingNode);
overrideDict.TryGetValue(key, out var overrideNode);
var loc = overrideNode == null ? default(MiniYamlNode.SourceLocation) : overrideNode.Location;
var comment = (overrideNode ?? existingNode).Comment;

View File

@@ -177,8 +177,7 @@ namespace OpenRA
public Map PrepareMap(string uid)
{
if (LoadScreen != null)
LoadScreen.Display();
LoadScreen?.Display();
if (MapCache[uid].Status != MapStatus.Available)
throw new InvalidDataException("Invalid map uid: {0}".F(uid));
@@ -202,12 +201,12 @@ namespace OpenRA
public void Dispose()
{
if (LoadScreen != null)
LoadScreen.Dispose();
LoadScreen?.Dispose();
MapCache.Dispose();
if (ObjectCreator != null)
ObjectCreator.Dispose();
ObjectCreator?.Dispose();
Manifest.Dispose();
}
}

View File

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

View File

@@ -167,28 +167,22 @@ namespace OpenRA.Network
// Games advertised using the old API calculated the play time locally
if (State == 2 && PlayTime < 0)
{
DateTime startTime;
if (DateTime.TryParse(Started, out startTime))
if (DateTime.TryParse(Started, out var startTime))
PlayTime = (int)(DateTime.UtcNow - startTime).TotalSeconds;
}
ExternalMod external;
var externalKey = ExternalMod.MakeKey(Mod, Version);
if (Game.ExternalMods.TryGetValue(externalKey, out external) && external.Version == Version)
if (Game.ExternalMods.TryGetValue(externalKey, out var external) && external.Version == Version)
IsCompatible = true;
// Games advertised using the old API used local mod metadata
if (string.IsNullOrEmpty(ModTitle))
{
Manifest mod;
if (external != null && external.Version == Version)
{
// Use external mod registration to populate the section header
ModTitle = external.Title;
}
else if (Game.Mods.TryGetValue(Mod, out mod))
else if (Game.Mods.TryGetValue(Mod, out var mod))
{
// Use internal mod data to populate the section header, but
// on-connect switching must use the external mod plumbing.

View File

@@ -11,63 +11,119 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using ICSharpCode.SharpZipLib.GZip;
using MaxMind.Db;
using System.Net.Sockets;
using System.Numerics;
using ICSharpCode.SharpZipLib.Zip;
namespace OpenRA.Network
{
public class GeoIP
{
public class GeoIP2Record
class IP2LocationReader
{
[Constructor]
public GeoIP2Record(GeoIP2Country country)
public readonly DateTime Date;
readonly Stream stream;
readonly uint columnCount;
readonly uint v4Count;
readonly uint v4Offset;
readonly uint v6Count;
readonly uint v6Offset;
public IP2LocationReader(Stream source)
{
Country = country;
// Copy stream data for reuse
stream = new MemoryStream();
source.CopyTo(stream);
stream.Position = 0;
if (stream.ReadUInt8() != 1)
throw new InvalidDataException("Only IP2Location type 1 databases are supported.");
columnCount = stream.ReadUInt8();
var year = stream.ReadUInt8();
var month = stream.ReadUInt8();
var day = stream.ReadUInt8();
Date = new DateTime(2000 + year, month, day);
v4Count = stream.ReadUInt32();
v4Offset = stream.ReadUInt32();
v6Count = stream.ReadUInt32();
v6Offset = stream.ReadUInt32();
}
public GeoIP2Country Country { get; set; }
}
public class GeoIP2Country
{
[Constructor]
public GeoIP2Country(GeoIP2CountryNames names)
BigInteger AddressForIndex(long index, bool isIPv6)
{
Names = names;
var start = isIPv6 ? v6Offset : v4Offset;
var offset = isIPv6 ? 12 : 0;
stream.Seek(start + index * (4 * columnCount + offset) - 1, SeekOrigin.Begin);
return new BigInteger(stream.ReadBytes(isIPv6 ? 16 : 4).Append((byte)0).ToArray());
}
public GeoIP2CountryNames Names { get; set; }
}
public class GeoIP2CountryNames
{
[Constructor]
public GeoIP2CountryNames(string en)
string CountryForIndex(long index, bool isIPv6)
{
English = en;
// Read file offset for country entry
var start = isIPv6 ? v6Offset : v4Offset;
var offset = isIPv6 ? 12 : 0;
stream.Seek(start + index * (4 * columnCount + offset) + offset + 3, SeekOrigin.Begin);
var countryOffset = stream.ReadUInt32();
// Read length-prefixed country name
stream.Seek(countryOffset + 3, SeekOrigin.Begin);
var length = stream.ReadUInt8();
// "-" is used to represent an unknown country in the database
var country = stream.ReadASCII(length);
return country != "-" ? country : null;
}
public string English { get; set; }
public string LookupCountry(IPAddress ip)
{
var isIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6;
if (!isIPv6 && ip.AddressFamily != AddressFamily.InterNetwork)
return null;
// Locate IP using a binary search
// The IP2Location python parser has an additional
// optimization that can jump directly to the row, but this adds
// extra complexity that isn't obviously needed for our limited database size
long low = 0;
long high = isIPv6 ? v6Count : v4Count;
// Append an empty byte to force the data to be treated as unsigned
var ipNumber = new BigInteger(ip.GetAddressBytes().Reverse().Append((byte)0).ToArray());
while (low <= high)
{
var mid = (low + high) / 2;
var min = AddressForIndex(mid, isIPv6);
var max = AddressForIndex(mid + 1, isIPv6);
if (min <= ipNumber && ipNumber < max)
return CountryForIndex(mid, isIPv6);
if (ipNumber < min)
high = mid - 1;
else
low = mid + 1;
}
return null;
}
}
static Reader database;
static IP2LocationReader database;
public static void Initialize(string databasePath)
public static void Initialize(string databasePath = "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP")
{
if (string.IsNullOrEmpty(databasePath))
if (!File.Exists(databasePath))
return;
try
{
using (var fileStream = new FileStream(databasePath, FileMode.Open, FileAccess.Read))
using (var z = new ZipFile(databasePath))
{
if (databasePath.EndsWith(".gz"))
using (var gzipStream = new GZipInputStream(fileStream))
database = new Reader(gzipStream);
else
database = new Reader(fileStream);
var entry = z.FindEntry("IP2LOCATION-LITE-DB1.IPV6.BIN", false);
database = new IP2LocationReader(z.GetInputStream(entry));
}
}
catch (Exception e)
@@ -82,9 +138,7 @@ namespace OpenRA.Network
{
try
{
var record = database.Find<GeoIP2Record>(ip);
if (record != null)
return record.Country.Names.English;
return database.LookupCountry(ip);
}
catch (Exception e)
{

View File

@@ -25,7 +25,7 @@ namespace OpenRA
}
[Flags]
enum OrderFields : byte
enum OrderFields : short
{
None = 0x0,
Target = 0x01,
@@ -35,7 +35,8 @@ namespace OpenRA
ExtraLocation = 0x10,
ExtraData = 0x20,
TargetIsCell = 0x40,
Subject = 0x80
Subject = 0x80,
Grouped = 0x100
}
static class OrderFieldsExts
@@ -52,6 +53,8 @@ namespace OpenRA
public readonly Actor Subject;
public readonly bool Queued;
public readonly Target Target;
public readonly Actor[] GroupedActors;
public string TargetString;
public CPos ExtraLocation;
public Actor[] ExtraActors;
@@ -64,7 +67,7 @@ namespace OpenRA
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData)
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
{
OrderString = orderString ?? "";
Subject = subject;
@@ -74,6 +77,7 @@ namespace OpenRA
ExtraActors = extraActors;
ExtraLocation = extraLocation;
ExtraData = extraData;
GroupedActors = groupedActors;
}
public static Order Deserialize(World world, BinaryReader r)
@@ -86,7 +90,7 @@ namespace OpenRA
case OrderType.Fields:
{
var order = r.ReadString();
var flags = (OrderFields)r.ReadByte();
var flags = (OrderFields)r.ReadInt16();
Actor subject = null;
if (flags.HasField(OrderFields.Subject))
@@ -103,8 +107,7 @@ namespace OpenRA
{
case TargetType.Actor:
{
Actor targetActor;
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out targetActor))
if (world != null && TryGetActorFromUInt(world, r.ReadUInt32(), out var targetActor))
target = Target.FromActor(targetActor);
break;
}
@@ -114,8 +117,7 @@ namespace OpenRA
var playerActorID = r.ReadUInt32();
var frozenActorID = r.ReadUInt32();
Actor playerActor;
if (world == null || !TryGetActorFromUInt(world, playerActorID, out playerActor))
if (world == null || !TryGetActorFromUInt(world, playerActorID, out var playerActor))
break;
if (playerActor.Owner.FrozenActorLayer == null)
@@ -164,13 +166,23 @@ namespace OpenRA
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32()) : CPos.Zero;
var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0;
Actor[] groupedActors = null;
if (flags.HasField(OrderFields.Grouped))
{
var count = r.ReadInt32();
if (world != null)
groupedActors = Exts.MakeArray(count, _ => world.GetActorById(r.ReadUInt32()));
else
r.ReadBytes(4 * count);
}
if (world == null)
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData);
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
if (subject == null && flags.HasField(OrderFields.Subject))
return null;
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData);
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
}
case OrderType.Handshake:
@@ -231,6 +243,11 @@ namespace OpenRA
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
}
public static Order FromGroupedOrder(Order grouped, Actor subject)
{
return new Order(grouped.OrderString, subject, grouped.Target, grouped.TargetString, grouped.Queued, grouped.ExtraActors, grouped.ExtraLocation, grouped.ExtraData);
}
public static Order Command(string text)
{
return new Order("Command", null, false) { IsImmediate = true, TargetString = text };
@@ -255,11 +272,11 @@ namespace OpenRA
public Order()
: this(null, null, Target.Invalid, null, false, null, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public byte[] Serialize()
{
@@ -267,7 +284,7 @@ namespace OpenRA
if (Type == OrderType.Handshake)
minLength += TargetString.Length + 1;
else if (Type == OrderType.Fields)
minLength += 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
minLength += 4 + 2 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
if (ExtraActors != null)
minLength += ExtraActors.Length * 4;
@@ -308,6 +325,9 @@ namespace OpenRA
if (Queued)
fields |= OrderFields.Queued;
if (GroupedActors != null)
fields |= OrderFields.Grouped;
if (ExtraActors != null)
fields |= OrderFields.ExtraActors;
@@ -317,7 +337,7 @@ namespace OpenRA
if (Target.SerializableCell != null)
fields |= OrderFields.TargetIsCell;
w.Write((byte)fields);
w.Write((short)fields);
if (fields.HasField(OrderFields.Subject))
w.Write(UIntFromActor(Subject));
@@ -362,6 +382,13 @@ namespace OpenRA
if (fields.HasField(OrderFields.ExtraData))
w.Write(ExtraData);
if (fields.HasField(OrderFields.Grouped))
{
w.Write(GroupedActors.Length);
foreach (var a in GroupedActors)
w.Write(UIntFromActor(a));
}
break;
}

View File

@@ -28,11 +28,10 @@ namespace OpenRA.Network
public Session.Client LocalClient { get { return LobbyInfo.ClientWithIndex(Connection.LocalClientId); } }
public World World;
public readonly string Host;
public readonly int Port;
public readonly ConnectionTarget Endpoint;
public readonly string Password = "";
public string ServerError = "Server is not responding";
public string ServerError = null;
public bool AuthenticationFailed = false;
public ExternalMod ServerExternalMod = null;
@@ -80,10 +79,9 @@ namespace OpenRA.Network
Connection.Send(i, new List<byte[]>());
}
public OrderManager(string host, int port, string password, IConnection conn)
public OrderManager(ConnectionTarget endpoint, string password, IConnection conn)
{
Host = host;
Port = port;
Endpoint = endpoint;
Password = password;
Connection = conn;
syncReport = new SyncReport(this);
@@ -117,7 +115,7 @@ namespace OpenRA.Network
Connection.SendImmediate(localImmediateOrders.Select(o => o.Serialize()));
localImmediateOrders.Clear();
var immediatePackets = new List<Pair<int, byte[]>>();
var immediatePackets = new List<(int ClientId, byte[] Packet)>();
Connection.Receive(
(clientId, packet) =>
@@ -128,16 +126,16 @@ namespace OpenRA.Network
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
CheckSync(packet);
else if (frame == 0)
immediatePackets.Add(Pair.New(clientId, packet));
immediatePackets.Add((clientId, packet));
else
frameData.AddFrameOrders(clientId, frame, packet);
});
foreach (var p in immediatePackets)
{
foreach (var o in p.Second.ToOrderList(World))
foreach (var o in p.Packet.ToOrderList(World))
{
UnitOrders.ProcessOrder(this, World, p.First, o);
UnitOrders.ProcessOrder(this, World, p.ClientId, o);
// A mod switch or other event has pulled the ground from beneath us
if (disposed)
@@ -151,8 +149,7 @@ namespace OpenRA.Network
void CheckSync(byte[] packet)
{
var frame = BitConverter.ToInt32(packet, 0);
byte[] existingSync;
if (syncForFrame.TryGetValue(frame, out existingSync))
if (syncForFrame.TryGetValue(frame, out var existingSync))
{
if (packet.Length != existingSync.Length)
OutOfSync(frame);
@@ -167,14 +164,14 @@ namespace OpenRA.Network
public bool IsReadyForNextFrame
{
get { return NetFrameNumber >= 1 && frameData.IsReadyForFrame(NetFrameNumber); }
get { return GameStarted && frameData.IsReadyForFrame(NetFrameNumber); }
}
public IEnumerable<Session.Client> GetClientsNotReadyForNextFrame
{
get
{
return NetFrameNumber >= 1
return GameStarted
? frameData.ClientsNotReadyForFrame(NetFrameNumber)
.Select(a => LobbyInfo.ClientWithIndex(a))
: NoClients;
@@ -209,8 +206,7 @@ namespace OpenRA.Network
public void Dispose()
{
disposed = true;
if (Connection != null)
Connection.Dispose();
Connection?.Dispose();
}
}

View File

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

View File

@@ -100,8 +100,7 @@ namespace OpenRA.Network
Metadata.Write(writer);
}
if (preStartBuffer != null)
preStartBuffer.Dispose();
preStartBuffer?.Dispose();
writer.Close();
}
}

View File

@@ -250,8 +250,7 @@ namespace OpenRA.Network
public bool OptionOrDefault(string id, bool def)
{
LobbyOptionState option;
if (LobbyOptions.TryGetValue(id, out option))
if (LobbyOptions.TryGetValue(id, out var option))
return option.IsEnabled;
return def;
@@ -259,8 +258,7 @@ namespace OpenRA.Network
public string OptionOrDefault(string id, string def)
{
LobbyOptionState option;
if (LobbyOptions.TryGetValue(id, out option))
if (LobbyOptions.TryGetValue(id, out var option))
return option.Value;
return def;

View File

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

View File

@@ -9,10 +9,8 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Server;
using OpenRA.Traits;
@@ -147,8 +145,7 @@ namespace OpenRA.Network
var data = MiniYaml.FromString(order.TargetString)[0];
var traitIndex = int.Parse(data.Key);
if (world != null)
world.AddGameSaveTraitData(traitIndex, data.Value);
world?.AddGameSaveTraitData(traitIndex, data.Value);
break;
}
@@ -192,9 +189,8 @@ namespace OpenRA.Network
var request = HandshakeRequest.Deserialize(order.TargetString);
var externalKey = ExternalMod.MakeKey(request.Mod, request.Version);
ExternalMod external;
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version)
&& Game.ExternalMods.TryGetValue(externalKey, out external))
if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) &&
Game.ExternalMods.TryGetValue(externalKey, out var external))
{
// The ConnectionFailedLogic will prompt the user to switch mods
orderManager.ServerExternalMod = external;
@@ -306,7 +302,10 @@ namespace OpenRA.Network
{
var strings = node.Key.Split('@');
if (strings[0] == "GlobalSettings")
{
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
}
}
SetOrderLag(orderManager);
@@ -337,7 +336,12 @@ namespace OpenRA.Network
default:
{
ResolveOrder(order);
if (order.GroupedActors == null)
ResolveOrder(order);
else
foreach (var subject in order.GroupedActors)
ResolveOrder(Order.FromGroupedOrder(order, subject));
break;
}
}

View File

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

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
<TargetFramework>net472</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<UseVSHostingProcess>false</UseVSHostingProcess>
<LangVersion>5</LangVersion>
<LangVersion>7.3</LangVersion>
<DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
@@ -16,8 +16,12 @@
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
<Configurations>Release;Debug;Release-x86</Configurations>
<ManagedExeLauncher Condition="('$(OS)' != 'Windows_NT')">mono </ManagedExeLauncher>
<Configurations>Release;Debug</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
@@ -27,7 +31,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
<PropertyGroup Condition="'$(TargetPlatform)' == 'win-x86'">
<Prefer32bit>true</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
@@ -47,26 +51,12 @@
<StartArguments>Game.Mod=ts</StartArguments>
</PropertyGroup>
<ItemGroup>
<Reference Include="Open.Nat">
<HintPath>..\thirdparty\download\Open.Nat.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MaxMind.Db">
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
<Private>False</Private>
</Reference>
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<None Include="App.config" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="OpenRA-Eluant" Version="1.0.17" />
<PackageReference Include="OpenRA-Open.NAT" Version="1.0.0" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<AdditionalFiles Include="../stylecop.json" />
<AdditionalFiles Include="Properties/launchSettings.json" />
</ItemGroup>
@@ -76,8 +66,4 @@
<Analyzer Remove="@(Analyzer)" />
</ItemGroup>
</Target>
<Target Name="PostProcess" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(IntermediateOutputPath)\$(TargetFileName).processed">
<Exec Command="$(ManagedExeLauncher)&quot;$(TargetDir)OpenRA.PostProcess.exe&quot; &quot;$(TargetPath)&quot; -LAA" />
<Touch Files="$(IntermediateOutputPath)\$(TargetFileName).processed" AlwaysCreate="true" />
</Target>
</Project>

View File

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

View File

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

View File

@@ -70,7 +70,7 @@ namespace OpenRA.Orders
bool useSelect;
if (Game.Settings.Game.UseClassicMouseStyle && !InputOverridesSelection(world, worldPixel, mi))
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>();
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>();
else
{
var ordersWithCursor = world.Selection.Actors
@@ -81,7 +81,7 @@ namespace OpenRA.Orders
if (cursorOrder != null)
return cursorOrder.Cursor;
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>() &&
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any());
}
@@ -96,7 +96,7 @@ namespace OpenRA.Orders
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAtMouse(xy)
.Where(a => !a.Actor.IsDead)
.Where(a => !a.Actor.IsDead && a.Actor.Info.HasTraitInfo<ISelectableInfo>() && (a.Actor.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(a.Actor)))
.WithHighestSelectionPriority(xy, mi.Modifiers);
if (actor == null)
@@ -124,6 +124,8 @@ namespace OpenRA.Orders
return false;
}
public virtual void SelectionChanged(World world, IEnumerable<Actor> selected) { }
/// <summary>
/// Returns the most appropriate order for a given actor and target.
/// First priority is given to orders that interact with the given actors.

View File

@@ -14,7 +14,6 @@ using System.Collections.Generic;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Scripting;
@@ -37,6 +36,9 @@ namespace OpenRA
public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
{
public const string PlayerActorType = "Player";
public const string EditorPlayerActorType = "EditorPlayer";
struct StanceColors
{
public Color Self;
@@ -164,13 +166,22 @@ namespace OpenRA
if (!Spectating)
PlayerMask = new LongBitSet<PlayerBitMask>(InternalName);
var playerActorType = world.Type == WorldType.Editor ? "EditorPlayer" : "Player";
PlayerActor = world.CreateActor(playerActorType, new TypeDictionary { new OwnerInit(this) });
// Set this property before running any Created callbacks on the player actor
IsBot = BotType != null;
// Special case handling is required for the Player actor:
// Since Actor.Created would be called before PlayerActor is assigned here
// querying player traits in INotifyCreated.Created would crash.
// Therefore assign the uninitialized actor and run the Created callbacks
// by calling Initialize ourselves.
var playerActorType = world.Type == WorldType.Editor ? EditorPlayerActorType : PlayerActorType;
PlayerActor = new Actor(world, playerActorType, new TypeDictionary { new OwnerInit(this) });
PlayerActor.Initialize(true);
Shroud = PlayerActor.Trait<Shroud>();
FrozenActorLayer = PlayerActor.TraitOrDefault<FrozenActorLayer>();
// Enable the bot logic on the host
IsBot = BotType != null;
if (IsBot && Game.IsHost)
{
var logic = PlayerActor.TraitsImplementing<IBot>().FirstOrDefault(b => b.Info.Type == BotType);
@@ -242,8 +253,7 @@ namespace OpenRA
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
Player a, b;
if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
if (!left.TryGetClrValue(out Player a) || !right.TryGetClrValue(out Player b))
return false;
return a == b;

View File

@@ -10,7 +10,6 @@
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@@ -33,7 +32,7 @@ namespace OpenRA
SheetBuilder sheetBuilder;
[FieldLoader.Ignore]
Cache<Pair<PlayerBadge, int>, Sprite> iconCache;
Cache<(PlayerBadge, int), Sprite> iconCache;
Sprite LoadSprite(string url, int density)
{
@@ -84,15 +83,15 @@ namespace OpenRA
{
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
iconCache = new Cache<Pair<PlayerBadge, int>, Sprite>(p =>
iconCache = new Cache<(PlayerBadge Badge, int Density), Sprite>(p =>
{
if (p.Second > 2 && !string.IsNullOrEmpty(p.First.Icon3x))
return LoadSprite(p.First.Icon3x, 3);
if (p.Density > 2 && !string.IsNullOrEmpty(p.Badge.Icon3x))
return LoadSprite(p.Badge.Icon3x, 3);
if (p.Second > 1 && !string.IsNullOrEmpty(p.First.Icon2x))
return LoadSprite(p.First.Icon2x, 2);
if (p.Density > 1 && !string.IsNullOrEmpty(p.Badge.Icon2x))
return LoadSprite(p.Badge.Icon2x, 2);
return LoadSprite(p.First.Icon, 1);
return LoadSprite(p.Badge.Icon, 1);
});
}
@@ -114,7 +113,7 @@ namespace OpenRA
{
var ws = Game.Renderer.WindowScale;
var density = ws > 2 ? 3 : ws > 1 ? 2 : 1;
return iconCache[Pair.New(badge, density)];
return iconCache[(badge, density)];
}
}
}

View File

@@ -47,14 +47,9 @@ namespace OpenRA.Primitives
BitSetIndex bits = 0;
lock (Bits)
{
foreach (var value in values)
{
BitSetIndex valueBit;
if (Bits.TryGetValue(value, out valueBit))
if (Bits.TryGetValue(value, out var valueBit))
bits |= valueBit;
}
}
return bits;
}

View File

@@ -124,10 +124,10 @@ namespace OpenRA.Primitives
if (value.Length != 6 && value.Length != 8)
return false;
byte red, green, blue, alpha = 255;
if (!byte.TryParse(value.Substring(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out red)
|| !byte.TryParse(value.Substring(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out green)
|| !byte.TryParse(value.Substring(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out blue))
byte alpha = 255;
if (!byte.TryParse(value.Substring(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var red)
|| !byte.TryParse(value.Substring(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var green)
|| !byte.TryParse(value.Substring(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var blue))
return false;
if (value.Length == 8

View File

@@ -50,14 +50,9 @@ namespace OpenRA.Primitives
long bits = 0;
lock (Bits)
{
foreach (var value in values)
{
long valueBit;
if (Bits.TryGetValue(value, out valueBit))
if (Bits.TryGetValue(value, out var valueBit))
bits |= valueBit;
}
}
return bits;
}

View File

@@ -1,70 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
namespace OpenRA.Primitives
{
public struct Pair<T, U> : IEquatable<Pair<T, U>>
{
public T First;
public U Second;
public Pair(T first, U second)
{
First = first;
Second = second;
}
internal static IEqualityComparer<T> Tcomparer = EqualityComparer<T>.Default;
internal static IEqualityComparer<U> Ucomparer = EqualityComparer<U>.Default;
public static bool operator ==(Pair<T, U> a, Pair<T, U> b)
{
return Tcomparer.Equals(a.First, b.First) && Ucomparer.Equals(a.Second, b.Second);
}
public static bool operator !=(Pair<T, U> a, Pair<T, U> b)
{
return !(a == b);
}
public override int GetHashCode() { return First.GetHashCode() ^ Second.GetHashCode(); }
public bool Equals(Pair<T, U> other) { return this == other; }
public override bool Equals(object obj) { return obj is Pair<T, U> && Equals((Pair<T, U>)obj); }
public Pair<T, U> WithFirst(T t) { return new Pair<T, U>(t, Second); }
public Pair<T, U> WithSecond(U u) { return new Pair<T, U>(First, u); }
public static T AsFirst(Pair<T, U> p) { return p.First; }
public static U AsSecond(Pair<T, U> p) { return p.Second; }
public override string ToString()
{
return "({0},{1})".F(First, Second);
}
class PairEqualityComparer : IEqualityComparer<Pair<T, U>>
{
public bool Equals(Pair<T, U> x, Pair<T, U> y) { return x == y; }
public int GetHashCode(Pair<T, U> obj) { return obj.GetHashCode(); }
}
public static IEqualityComparer<Pair<T, U>> EqualityComparer { get { return new PairEqualityComparer(); } }
}
public static class Pair
{
public static Pair<T, U> New<T, U>(T t, U u) { return new Pair<T, U>(t, u); }
}
}

View File

@@ -0,0 +1,114 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Linq;
namespace OpenRA.Primitives
{
public struct Polygon
{
public static readonly Polygon Empty = new Polygon(Rectangle.Empty);
public readonly Rectangle BoundingRect;
public readonly int2[] Vertices;
bool isRectangle;
public Polygon(Rectangle bounds)
{
BoundingRect = bounds;
Vertices = new[] { bounds.TopLeft, bounds.BottomLeft, bounds.BottomRight, bounds.TopRight };
isRectangle = true;
}
public Polygon(int2[] vertices)
{
if (vertices != null && vertices.Length > 0)
{
Vertices = vertices;
var left = int.MaxValue;
var right = int.MinValue;
var top = int.MaxValue;
var bottom = int.MinValue;
foreach (var p in vertices)
{
left = Math.Min(left, p.X);
right = Math.Max(right, p.X);
top = Math.Min(top, p.Y);
bottom = Math.Max(bottom, p.Y);
}
BoundingRect = Rectangle.FromLTRB(left, top, right, bottom);
isRectangle = false;
}
else
{
isRectangle = true;
BoundingRect = Rectangle.Empty;
Vertices = Exts.MakeArray(4, _ => int2.Zero);
}
}
public bool IsEmpty { get { return BoundingRect.IsEmpty; } }
public bool Contains(int2 xy)
{
return isRectangle ? BoundingRect.Contains(xy) : Vertices.PolygonContains(xy);
}
public bool IntersectsWith(Rectangle rect)
{
var intersectsBoundingRect = BoundingRect.Left < rect.Right && BoundingRect.Right > rect.Left && BoundingRect.Top < rect.Bottom && BoundingRect.Bottom > rect.Top;
if (isRectangle)
return intersectsBoundingRect;
// Easy case 1: Rect and bounding box don't intersect
if (!intersectsBoundingRect)
return false;
// Easy case 2: Rect and bounding box intersect in a cross shape
if ((rect.Left <= BoundingRect.Left && rect.Right >= BoundingRect.Right) || (rect.Top <= BoundingRect.Top && rect.Bottom >= BoundingRect.Bottom))
return true;
// Easy case 3: Corner of rect is inside the polygon
if (Vertices.PolygonContains(rect.TopLeft) || Vertices.PolygonContains(rect.TopRight) || Vertices.PolygonContains(rect.BottomLeft) || Vertices.PolygonContains(rect.BottomRight))
return true;
// Easy case 4: Polygon vertex is inside rect
if (Vertices.Any(p => rect.Contains(p)))
return true;
// Hard case: check intersection of every line segment pair
var rectVertices = new[]
{
rect.TopLeft,
rect.BottomLeft,
rect.BottomRight,
rect.TopRight
};
for (var i = 0; i < Vertices.Length; i++)
for (var j = 0; j < 4; j++)
if (Exts.LinesIntersect(Vertices[i], Vertices[(i + 1) % Vertices.Length], rectVertices[j], rectVertices[(j + 1) % 4]))
return true;
return false;
}
public override int GetHashCode()
{
var code = BoundingRect.GetHashCode();
foreach (var v in Vertices)
code = ((code << 5) + code) ^ v.GetHashCode();
return code;
}
}
}

View File

@@ -66,6 +66,11 @@ namespace OpenRA.Primitives
public int2 Location { get { return new int2(X, Y); } }
public Size Size { get { return new Size(Width, Height); } }
public int2 TopLeft { get { return Location; } }
public int2 TopRight { get { return new int2(X + Width, Y); } }
public int2 BottomLeft { get { return new int2(X, Y + Height); } }
public int2 BottomRight { get { return new int2(X + Width, Y + Height); } }
public bool Contains(int x, int y)
{
return x >= Left && x < Right && y >= Top && y < Bottom;

View File

@@ -120,8 +120,7 @@ namespace OpenRA.Primitives
/// <param name="count">The length of the segment.</param>
public static Stream CreateWithoutOwningStream(Stream stream, long offset, int count)
{
Stream parentStream;
var nestedOffset = offset + GetOverallNestedOffset(stream, out parentStream);
var nestedOffset = offset + GetOverallNestedOffset(stream, out var parentStream);
// Special case FileStream - instead of creating an in-memory copy,
// just reference the portion of the on-disk file that we need to save memory.

View File

@@ -52,8 +52,7 @@ namespace OpenRA.Primitives
public bool Remove(T item)
{
Rectangle bounds;
if (!itemBounds.TryGetValue(item, out bounds))
if (!itemBounds.TryGetValue(item, out var bounds))
return false;
MutateBins(item, bounds, removeItem);
@@ -91,8 +90,7 @@ namespace OpenRA.Primitives
void MutateBins(T actor, Rectangle bounds, Action<Dictionary<T, Rectangle>, T, Rectangle> action)
{
int minRow, maxRow, minCol, maxCol;
BoundsToBinRowsAndCols(bounds, out minRow, out maxRow, out minCol, out maxCol);
BoundsToBinRowsAndCols(bounds, out var minRow, out var maxRow, out var minCol, out var maxCol);
for (var row = minRow; row < maxRow; row++)
for (var col = minCol; col < maxCol; col++)
@@ -110,8 +108,7 @@ namespace OpenRA.Primitives
public IEnumerable<T> InBox(Rectangle box)
{
int minRow, maxRow, minCol, maxCol;
BoundsToBinRowsAndCols(box, out minRow, out maxRow, out minCol, out maxCol);
BoundsToBinRowsAndCols(box, out var minRow, out var maxRow, out var minCol, out var maxCol);
// We want to return any items intersecting the box.
// If the box covers multiple bins, we must handle items that are contained in multiple bins and avoid
@@ -139,5 +136,7 @@ namespace OpenRA.Primitives
}
public IEnumerable<Rectangle> ItemBounds { get { return itemBounds.Values; } }
public IEnumerable<T> Items { get { return itemBounds.Keys; } }
}
}

View File

@@ -41,6 +41,11 @@ namespace OpenRA.Primitives
return data.ContainsKey(typeof(T));
}
public bool Contains(Type t)
{
return data.ContainsKey(t);
}
public T Get<T>()
{
return (T)Get(typeof(T), true);
@@ -56,8 +61,7 @@ namespace OpenRA.Primitives
object Get(Type t, bool throwsIfMissing)
{
List<object> ret;
if (!data.TryGetValue(t, out ret))
if (!data.TryGetValue(t, out var ret))
{
if (throwsIfMissing)
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(t));
@@ -71,8 +75,7 @@ namespace OpenRA.Primitives
public IEnumerable<T> WithInterface<T>()
{
List<object> objs;
if (data.TryGetValue(typeof(T), out objs))
if (data.TryGetValue(typeof(T), out var objs))
return objs.Cast<T>();
return new T[0];
}
@@ -89,8 +92,7 @@ namespace OpenRA.Primitives
void InnerRemove(Type t, object val)
{
List<object> objs;
if (!data.TryGetValue(t, out objs))
if (!data.TryGetValue(t, out var objs))
return;
objs.Remove(val);
if (objs.Count == 0)

View File

@@ -62,5 +62,6 @@ namespace OpenRA
public override string ToString() { return "{0},{1},{2}".F(X, Y, Z); }
public static readonly float3 Zero = new float3(0, 0, 0);
public static readonly float3 Ones = new float3(1, 1, 1);
}
}

View File

@@ -31,6 +31,15 @@ namespace OpenRA
public RgbaColorRenderer RgbaColorRenderer { get; private set; }
public SpriteRenderer SpriteRenderer { get; private set; }
public RgbaSpriteRenderer RgbaSpriteRenderer { get; private set; }
public bool WindowHasInputFocus
{
get
{
return Window.HasInputFocus;
}
}
public IReadOnlyDictionary<string, SpriteFont> Fonts;
internal IPlatformWindow Window { get; private set; }
@@ -66,7 +75,10 @@ namespace OpenRA
this.platform = platform;
var resolution = GetResolution(graphicSettings);
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, graphicSettings.VideoDisplay);
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height),
graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize,
graphicSettings.VideoDisplay, graphicSettings.GLProfile);
Context = Window.Context;
TempBufferSize = graphicSettings.BatchSize;
@@ -103,8 +115,7 @@ namespace OpenRA
font.Dispose();
using (new PerfTimer("SpriteFonts"))
{
if (fontSheetBuilder != null)
fontSheetBuilder.Dispose();
fontSheetBuilder?.Dispose();
fontSheetBuilder = new SheetBuilder(SheetType.BGRA, 512);
Fonts = modData.Manifest.Get<Fonts>().FontList.ToDictionary(x => x.Key,
x => new SpriteFont(x.Value.Font, modData.DefaultFileSystem.Open(x.Value.Font).ReadAllBytes(),
@@ -144,8 +155,7 @@ namespace OpenRA
if (screenSprite == null || screenSprite.Sheet.Size != surfaceBufferSize)
{
if (screenBuffer != null)
screenBuffer.Dispose();
screenBuffer?.Dispose();
// Render the screen into a frame buffer to simplify reading back screenshots
screenBuffer = Context.CreateFrameBuffer(surfaceBufferSize, Color.FromArgb(0xFF, 0, 0, 0));
@@ -183,8 +193,7 @@ namespace OpenRA
var worldBufferSize = worldViewport.Size.NextPowerOf2();
if (worldSprite == null || worldSprite.Sheet.Size != worldBufferSize)
{
if (worldBuffer != null)
worldBuffer.Dispose();
worldBuffer?.Dispose();
// Render the world into a framebuffer at 1:1 scaling to allow the depth buffer to match the artwork at all zoom levels
worldBuffer = Context.CreateFrameBuffer(worldBufferSize);
@@ -301,6 +310,8 @@ namespace OpenRA
public Size NativeResolution { get { return Window.NativeWindowSize; } }
public float WindowScale { get { return Window.EffectiveWindowScale; } }
public float NativeWindowScale { get { return Window.NativeWindowScale; } }
public GLProfile GLProfile { get { return Window.GLProfile; } }
public GLProfile[] SupportedGLProfiles { get { return Window.SupportedGLProfiles; } }
public interface IBatchRenderer { void Flush(); }
@@ -315,8 +326,7 @@ namespace OpenRA
{
if (currentBatchRenderer == value)
return;
if (currentBatchRenderer != null)
currentBatchRenderer.Flush();
currentBatchRenderer?.Flush();
currentBatchRenderer = value;
}
}
@@ -446,8 +456,7 @@ namespace OpenRA
{
WorldModelRenderer.Dispose();
tempBuffer.Dispose();
if (fontSheetBuilder != null)
fontSheetBuilder.Dispose();
fontSheetBuilder?.Dispose();
if (Fonts != null)
foreach (var font in Fonts.Values)
font.Dispose();

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