Compare commits

...

199 Commits

Author SHA1 Message Date
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
abcdefg30
df4c363e9c Notify blockers upon paradropping 2020-03-03 20:51:39 +00:00
abcdefg30
a909a3e692 Ignore self and actors not at ground level in Parachutable.OnLanded 2020-03-03 20:51:39 +00:00
abcdefg30
dd26253905 Fix the IgnoreActor check in Parachutable 2020-03-03 20:51:39 +00:00
abcdefg30
69b7ba2d22 Fix NREs in ProductionParadrop 2020-03-03 20:51:39 +00:00
Paul Chote
d2f306e488 Fix GetActorsAt(CPos, SubCell) with special-case subcells.
If given FullCell or Any we should be returning actors in
any subcell, not none.
2020-03-03 20:06:51 +01:00
Paul Chote
4a6fefa434 Disable idle scanning on RA planes. 2020-03-02 22:22:56 +01:00
Paul Chote
16e0ea611e Revert "Fix AttackFollow ignoring allowMove flag when auto-targeting."
This reverts commit 3e116060cf.
2020-03-02 22:22:56 +01:00
Paul Chote
05a2e77be2 Add support for uncompressed databases. 2020-03-02 17:29:30 +01:00
Paul Chote
dd2fa36261 Fix invalid channel server crash. 2020-03-02 17:29:30 +01:00
abcdefg30
5fa1dec6d8 Fix a crash in --clear-invalid-mod-registrations 2020-03-02 17:26:34 +01:00
Paul Chote
f86d96794d Add explicitly defined version strings to the lua docs. 2020-03-02 17:24:17 +01:00
501 changed files with 10739 additions and 5833 deletions

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

@@ -34,12 +34,13 @@ env:
# call OpenRA to check for YAML errors
# Run the NUnit tests
script:
- travis_retry make all-dependencies
- make all
- test "$TRAVIS_OS_NAME" == "linux" && make check || echo "Skipping check"
- test "$TRAVIS_OS_NAME" == "linux" && make check-scripts || echo "Skipping scripts check"
- test "$TRAVIS_OS_NAME" == "linux" && make test || echo "Skipping tests"
- test "$TRAVIS_OS_NAME" == "linux" && make nunit || echo "Skipping nunit tests"
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult OpenRA.Test.dll;
fi
# Only watch the development branch and tagged release.
branches:

View File

@@ -160,9 +160,6 @@ FreeType License.
Using OpenAL Soft distributed under the GNU LGPL.
Using MaxMind GeoIP2 .NET API distributed under
the Apache 2.0 license.
Using SDL2-CS and OpenAL-CS created by Ethan
Lee and released under the zlib license.
@@ -182,6 +179,9 @@ Krueger and distributed under the GNU GPL terms.
Using rix0rrr.BeaconLib developed by Rico Huijbers
distributed under MIT License.
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.
Finally, special thanks goes to the original teams
at Westwood Studios and EA for creating the classic
games which OpenRA aims to reimagine.

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,11 +19,17 @@ Run the game with `launch-game.cmd`. It can be handed arguments that specify the
Linux
=====
Mono, version 5.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
Use `make dependencies` to map the native libraries to your system and fetch the remaining CLI dependencies to place them at the appropriate places.
To compile OpenRA, run `make` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
To compile OpenRA, run `make all` from the command line. After this one can run the game with `./launch-game.sh`. It is also possible to specify the mod you wish to run from the command line, e.g. with `./launch-game.sh Game.Mod=ts` if you wish to try the experimental Tiberian Sun mod.
The default behaviour on the x86_64 architecture is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`.
If you choose to use system libraries, or your system is not x86_64, you will need to install the following using your system package manager:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
Type `sudo make install` for system-wide installation. Run `sudo make install-linux-shortcuts` to get startup scripts, icons and desktop files. You can then run the Red Alert by executing the `openra-ra` command, the Dune 2000 mod by running the `openra-d2k` command and Tiberian Dawn by the `openra-cnc` command. Alternatively, you can also run these mods by clicking on their desktop shortcuts if you ran `sudo make install-linux-shortcuts`.
@@ -95,18 +95,17 @@ The EPEL repository is required in order for the following command to run proper
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
```
OSX
macOS
=====
Before compiling OpenRA you must install the following dependencies:
* [Mono >= 5.4](https://www.mono-project.com/download/stable/#download-mac)
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
Use `make dependencies` to download pre-compiled native libraries for:
* [SDL 2](http://www.libsdl.org/download-2.0.php)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm)
* [OpenAL](http://kcat.strangesoft.net/openal.html)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
To compile OpenRA, run `make` from the command line.
The default behaviour is to download several pre-compiled native libraries using the Nuget packaging manager. If you prefer to use system libraries, compile instead using `make TARGETPLATFORM=unix-generic`. If you choose to use system libraries you will need to install:
* [SDL 2](http://www.libsdl.org/download-2.0.php) (`brew install sdl2`)
* [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm) (`brew install freetype`)
* [OpenAL](http://kcat.strangesoft.net/openal.html) (`brew install openal-soft`)
* [liblua 5.1](http://luabinaries.sourceforge.net/download.html) (`brew install lua@5.1`)
Run with `./launch-game.sh`.

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

View File

@@ -74,6 +74,31 @@ namespace OpenRA
}
}
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class ConditionState
{
/// <summary>Delegates that have registered to be notified when this condition changes.</summary>
public readonly List<VariableObserverNotifier> Notifiers = new List<VariableObserverNotifier>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>();
}
readonly Dictionary<string, ConditionState> conditionStates = new Dictionary<string, ConditionState>();
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
readonly Dictionary<int, string> conditionTokens = new Dictionary<int, string>();
int nextConditionToken = 1;
/// <summary>Cache of condition -> enabled state for quick evaluation of token counter conditions.</summary>
readonly Dictionary<string, int> conditionCache = new Dictionary<string, int>();
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
readonly IReadOnlyDictionary<string, int> readOnlyConditionCache;
internal SyncHash[] SyncHashes { get; private set; }
readonly IFacing facing;
@@ -93,6 +118,8 @@ namespace OpenRA
{
var init = new ActorInitializer(this, initDict);
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
World = world;
ActorID = world.NextAID();
if (initDict.Contains<OwnerInit>())
@@ -148,9 +175,36 @@ namespace OpenRA
{
created = true;
// Make sure traits are usable for condition notifiers
foreach (var t in TraitsImplementing<INotifyCreated>())
t.Created(this);
var allObserverNotifiers = new HashSet<VariableObserverNotifier>();
foreach (var provider in TraitsImplementing<IObservesVariables>())
{
foreach (var variableUser in provider.GetVariableObservers())
{
allObserverNotifiers.Add(variableUser.Notifier);
foreach (var variable in variableUser.Variables)
{
var cs = conditionStates.GetOrAdd(variable);
cs.Notifiers.Add(variableUser.Notifier);
// Initialize conditions that have not yet been granted to 0
// NOTE: Some conditions may have already been granted by INotifyCreated calling GrantCondition,
// and we choose to assign the token count to safely cover both cases instead of adding an if branch.
conditionCache[variable] = cs.Tokens.Count;
}
}
}
// Update all traits with their initial condition state
foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache);
// TODO: Some traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead.
// The initial activity should run before any activities queued by INotifyCreated.Created
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
ICreationActivity creationActivity = null;
@@ -233,7 +287,7 @@ namespace OpenRA
yield return r;
}
public Rectangle MouseBounds(WorldRenderer wr)
public Polygon MouseBounds(WorldRenderer wr)
{
foreach (var mb in mouseBounds)
{
@@ -242,7 +296,7 @@ namespace OpenRA
return bounds;
}
return Rectangle.Empty;
return Polygon.Empty;
}
public void QueueActivity(bool queued, Activity nextActivity)
@@ -454,6 +508,60 @@ namespace OpenRA
return new[] { CenterPosition };
}
#region Conditions
void UpdateConditionState(string condition, int token, bool isRevoke)
{
ConditionState conditionState = conditionStates.GetOrAdd(condition);
if (isRevoke)
conditionState.Tokens.Remove(token);
else
conditionState.Tokens.Add(token);
conditionCache[condition] = conditionState.Tokens.Count;
// Conditions may be granted or revoked before the state is initialized.
// These notifications will be processed after INotifyCreated.Created.
if (created)
foreach (var notify in conditionState.Notifiers)
notify(this, readOnlyConditionCache);
}
/// <summary>Grants a specified condition.</summary>
/// <returns>The token that is used to revoke this condition.</returns>
public int GrantCondition(string condition)
{
var token = nextConditionToken++;
conditionTokens.Add(token, condition);
UpdateConditionState(condition, token, false);
return token;
}
/// <summary>
/// Revokes a previously granted condition.
/// </summary>
/// <param name="token">The token ID returned by GrantCondition.</param>
/// <returns>The invalid token ID.</returns>
public int RevokeCondition(int token)
{
string condition;
if (!conditionTokens.TryGetValue(token, out condition))
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
conditionTokens.Remove(token);
UpdateConditionState(condition, token, true);
return InvalidConditionToken;
}
/// <summary>Returns whether the specified token is valid for RevokeCondition</summary>
public bool TokenValid(int token)
{
return conditionTokens.ContainsKey(token);
}
#endregion
#region Scripting interface
Lazy<ScriptActorInterface> luaInterface;

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

@@ -59,7 +59,9 @@ namespace OpenRA
public ExternalMods()
{
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
// Don't try to load mod icons if we don't have a texture to put them in
if (Game.Renderer != null)
sheetBuilder = new SheetBuilder(SheetType.BGRA, CreateSheet);
// Several types of support directory types are available, depending on
// how the player has installed and launched the game.
@@ -94,20 +96,24 @@ namespace OpenRA
void LoadMod(MiniYaml yaml, string path = null, bool forceRegistration = false)
{
var mod = FieldLoader.Load<ExternalMod>(yaml);
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
mod.Icon = sheetBuilder.Add(new Png(stream));
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
if (sheetBuilder != null)
{
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
mod.Icon = sheetBuilder.Add(new Png(stream));
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);
}
// Avoid possibly overwriting a valid mod with an obviously bogus one
var key = ExternalMod.MakeKey(mod);

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.

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;
}
@@ -89,12 +88,12 @@ namespace OpenRA
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager("<no server>", -1, "", new ReplayConnection(replayFile)));
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager("<no server>", -1, "", new EchoConnection()));
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
}
// More accurate replacement for Environment.TickCount
@@ -105,14 +104,14 @@ namespace OpenRA
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static event Action<string, int> OnRemoteDirectConnect = (a, b) => { };
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static void RemoteDirectConnect(string host, int port)
public static void RemoteDirectConnect(ConnectionTarget endpoint)
{
OnRemoteDirectConnect(host, port);
OnRemoteDirectConnect(endpoint);
}
// Hacky workaround for orderManager visibility
@@ -234,7 +233,7 @@ namespace OpenRA
LobbyInfoChanged += lobbyReady;
om = JoinServer(IPAddress.Loopback.ToString(), CreateLocalServer(mapUID), "");
om = JoinServer(CreateLocalServer(mapUID), "");
}
public static bool IsHost
@@ -302,6 +301,7 @@ namespace OpenRA
Log.AddChannel("graphics", "graphics.log");
Log.AddChannel("geoip", "geoip.log");
Log.AddChannel("nat", "nat.log");
Log.AddChannel("client", "client.log");
var platforms = new[] { Settings.Game.Platform, "Default", null };
foreach (var p in platforms)
@@ -385,7 +385,7 @@ namespace OpenRA
LobbyInfoChanged = () => { };
ConnectionStateChanged = om => { };
BeforeGameStart = () => { };
OnRemoteDirectConnect = (a, b) => { };
OnRemoteDirectConnect = endpoint => { };
delayedActions = new ActionQueue();
Ui.ResetAll();
@@ -899,12 +899,19 @@ namespace OpenRA
return ModData.ObjectCreator.CreateObject<T>(name);
}
public static void CreateServer(ServerSettings settings)
public static ConnectionTarget CreateServer(ServerSettings settings)
{
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort),
new IPEndPoint(IPAddress.Any, settings.ListenPort)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Multiplayer);
return server.GetEndpointForLocalConnection();
}
public static int CreateLocalServer(string map)
public static ConnectionTarget CreateLocalServer(string map)
{
var settings = new ServerSettings()
{
@@ -913,9 +920,14 @@ namespace OpenRA
AdvertiseOnline = false
};
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Loopback, 0),
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);
return server.Port;
return server.GetEndpointForLocalConnection();
}
public static bool IsCurrentWorld(World world)

View File

@@ -50,6 +50,16 @@ namespace OpenRA.GameRules
WeaponTarget = args.GuidedTarget;
}
// For places that only want to update some of the fields (usually DamageModifiers)
public WarheadArgs(WarheadArgs args)
{
Weapon = args.Weapon;
DamageModifiers = args.DamageModifiers;
Source = args.Source;
SourceActor = args.SourceActor;
WeaponTarget = args.WeaponTarget;
}
// Default empty constructor for callers that want to initialize fields themselves
public WarheadArgs() { }
}

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

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

@@ -34,8 +34,8 @@ namespace OpenRA.Graphics
Rectangle Bounds { get; }
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, int facing);
Sprite GetShadow(int frame, int facing);
Sprite GetSprite(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing);
}
public interface ISpriteSequenceLoader

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,6 +89,8 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get { return minZoom; } }
public void AdjustZoom(float dz)
{
// Exponential ensures that equal positive and negative steps have the same effect
@@ -237,6 +244,9 @@ namespace OpenRA.Graphics
Zoom = minZoom;
else
Zoom = Zoom.Clamp(minZoom, maxZoom);
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}
public CPos ViewToWorld(int2 view)

View File

@@ -277,11 +277,13 @@ namespace OpenRA.Graphics
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.MediumSpringGreen);
}
foreach (var r in World.ScreenMap.MouseBounds(World.RenderPlayer))
foreach (var b in World.ScreenMap.MouseBounds(World.RenderPlayer))
{
var tl = Viewport.WorldToViewPx(new float2(r.Left, r.Top));
var br = Viewport.WorldToViewPx(new float2(r.Right, r.Bottom));
Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.OrangeRed);
var points = b.Vertices
.Select(p => Viewport.WorldToViewPx(p).ToFloat2())
.ToArray();
Game.Renderer.RgbaColorRenderer.DrawPolygon(points, 1, Color.OrangeRed);
}
}

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

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

View File

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

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))
@@ -164,13 +168,23 @@ namespace OpenRA
var extraLocation = flags.HasField(OrderFields.ExtraLocation) ? new CPos(r.ReadInt32()) : CPos.Zero;
var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0;
Actor[] groupedActors = null;
if (flags.HasField(OrderFields.Grouped))
{
var count = r.ReadInt32();
if (world != null)
groupedActors = Exts.MakeArray(count, _ => world.GetActorById(r.ReadUInt32()));
else
r.ReadBytes(4 * count);
}
if (world == null)
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData);
return new Order(order, null, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
if (subject == null && flags.HasField(OrderFields.Subject))
return null;
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData);
return new Order(order, subject, target, targetString, queued, extraActors, extraLocation, extraData, groupedActors);
}
case OrderType.Handshake:
@@ -231,6 +245,11 @@ namespace OpenRA
return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString };
}
public static Order FromGroupedOrder(Order grouped, Actor subject)
{
return new Order(grouped.OrderString, subject, grouped.Target, grouped.TargetString, grouped.Queued, grouped.ExtraActors, grouped.ExtraLocation, grouped.ExtraData);
}
public static Order Command(string text)
{
return new Order("Command", null, false) { IsImmediate = true, TargetString = text };
@@ -255,11 +274,11 @@ namespace OpenRA
public Order()
: this(null, null, Target.Invalid, null, false, null, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0) { }
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public byte[] Serialize()
{
@@ -267,7 +286,7 @@ namespace OpenRA
if (Type == OrderType.Handshake)
minLength += TargetString.Length + 1;
else if (Type == OrderType.Fields)
minLength += 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
minLength += 4 + 2 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4 + 4;
if (ExtraActors != null)
minLength += ExtraActors.Length * 4;
@@ -308,6 +327,9 @@ namespace OpenRA
if (Queued)
fields |= OrderFields.Queued;
if (GroupedActors != null)
fields |= OrderFields.Grouped;
if (ExtraActors != null)
fields |= OrderFields.ExtraActors;
@@ -317,7 +339,7 @@ namespace OpenRA
if (Target.SerializableCell != null)
fields |= OrderFields.TargetIsCell;
w.Write((byte)fields);
w.Write((short)fields);
if (fields.HasField(OrderFields.Subject))
w.Write(UIntFromActor(Subject));
@@ -362,6 +384,13 @@ namespace OpenRA
if (fields.HasField(OrderFields.ExtraData))
w.Write(ExtraData);
if (fields.HasField(OrderFields.Grouped))
{
w.Write(GroupedActors.Length);
foreach (var a in GroupedActors)
w.Write(UIntFromActor(a));
}
break;
}

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

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using OpenRA.FileFormats;
using OpenRA.Primitives;
@@ -32,6 +33,13 @@ namespace OpenRA.Network
public int LocalClientId { get { return -1; } }
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
public IPEndPoint EndPoint
{
get { throw new NotSupportedException("A replay connection doesn't have an endpoint"); }
}
public string ErrorMessage { get { return null; } }
public readonly int TickCount;
public readonly int FinalGameTick;
public readonly bool IsValid;

View File

@@ -337,7 +337,12 @@ namespace OpenRA.Network
default:
{
ResolveOrder(order);
if (order.GroupedActors == null)
ResolveOrder(order);
else
foreach (var subject in order.GroupedActors)
ResolveOrder(Order.FromGroupedOrder(order, subject));
break;
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
<TargetFramework>net472</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<UseVSHostingProcess>false</UseVSHostingProcess>
@@ -16,8 +16,12 @@
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
<Configurations>Release;Debug;Release-x86</Configurations>
<ManagedExeLauncher Condition="('$(OS)' != 'Windows_NT')">mono </ManagedExeLauncher>
<Configurations>Release;Debug</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
@@ -27,7 +31,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release-x86'">
<PropertyGroup Condition="'$(TargetPlatform)' == 'win-x86'">
<Prefer32bit>true</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
@@ -47,26 +51,12 @@
<StartArguments>Game.Mod=ts</StartArguments>
</PropertyGroup>
<ItemGroup>
<Reference Include="Open.Nat">
<HintPath>..\thirdparty\download\Open.Nat.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MaxMind.Db">
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
<Private>False</Private>
</Reference>
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<None Include="App.config" />
<PackageReference Include="OpenRA-Eluant" Version="1.0.17" />
<PackageReference Include="OpenRA-Open.NAT" Version="1.0.0" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<AdditionalFiles Include="../stylecop.json" />
<AdditionalFiles Include="Properties/launchSettings.json" />
</ItemGroup>
@@ -76,8 +66,4 @@
<Analyzer Remove="@(Analyzer)" />
</ItemGroup>
</Target>
<Target Name="PostProcess" AfterTargets="Build" Inputs="$(TargetPath)" Outputs="$(IntermediateOutputPath)\$(TargetFileName).processed">
<Exec Command="$(ManagedExeLauncher)&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
@@ -53,8 +53,7 @@ namespace OpenRA.Orders
world.CancelInputMode();
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
foreach (var subject in Subjects)
yield return new Order(OrderName, subject, Target.FromCell(world, cell), queued);
yield return new Order(OrderName, null, Target.FromCell(world, cell), queued, null, Subjects.ToArray());
}
}

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)

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

@@ -139,5 +139,7 @@ namespace OpenRA.Primitives
}
public IEnumerable<Rectangle> ItemBounds { get { return itemBounds.Values; } }
public IEnumerable<T> Items { get { return itemBounds.Keys; } }
}
}

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;
@@ -301,6 +313,8 @@ namespace OpenRA
public Size NativeResolution { get { return Window.NativeWindowSize; } }
public float WindowScale { get { return Window.EffectiveWindowScale; } }
public float NativeWindowScale { get { return Window.NativeWindowScale; } }
public GLProfile GLProfile { get { return Window.GLProfile; } }
public GLProfile[] SupportedGLProfiles { get { return Window.SupportedGLProfiles; } }
public interface IBatchRenderer { void Flush(); }

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Traits
{
public static int SelectionPriority(this ActorInfo a, Modifiers modifiers)
{
var selectableInfo = a.TraitInfoOrDefault<SelectableInfo>();
var selectableInfo = a.TraitInfoOrDefault<ISelectableInfo>();
return selectableInfo != null ? BaseSelectionPriority(selectableInfo, modifiers) : int.MinValue;
}
@@ -28,7 +28,7 @@ namespace OpenRA.Traits
public static int SelectionPriority(this Actor a, Modifiers modifiers)
{
var info = a.Info.TraitInfo<SelectableInfo>();
var info = a.Info.TraitInfo<ISelectableInfo>();
var basePriority = BaseSelectionPriority(info, modifiers);
var viewer = (a.World.LocalPlayer == null || a.World.LocalPlayer.Spectating) ? a.World.RenderPlayer : a.World.LocalPlayer;
@@ -47,7 +47,7 @@ namespace OpenRA.Traits
}
}
static int BaseSelectionPriority(SelectableInfo info, Modifiers modifiers)
static int BaseSelectionPriority(ISelectableInfo info, Modifiers modifiers)
{
var priority = info.Priority;
@@ -73,14 +73,18 @@ namespace OpenRA.Traits
return actors.MaxByOrDefault(a => CalculateActorSelectionPriority(a.Info, a.MouseBounds, selectionPixel, modifiers));
}
static long CalculateActorSelectionPriority(ActorInfo info, Rectangle bounds, int2 selectionPixel, Modifiers modifiers)
static long CalculateActorSelectionPriority(ActorInfo info, Polygon bounds, int2 selectionPixel, Modifiers modifiers)
{
if (bounds.IsEmpty)
return info.SelectionPriority(modifiers);
// Assume that the center of the polygon is the same as the center of the bounding box
// This isn't necessarily true for arbitrary polygons, but is fine for the hexagonal and diamond
// shapes that are currently implemented
var br = bounds.BoundingRect;
var centerPixel = new int2(
bounds.Left + bounds.Size.Width / 2,
bounds.Top + bounds.Size.Height / 2);
br.Left + br.Size.Width / 2,
br.Top + br.Size.Height / 2);
var pixelDistance = (centerPixel - selectionPixel).Length;
return info.SelectionPriority(modifiers) - (long)pixelDistance << 16;

View File

@@ -18,7 +18,6 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Support;
@@ -43,8 +42,6 @@ namespace OpenRA.Server
{
public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match.";
public readonly IPAddress Ip;
public readonly int Port;
public readonly MersenneTwister Random = new MersenneTwister();
public readonly ServerType Type;
@@ -64,7 +61,7 @@ namespace OpenRA.Server
public GameSave GameSave = null;
readonly int randomSeed;
readonly TcpListener listener;
readonly List<TcpListener> listeners = new List<TcpListener>();
readonly TypeDictionary serverTraits = new TypeDictionary();
readonly PlayerDatabase playerDatabase;
@@ -129,15 +126,43 @@ namespace OpenRA.Server
t.GameEnded(this);
}
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, ServerType type)
public Server(List<IPEndPoint> endpoints, ServerSettings settings, ModData modData, ServerType type)
{
Log.AddChannel("server", "server.log", true);
listener = new TcpListener(endpoint);
listener.Start();
var localEndpoint = (IPEndPoint)listener.LocalEndpoint;
Ip = localEndpoint.Address;
Port = localEndpoint.Port;
SocketException lastException = null;
var checkReadServer = new List<Socket>();
foreach (var endpoint in endpoints)
{
var listener = new TcpListener(endpoint);
try
{
try
{
listener.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 1);
}
catch (Exception ex)
{
if (ex is SocketException || ex is ArgumentException)
Log.Write("server", "Failed to set socket option on {0}: {1}", endpoint.ToString(), ex.Message);
else
throw;
}
listener.Start();
listeners.Add(listener);
checkReadServer.Add(listener.Server);
}
catch (SocketException ex)
{
lastException = ex;
Log.Write("server", "Failed to listen on {0}: {1}", endpoint.ToString(), ex.Message);
}
}
if (listeners.Count == 0)
throw lastException;
Type = type;
Settings = settings;
@@ -149,7 +174,8 @@ namespace OpenRA.Server
randomSeed = (int)DateTime.Now.ToBinary();
GeoIP.Initialize(settings.GeoIPDatabase);
if (type != ServerType.Local && settings.EnableGeoIP)
GeoIP.Initialize();
if (UPnP.Status == UPnPStatus.Enabled)
UPnP.ForwardPort(Settings.ListenPort, Settings.ListenPort).Wait();
@@ -185,7 +211,7 @@ namespace OpenRA.Server
{
var checkRead = new List<Socket>();
if (State == ServerState.WaitingPlayers)
checkRead.Add(listener.Server);
checkRead.AddRange(checkReadServer);
checkRead.AddRange(Conns.Select(c => c.Socket));
checkRead.AddRange(PreConns.Select(c => c.Socket));
@@ -204,9 +230,10 @@ namespace OpenRA.Server
foreach (var s in checkRead)
{
if (s == listener.Server)
var serverIndex = checkReadServer.IndexOf(s);
if (serverIndex >= 0)
{
AcceptConnection();
AcceptConnection(listeners[serverIndex]);
continue;
}
@@ -245,9 +272,14 @@ namespace OpenRA.Server
PreConns.Clear();
Conns.Clear();
try { listener.Stop(); }
catch { }
}) { IsBackground = true }.Start();
foreach (var listener in listeners)
{
try { listener.Stop(); }
catch { }
}
})
{ IsBackground = true }.Start();
}
int nextPlayerIndex;
@@ -256,7 +288,7 @@ namespace OpenRA.Server
return nextPlayerIndex++;
}
void AcceptConnection()
void AcceptConnection(TcpListener listener)
{
Socket newSocket;
@@ -348,7 +380,7 @@ namespace OpenRA.Server
{
Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name),
IPAddress = ipAddress.ToString(),
AnonymizedIPAddress = Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
AnonymizedIPAddress = Type != ServerType.Local && Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
Location = GeoIP.LookupCountry(ipAddress),
Index = newConn.PlayerIndex,
PreferredColor = handshake.Client.PreferredColor,
@@ -498,10 +530,16 @@ namespace OpenRA.Server
profile.ProfileName, profile.ProfileID);
}
else if (profile.KeyRevoked)
{
profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
else
{
profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
}
else
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",
@@ -949,7 +987,8 @@ namespace OpenRA.Server
public void StartGame()
{
listener.Stop();
foreach (var listener in listeners)
listener.Stop();
Console.WriteLine("[{0}] Game started", DateTime.Now.ToString(Settings.TimestampFormat));
@@ -1011,5 +1050,22 @@ namespace OpenRA.Server
});
}
}
public ConnectionTarget GetEndpointForLocalConnection()
{
var endpoints = new List<DnsEndPoint>();
foreach (var listener in listeners)
{
var endpoint = (IPEndPoint)listener.LocalEndpoint;
if (IPAddress.IPv6Any.Equals(endpoint.Address))
endpoints.Add(new DnsEndPoint(IPAddress.IPv6Loopback.ToString(), endpoint.Port));
else if (IPAddress.Any.Equals(endpoint.Address))
endpoints.Add(new DnsEndPoint(IPAddress.Loopback.ToString(), endpoint.Port));
else
endpoints.Add(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port));
}
return new ConnectionTarget(endpoints);
}
}
}

View File

@@ -82,13 +82,12 @@ namespace OpenRA
[Desc("Sets the timestamp format. Defaults to the ISO 8601 standard.")]
public string TimestampFormat = "yyyy-MM-ddTHH:mm:ss";
[Desc("Path to a MaxMind GeoLite2 database to use for player geo-location.",
"Database files can be downloaded from https://dev.maxmind.com/geoip/geoip2/geolite2/")]
public string GeoIPDatabase = null;
[Desc("Allow clients to see anonymised IPs for other clients.")]
public bool ShareAnonymizedIPs = true;
[Desc("Allow clients to see the country of other clients.")]
public bool EnableGeoIP = true;
public ServerSettings Clone()
{
return (ServerSettings)MemberwiseClone();
@@ -173,12 +172,15 @@ namespace OpenRA
[Desc("Disable operating-system provided cursor rendering.")]
public bool DisableHardwareCursors = false;
[Desc("Use OpenGL ES if both ES and regular OpenGL are available.")]
public bool PreferGLES = false;
[Desc("Display index to use in a multi-monitor fullscreen setup.")]
public int VideoDisplay = 0;
[Desc("Preferred OpenGL profile to use.",
"Modern: OpenGL Core Profile 3.2 or greater.",
"Embedded: OpenGL ES 3.0 or greater.",
"Legacy: OpenGL 2.1 with framebuffer_object extension.")]
public GLProfile GLProfile = GLProfile.Modern;
public int BatchSize = 8192;
public int SheetSize = 2048;
@@ -228,7 +230,8 @@ namespace OpenRA
public int MouseScrollDeadzone = 8;
public bool UseClassicMouseStyle = false;
public bool ClassicMouseMiddleScroll = false;
public bool UseAlternateScrollButton = false;
public StatusBarsType StatusBars = StatusBarsType.Standard;
public TargetLinesType TargetLines = TargetLinesType.Manual;
public bool UsePlayerStanceColors = false;

View File

@@ -9,6 +9,9 @@
*/
#endregion
using System;
using OpenRA.Network;
namespace OpenRA
{
public class LaunchArguments
@@ -38,17 +41,28 @@ namespace OpenRA
FieldLoader.LoadField(this, f.Name, args.GetValue("Launch" + "." + f.Name, ""));
}
public string GetConnectAddress()
public ConnectionTarget GetConnectEndPoint()
{
var connect = string.Empty;
try
{
Uri uri;
if (!string.IsNullOrEmpty(URI))
uri = new Uri(URI);
else if (!string.IsNullOrEmpty(Connect))
uri = new Uri("tcp://" + Connect);
else
return null;
if (!string.IsNullOrEmpty(Connect))
connect = Connect;
if (!string.IsNullOrEmpty(URI))
connect = URI.Substring(URI.IndexOf("://", System.StringComparison.Ordinal) + 3).TrimEnd('/');
return connect;
if (uri.IsAbsoluteUri)
return new ConnectionTarget(uri.Host, uri.Port);
else
return null;
}
catch (Exception ex)
{
Log.Write("client", "Failed to parse Launch.URI or Launch.Connect: {0}", ex.Message);
return null;
}
}
}
}

View File

@@ -68,8 +68,7 @@ namespace OpenRA.Traits
public IRenderable[] Renderables = NoRenderables;
public Rectangle[] ScreenBounds = NoBounds;
// TODO: Replace this with an int2[] polygon
public Rectangle MouseBounds = Rectangle.Empty;
public Polygon MouseBounds = Polygon.Empty;
static readonly IRenderable[] NoRenderables = new IRenderable[0];
static readonly Rectangle[] NoBounds = new Rectangle[0];

View File

@@ -66,9 +66,6 @@ namespace OpenRA.Traits
void Kill(Actor self, Actor attacker, BitSet<DamageType> damageTypes);
}
// depends on the order of pips in WorldRenderer.cs!
public enum PipType { Transparent, Green, Yellow, Red, Gray, Blue, Ammo, AmmoEmpty }
[Flags]
public enum Stance
{
@@ -123,43 +120,10 @@ namespace OpenRA.Traits
IEnumerable<Rectangle> ScreenBounds(Actor self, WorldRenderer wr);
}
// TODO: Replace Rectangle with an int2[] polygon
public interface IMouseBounds { Rectangle MouseoverBounds(Actor self, WorldRenderer wr); }
public interface IMouseBounds { Polygon MouseoverBounds(Actor self, WorldRenderer wr); }
public interface IMouseBoundsInfo : ITraitInfoInterface { }
public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); }
// HACK: This provides a shim for legacy code until it can be rewritten
public interface IDecorationBounds { Rectangle DecorationBounds(Actor self, WorldRenderer wr); }
public interface IDecorationBoundsInfo : ITraitInfoInterface { }
public static class DecorationBoundsExtensions
{
public static Rectangle FirstNonEmptyBounds(this IEnumerable<IDecorationBounds> decorationBounds, Actor self, WorldRenderer wr)
{
// PERF: Avoid LINQ.
foreach (var decoration in decorationBounds)
{
var bounds = decoration.DecorationBounds(self, wr);
if (!bounds.IsEmpty)
return bounds;
}
return Rectangle.Empty;
}
public static Rectangle FirstNonEmptyBounds(this IDecorationBounds[] decorationBounds, Actor self, WorldRenderer wr)
{
// PERF: Avoid LINQ.
foreach (var decoration in decorationBounds)
{
var bounds = decoration.DecorationBounds(self, wr);
if (!bounds.IsEmpty)
return bounds;
}
return Rectangle.Empty;
}
}
public interface IIssueOrder
{
IEnumerable<IOrderTargeter> Orders { get; }
@@ -296,12 +260,14 @@ namespace OpenRA.Traits
public interface ILoadsPalettes { void LoadPalettes(WorldRenderer wr); }
public interface ILoadsPlayerPalettes { void LoadPlayerPalettes(WorldRenderer wr, string playerName, Color playerColor, bool replaceExisting); }
public interface IPaletteModifier { void AdjustPalette(IReadOnlyDictionary<string, MutablePalette> b); }
public interface IPips { IEnumerable<PipType> GetPips(Actor self); }
[RequireExplicitImplementation]
public interface ISelectionBar { float GetValue(); Color GetColor(); bool DisplayWhenEmpty { get; } }
public interface ISelectionDecorations { void DrawRollover(Actor self, WorldRenderer worldRenderer); }
public interface ISelectionDecorations
{
IEnumerable<IRenderable> RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color);
}
public interface IMapPreviewSignatureInfo : ITraitInfoInterface
{
@@ -440,6 +406,22 @@ namespace OpenRA.Traits
bool SpatiallyPartitionable { get; }
}
[Flags]
public enum SelectionPriorityModifiers
{
None = 0,
Ctrl = 1,
Alt = 2
}
[RequireExplicitImplementation]
public interface ISelectableInfo : ITraitInfoInterface
{
int Priority { get; }
SelectionPriorityModifiers PriorityModifiers { get; }
string Voice { get; }
}
public interface ISelection
{
int Hash { get; }
@@ -450,6 +432,8 @@ namespace OpenRA.Traits
bool Contains(Actor a);
void Combine(World world, IEnumerable<Actor> newSelection, bool isCombine, bool isClick);
void Clear();
bool RolloverContains(Actor a);
void SetRollover(IEnumerable<Actor> actors);
void DoControlGroup(World world, WorldRenderer worldRenderer, int group, Modifiers mods, int multiTapCount);
void AddToControlGroup(Actor a, int group);
void RemoveFromControlGroup(Actor a);
@@ -555,4 +539,24 @@ namespace OpenRA.Traits
[RequireExplicitImplementation]
public interface ICreationActivity { Activity GetCreationActivity(); }
[RequireExplicitImplementation]
public interface IObservesVariablesInfo : ITraitInfo { }
public delegate void VariableObserverNotifier(Actor self, IReadOnlyDictionary<string, int> variables);
public struct VariableObserver
{
public VariableObserverNotifier Notifier;
public IEnumerable<string> Variables;
public VariableObserver(VariableObserverNotifier notifier, IEnumerable<string> variables)
{
Notifier = notifier;
Variables = variables;
}
}
public interface IObservesVariables
{
IEnumerable<VariableObserver> GetVariableObservers();
}
}

View File

@@ -18,23 +18,15 @@ using OpenRA.Primitives;
namespace OpenRA.Traits
{
public struct ActorBoundsPair : IEquatable<ActorBoundsPair>
public struct ActorBoundsPair
{
public readonly Actor Actor;
public readonly Polygon Bounds;
// TODO: Replace this with an int2[] polygon
public readonly Rectangle Bounds;
public ActorBoundsPair(Actor actor, Rectangle bounds) { Actor = actor; Bounds = bounds; }
public static bool operator ==(ActorBoundsPair me, ActorBoundsPair other) { return me.Actor == other.Actor && Equals(me.Bounds, other.Bounds); }
public static bool operator !=(ActorBoundsPair me, ActorBoundsPair other) { return !(me == other); }
public ActorBoundsPair(Actor actor, Polygon bounds) { Actor = actor; Bounds = bounds; }
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
public bool Equals(ActorBoundsPair other) { return this == other; }
public override bool Equals(object obj) { return obj is ActorBoundsPair && Equals((ActorBoundsPair)obj); }
public override string ToString() { return "{0}->{1}".F(Actor.Info.Name, Bounds.GetType().Name); }
}
@@ -198,7 +190,7 @@ namespace OpenRA.Traits
return partitionedMouseActors.InBox(r)
.Where(actorIsInWorld)
.Select(selectActorAndBounds)
.Where(x => r.IntersectsWith(x.Bounds));
.Where(x => x.Bounds.IntersectsWith(r));
}
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
@@ -224,12 +216,12 @@ namespace OpenRA.Traits
foreach (var a in addOrUpdateActors)
{
var mouseBounds = a.MouseBounds(worldRenderer);
if (!mouseBounds.Size.IsEmpty)
if (!mouseBounds.IsEmpty)
{
if (partitionedMouseActors.Contains(a))
partitionedMouseActors.Update(a, mouseBounds);
partitionedMouseActors.Update(a, mouseBounds.BoundingRect);
else
partitionedMouseActors.Add(a, mouseBounds);
partitionedMouseActors.Add(a, mouseBounds.BoundingRect);
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
}
@@ -263,12 +255,12 @@ namespace OpenRA.Traits
foreach (var fa in kv.Value)
{
var mouseBounds = fa.MouseBounds;
if (!mouseBounds.Size.IsEmpty)
if (!mouseBounds.IsEmpty)
{
if (partitionedMouseFrozenActors[kv.Key].Contains(fa))
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds);
partitionedMouseFrozenActors[kv.Key].Update(fa, mouseBounds.BoundingRect);
else
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds);
partitionedMouseFrozenActors[kv.Key].Add(fa, mouseBounds.BoundingRect);
}
else
partitionedMouseFrozenActors[kv.Key].Remove(fa);
@@ -308,11 +300,10 @@ namespace OpenRA.Traits
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].ItemBounds) : bounds;
}
public IEnumerable<Rectangle> MouseBounds(Player viewer)
public IEnumerable<Polygon> MouseBounds(Player viewer)
{
var bounds = partitionedMouseActors.ItemBounds;
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].ItemBounds) : bounds;
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Items.Select(fa => fa.MouseBounds)) : bounds;
}
}
}

View File

@@ -57,7 +57,13 @@ namespace OpenRA
if (o == null)
continue;
if (PlayVoiceForOrder(o))
if (o.GroupedActors != null)
{
foreach (var subject in o.GroupedActors)
if (PlayVoiceForOrder(Order.FromGroupedOrder(o, subject)))
return;
}
else if (PlayVoiceForOrder(o))
return;
}
}

View File

@@ -107,9 +107,11 @@ namespace OpenRA.Mods.Cnc.Activities
if (minefield != null)
{
var positionable = (IPositionable)movement;
var mobile = positionable as Mobile;
minefield.RemoveAll(c => self.World.ActorMap.GetActorsAt(c)
.Any(a => a.Info.Name == minelayer.Info.Mine.ToLowerInvariant()) ||
(!positionable.CanEnterCell(c, null, BlockedByActor.Immovable) && !self.World.FogObscures(c)));
.Any(a => a.Info.Name == minelayer.Info.Mine.ToLowerInvariant() && a.CanBeViewedByPlayer(self.Owner)) ||
((!positionable.CanEnterCell(c, null, BlockedByActor.Immovable) || (mobile != null && !mobile.CanStayInCell(c)))
&& self.Owner.Shroud.IsVisible(c)));
}
}

View File

@@ -25,15 +25,11 @@ namespace OpenRA.Mods.Cnc
Sprite[] border;
float2 nodPos, gdiPos, evaPos;
Rectangle bounds;
SpriteFont loadingFont, versionFont;
string loadingText, versionText;
float2 loadingPos, versionPos;
string versionText;
Sheet lastSheet;
int lastDensity;
Size lastResolution;
IReadOnlyDictionary<string, SpriteFont> lastFonts;
public override void Init(ModData modData, Dictionary<string, string> info)
{
@@ -82,20 +78,6 @@ namespace OpenRA.Mods.Cnc
var barY = bounds.Height - 78;
// The fonts dictionary may change when switching between the mod and content installer
if (r.Fonts != lastFonts)
{
lastFonts = r.Fonts;
loadingFont = lastFonts["BigBold"];
loadingText = Info["Text"];
loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
versionFont = lastFonts["Regular"];
var versionSize = versionFont.Measure(versionText);
versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2);
}
loadTick = ++loadTick % 8;
r.RgbaSpriteRenderer.DrawSprite(gdiLogo, gdiPos);
@@ -104,11 +86,18 @@ namespace OpenRA.Mods.Cnc
WidgetUtils.DrawPanel(bounds, border);
if (loadingFont != null)
if (r.Fonts != null)
{
var loadingFont = r.Fonts["BigBold"];
var loadingText = Info["Text"];
var loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY);
loadingFont.DrawText(loadingText, loadingPos, Color.Gray);
if (versionFont != null)
var versionFont = r.Fonts["Regular"];
var versionSize = versionFont.Measure(versionText);
var versionPos = new float2(bounds.Width - 107 - versionSize.X / 2, 115 - versionSize.Y / 2);
versionFont.DrawTextWithContrast(versionText, versionPos, Color.White, Color.Black, 2);
}
for (var i = 0; i <= 8; i++)
{

View File

@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Cnc.FileFormats
Transforms[c + ids[k]] = s.ReadFloat();
Array.Copy(Transforms, 16 * (LimbCount * j + i), testMatrix, 0, 16);
if (Util.MatrixInverse(testMatrix) == null)
if (OpenRA.Graphics.Util.MatrixInverse(testMatrix) == null)
throw new InvalidDataException(
"The transformation matrix for HVA file `{0}` section {1} frame {2} is invalid because it is not invertible!"
.F(fileName, i, j));

View File

@@ -0,0 +1,50 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
namespace OpenRA.Mods.Cnc.Graphics
{
public class ClassicSpriteSequenceLoader : DefaultSpriteSequenceLoader
{
public ClassicSpriteSequenceLoader(ModData modData)
: base(modData) { }
public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
{
return new ClassicSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
}
}
public class ClassicSpriteSequence : DefaultSpriteSequence
{
readonly bool useClassicFacings;
public ClassicSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info)
{
var d = info.ToDictionary();
useClassicFacings = LoadField(d, "UseClassicFacings", false);
if (useClassicFacings && Facings != 32)
throw new InvalidOperationException(
"{0}: Sequence {1}.{2}: UseClassicFacings is only valid for 32 facings"
.F(info.Nodes[0].Location, sequence, animation));
}
protected override int GetFacingFrameOffset(WAngle facing)
{
return Util.ClassicQuantizeFacing(facing.Facing, Facings, useClassicFacings);
}
}
}

View File

@@ -0,0 +1,92 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
namespace OpenRA.Mods.Cnc.Graphics
{
public class ClassicTilesetSpecificSpriteSequenceLoader : ClassicSpriteSequenceLoader
{
public readonly string DefaultSpriteExtension = ".shp";
public readonly Dictionary<string, string> TilesetExtensions = new Dictionary<string, string>();
public readonly Dictionary<string, string> TilesetCodes = new Dictionary<string, string>();
public ClassicTilesetSpecificSpriteSequenceLoader(ModData modData)
: base(modData)
{
var metadata = modData.Manifest.Get<SpriteSequenceFormat>().Metadata;
MiniYaml yaml;
if (metadata.TryGetValue("DefaultSpriteExtension", out yaml))
DefaultSpriteExtension = yaml.Value;
if (metadata.TryGetValue("TilesetExtensions", out yaml))
TilesetExtensions = yaml.ToDictionary(kv => kv.Value);
if (metadata.TryGetValue("TilesetCodes", out yaml))
TilesetCodes = yaml.ToDictionary(kv => kv.Value);
}
public override ISpriteSequence CreateSequence(ModData modData, TileSet tileSet, SpriteCache cache, string sequence, string animation, MiniYaml info)
{
return new ClassicTilesetSpecificSpriteSequence(modData, tileSet, cache, this, sequence, animation, info);
}
}
public class ClassicTilesetSpecificSpriteSequence : ClassicSpriteSequence
{
public ClassicTilesetSpecificSpriteSequence(ModData modData, TileSet tileSet, SpriteCache cache, ISpriteSequenceLoader loader, string sequence, string animation, MiniYaml info)
: base(modData, tileSet, cache, loader, sequence, animation, info) { }
string ResolveTilesetId(TileSet tileSet, Dictionary<string, MiniYaml> d)
{
var tsId = tileSet.Id;
MiniYaml yaml;
if (d.TryGetValue("TilesetOverrides", out yaml))
{
var tsNode = yaml.Nodes.FirstOrDefault(n => n.Key == tsId);
if (tsNode != null)
tsId = tsNode.Value.Value;
}
return tsId;
}
protected override string GetSpriteSrc(ModData modData, TileSet tileSet, string sequence, string animation, string sprite, Dictionary<string, MiniYaml> d)
{
var loader = (ClassicTilesetSpecificSpriteSequenceLoader)Loader;
var spriteName = sprite ?? sequence;
if (LoadField(d, "UseTilesetCode", false))
{
string code;
if (loader.TilesetCodes.TryGetValue(ResolveTilesetId(tileSet, d), out code))
spriteName = spriteName.Substring(0, 1) + code + spriteName.Substring(2, spriteName.Length - 2);
}
if (LoadField(d, "AddExtension", true))
{
var useTilesetExtension = LoadField(d, "UseTilesetExtension", false);
string tilesetExtension;
if (useTilesetExtension && loader.TilesetExtensions.TryGetValue(ResolveTilesetId(tileSet, d), out tilesetExtension))
return spriteName + tilesetExtension;
return spriteName + loader.DefaultSpriteExtension;
}
return spriteName;
}
}
}

View File

@@ -74,8 +74,8 @@ namespace OpenRA.Mods.Cnc.Graphics
t[14] *= l.Scale * (l.Bounds[5] - l.Bounds[2]) / l.Size[2];
// Center, flip and scale
t = Util.MatrixMultiply(t, Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2]));
t = Util.MatrixMultiply(Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t);
t = OpenRA.Graphics.Util.MatrixMultiply(t, OpenRA.Graphics.Util.TranslationMatrix(l.Bounds[0], l.Bounds[1], l.Bounds[2]));
t = OpenRA.Graphics.Util.MatrixMultiply(OpenRA.Graphics.Util.ScaleMatrix(l.Scale, -l.Scale, l.Scale), t);
return t;
}
@@ -119,7 +119,7 @@ namespace OpenRA.Mods.Cnc.Graphics
};
// Calculate limb bounding box
var bb = Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b);
var bb = OpenRA.Graphics.Util.MatrixAABBMultiply(TransformationMatrix(j, frame), b);
for (var i = 0; i < 3; i++)
{
ret[i] = Math.Min(ret[i], bb[i]);

View File

@@ -77,8 +77,8 @@ namespace OpenRA.Mods.Cnc.Graphics
var size = new Size(su, sv);
var s = sheetBuilder.Allocate(size);
var t = sheetBuilder.Allocate(size);
Util.FastCopyIntoChannel(s, colors);
Util.FastCopyIntoChannel(t, normals);
OpenRA.Graphics.Util.FastCopyIntoChannel(s, colors);
OpenRA.Graphics.Util.FastCopyIntoChannel(t, normals);
// s and t are guaranteed to use the same sheet because
// of the custom voxel sheet allocation implementation

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<TargetFramework>net472</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<LangVersion>5</LangVersion>
@@ -21,10 +21,6 @@
<Optimize>false</Optimize>
</PropertyGroup>
<ItemGroup>
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
<Private>False</Private>
</Reference>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
<Private>False</Private>
</ProjectReference>
@@ -32,6 +28,7 @@
<Private>False</Private>
</ProjectReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<AdditionalFiles Include="../stylecop.json" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">

View File

@@ -34,8 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits
{
readonly AttackLeapInfo info;
ConditionManager conditionManager;
int leapToken = ConditionManager.InvalidConditionToken;
int leapToken = Actor.InvalidConditionToken;
public AttackLeap(Actor self, AttackLeapInfo info)
: base(self, info)
@@ -43,12 +42,6 @@ namespace OpenRA.Mods.Cnc.Traits
this.info = info;
}
protected override void Created(Actor self)
{
conditionManager = self.TraitOrDefault<ConditionManager>();
base.Created(self);
}
protected override bool CanAttack(Actor self, Target target)
{
if (target.Type != TargetType.Actor)
@@ -62,14 +55,14 @@ namespace OpenRA.Mods.Cnc.Traits
public void GrantLeapCondition(Actor self)
{
if (conditionManager != null && !string.IsNullOrEmpty(info.LeapCondition))
leapToken = conditionManager.GrantCondition(self, info.LeapCondition);
if (!string.IsNullOrEmpty(info.LeapCondition))
leapToken = self.GrantCondition(info.LeapCondition);
}
public void RevokeLeapCondition(Actor self)
{
if (leapToken != ConditionManager.InvalidConditionToken)
leapToken = conditionManager.RevokeCondition(self, leapToken);
if (leapToken != Actor.InvalidConditionToken)
leapToken = self.RevokeCondition(leapToken);
}
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)

View File

@@ -0,0 +1,32 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Mods.Common.Traits;
namespace OpenRA.Mods.Cnc.Traits
{
[Desc("Fudge the coordinate system angles like the early games (for sprite sequences that use classic facing fudge).")]
public class ClassicFacingBodyOrientationInfo : BodyOrientationInfo
{
public override int QuantizeFacing(int facing, int facings)
{
return OpenRA.Mods.Cnc.Util.ClassicQuantizeFacing(facing, facings, true) * (256 / facings);
}
public override object Create(ActorInitializer init) { return new ClassicFacingBodyOrientation(init, this); }
}
public class ClassicFacingBodyOrientation : BodyOrientation
{
public ClassicFacingBodyOrientation(ActorInitializer init, ClassicFacingBodyOrientationInfo info)
: base(init, info) { }
}
}

View File

@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Cnc.Traits
public object Create(ActorInitializer init) { return new ConyardChronoReturn(init, this); }
}
public class ConyardChronoReturn : INotifyCreated, ITick, ISync, IObservesVariables, ISelectionBar, INotifySold,
public class ConyardChronoReturn : ITick, ISync, IObservesVariables, ISelectionBar, INotifySold,
IDeathActorInitModifier, ITransformActorInitModifier
{
readonly ConyardChronoReturnInfo info;
@@ -70,8 +70,7 @@ namespace OpenRA.Mods.Cnc.Traits
readonly Actor self;
readonly string faction;
ConditionManager conditionManager;
int conditionToken = ConditionManager.InvalidConditionToken;
int conditionToken = Actor.InvalidConditionToken;
Actor chronosphere;
int duration;
@@ -110,11 +109,6 @@ namespace OpenRA.Mods.Cnc.Traits
chronosphere = init.Get<ChronoshiftChronosphereInit, Actor>();
}
void INotifyCreated.Created(Actor self)
{
conditionManager = self.TraitOrDefault<ConditionManager>();
}
IEnumerable<VariableObserver> IObservesVariables.GetVariableObservers()
{
if (info.ReturnOriginalActorOnCondition != null)
@@ -128,8 +122,8 @@ namespace OpenRA.Mods.Cnc.Traits
void TriggerVortex()
{
if (conditionManager != null && !string.IsNullOrEmpty(info.Condition) && conditionToken == ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.GrantCondition(self, info.Condition);
if (!string.IsNullOrEmpty(info.Condition) && conditionToken == Actor.InvalidConditionToken)
conditionToken = self.GrantCondition(info.Condition);
triggered = true;
@@ -140,8 +134,8 @@ namespace OpenRA.Mods.Cnc.Traits
wsb.PlayCustomAnimation(self, info.Sequence, () =>
{
triggered = false;
if (conditionToken != ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
if (conditionToken != Actor.InvalidConditionToken)
conditionToken = self.RevokeCondition(conditionToken);
});
}

View File

@@ -99,7 +99,7 @@ namespace OpenRA.Mods.Cnc.Traits
public object Create(ActorInitializer init) { return new Disguise(init.Self, this); }
}
class Disguise : INotifyCreated, IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
class Disguise : IEffectiveOwner, IIssueOrder, IResolveOrder, IOrderVoice, IRadarColorModifier, INotifyAttack,
INotifyDamage, INotifyUnload, INotifyDemolition, INotifyInfiltration, ITick
{
public ActorInfo AsActor { get; private set; }
@@ -112,9 +112,8 @@ namespace OpenRA.Mods.Cnc.Traits
readonly Actor self;
readonly DisguiseInfo info;
ConditionManager conditionManager;
int disguisedToken = ConditionManager.InvalidConditionToken;
int disguisedAsToken = ConditionManager.InvalidConditionToken;
int disguisedToken = Actor.InvalidConditionToken;
int disguisedAsToken = Actor.InvalidConditionToken;
CPos? lastPos;
public Disguise(Actor self, DisguiseInfo info)
@@ -125,11 +124,6 @@ namespace OpenRA.Mods.Cnc.Traits
AsActor = self.Info;
}
void INotifyCreated.Created(Actor self)
{
conditionManager = self.TraitOrDefault<ConditionManager>();
}
IEnumerable<IOrderTargeter> IIssueOrder.Orders
{
get
@@ -225,25 +219,22 @@ namespace OpenRA.Mods.Cnc.Traits
foreach (var t in self.TraitsImplementing<INotifyEffectiveOwnerChanged>())
t.OnEffectiveOwnerChanged(self, oldEffectiveOwner, AsPlayer);
if (conditionManager != null)
if (Disguised != oldDisguiseSetting)
{
if (Disguised != oldDisguiseSetting)
{
if (Disguised && disguisedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(info.DisguisedCondition))
disguisedToken = conditionManager.GrantCondition(self, info.DisguisedCondition);
else if (!Disguised && disguisedToken != ConditionManager.InvalidConditionToken)
disguisedToken = conditionManager.RevokeCondition(self, disguisedToken);
}
if (Disguised && disguisedToken == Actor.InvalidConditionToken && !string.IsNullOrEmpty(info.DisguisedCondition))
disguisedToken = self.GrantCondition(info.DisguisedCondition);
else if (!Disguised && disguisedToken != Actor.InvalidConditionToken)
disguisedToken = self.RevokeCondition(disguisedToken);
}
if (AsActor != oldEffectiveActor)
{
if (disguisedAsToken != ConditionManager.InvalidConditionToken)
disguisedAsToken = conditionManager.RevokeCondition(self, disguisedAsToken);
if (AsActor != oldEffectiveActor)
{
if (disguisedAsToken != Actor.InvalidConditionToken)
disguisedAsToken = self.RevokeCondition(disguisedAsToken);
string disguisedAsCondition;
if (info.DisguisedAsConditions.TryGetValue(AsActor.Name, out disguisedAsCondition))
disguisedAsToken = conditionManager.GrantCondition(self, disguisedAsCondition);
}
string disguisedAsCondition;
if (info.DisguisedAsConditions.TryGetValue(AsActor.Name, out disguisedAsCondition))
disguisedAsToken = self.GrantCondition(disguisedAsCondition);
}
}

View File

@@ -20,6 +20,7 @@ namespace OpenRA.Mods.Cnc.Traits
[Desc("Funds are transferred from the owner to the infiltrator.")]
class InfiltrateForCashInfo : ITraitInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[Desc("Percentage of the victim's resources that will be stolen.")]
@@ -34,7 +35,11 @@ namespace OpenRA.Mods.Cnc.Traits
[NotificationReference("Speech")]
[Desc("Sound the victim will hear when they get robbed.")]
public readonly string Notification = null;
public readonly string InfiltratedNotification = null;
[NotificationReference("Speech")]
[Desc("Sound the perpetrator will hear after successful infiltration.")]
public readonly string InfiltrationNotification = null;
[Desc("Whether to show the cash tick indicators rising from the actor.")]
public readonly bool ShowTicks = true;
@@ -63,8 +68,11 @@ namespace OpenRA.Mods.Cnc.Traits
targetResources.TakeCash(toTake);
spyResources.GiveCash(toGive);
if (info.Notification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.Notification, self.Owner.Faction.InternalName);
if (info.InfiltratedNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
if (info.InfiltrationNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
if (info.ShowTicks)
self.World.AddFrameEndTask(w => w.Add(new FloatingText(self.CenterPosition, infiltrator.Owner.Color, FloatingText.FormatCashTick(toGive), 30)));

View File

@@ -21,6 +21,7 @@ namespace OpenRA.Mods.Cnc.Traits
[Desc("Reveals a decoration sprite to the indicated players when infiltrated.")]
class InfiltrateForDecorationInfo : WithDecorationInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
public override object Create(ActorInitializer init) { return new InfiltrateForDecoration(init.Self, this); }

View File

@@ -19,8 +19,17 @@ namespace OpenRA.Mods.Cnc.Traits
[Desc("Steal and reset the owner's exploration.")]
class InfiltrateForExplorationInfo : ITraitInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[NotificationReference("Speech")]
[Desc("Sound the victim will hear when they get sabotaged.")]
public readonly string InfiltratedNotification = null;
[NotificationReference("Speech")]
[Desc("Sound the perpetrator will hear after successful infiltration.")]
public readonly string InfiltrationNotification = null;
public object Create(ActorInitializer init) { return new InfiltrateForExploration(init.Self, this); }
}
@@ -38,6 +47,12 @@ namespace OpenRA.Mods.Cnc.Traits
if (!info.Types.Overlaps(types))
return;
if (info.InfiltratedNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
if (info.InfiltrationNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
infiltrator.Owner.Shroud.Explore(self.Owner.Shroud);
var preventReset = self.Owner.PlayerActor.TraitsImplementing<IPreventsShroudReset>()
.Any(p => p.PreventShroudReset(self));

View File

@@ -17,10 +17,20 @@ namespace OpenRA.Mods.Cnc.Traits
{
class InfiltrateForPowerOutageInfo : ITraitInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[Desc("Measured in ticks.")]
public readonly int Duration = 25 * 20;
[NotificationReference("Speech")]
[Desc("Sound the victim will hear when they get sabotaged.")]
public readonly string InfiltratedNotification = null;
[NotificationReference("Speech")]
[Desc("Sound the perpetrator will hear after successful infiltration.")]
public readonly string InfiltrationNotification = null;
public object Create(ActorInitializer init) { return new InfiltrateForPowerOutage(init.Self, this); }
}
@@ -40,6 +50,12 @@ namespace OpenRA.Mods.Cnc.Traits
if (!info.Types.Overlaps(types))
return;
if (info.InfiltratedNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
if (info.InfiltrationNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
playerPower.TriggerPowerOutage(info.Duration);
}

View File

@@ -21,8 +21,17 @@ namespace OpenRA.Mods.Cnc.Traits
[FieldLoader.Require]
public readonly string Proxy = null;
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[NotificationReference("Speech")]
[Desc("Sound the victim will hear when technology gets stolen.")]
public readonly string InfiltratedNotification = null;
[NotificationReference("Speech")]
[Desc("Sound the perpetrator will hear after successful infiltration.")]
public readonly string InfiltrationNotification = null;
public object Create(ActorInitializer init) { return new InfiltrateForSupportPower(this); }
}
@@ -40,6 +49,12 @@ namespace OpenRA.Mods.Cnc.Traits
if (!info.Types.Overlaps(types))
return;
if (info.InfiltratedNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
if (info.InfiltrationNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
infiltrator.World.AddFrameEndTask(w => w.CreateActor(info.Proxy, new TypeDictionary
{
new OwnerInit(infiltrator.Owner)

View File

@@ -0,0 +1,61 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Cnc.Traits
{
class InfiltrateForSupportPowerResetInfo : ITraitInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[NotificationReference("Speech")]
[Desc("Sound the victim will hear when technology gets stolen.")]
public readonly string InfiltratedNotification = null;
[NotificationReference("Speech")]
[Desc("Sound the perpetrator will hear after successful infiltration.")]
public readonly string InfiltrationNotification = null;
public object Create(ActorInitializer init) { return new InfiltrateForSupportPowerReset(this); }
}
class InfiltrateForSupportPowerReset : INotifyInfiltrated
{
readonly InfiltrateForSupportPowerResetInfo info;
public InfiltrateForSupportPowerReset(InfiltrateForSupportPowerResetInfo info)
{
this.info = info;
}
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet<TargetableType> types)
{
if (!info.Types.Overlaps(types))
return;
if (info.InfiltratedNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.InfiltratedNotification, self.Owner.Faction.InternalName);
if (info.InfiltrationNotification != null)
Game.Sound.PlayNotification(self.World.Map.Rules, infiltrator.Owner, "Speech", info.InfiltrationNotification, infiltrator.Owner.Faction.InternalName);
var manager = self.Owner.PlayerActor.Trait<SupportPowerManager>();
var powers = manager.GetPowersForActor(self).Where(sp => !sp.Disabled);
foreach (var power in powers)
power.ResetTimer();
}
}
}

View File

@@ -29,6 +29,7 @@ namespace OpenRA.Mods.Cnc.Traits
public readonly bool SkipMakeAnims = true;
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
public object Create(ActorInitializer init) { return new InfiltrateForTransform(init, this); }

View File

@@ -22,6 +22,7 @@ namespace OpenRA.Mods.Cnc.Traits
{
public class InfiltratesInfo : ConditionalTraitInfo
{
[Desc("The `TargetTypes` from `Targetable` that are allowed to enter.")]
public readonly BitSet<TargetableType> Types = default(BitSet<TargetableType>);
[VoiceReference]

View File

@@ -83,22 +83,15 @@ namespace OpenRA.Mods.Cnc.Traits
}
}
class MadTank : INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IIssueDeployOrder
class MadTank : IIssueOrder, IResolveOrder, IOrderVoice, IIssueDeployOrder
{
readonly MadTankInfo info;
ConditionManager conditionManager;
public MadTank(Actor self, MadTankInfo info)
{
this.info = info;
}
void INotifyCreated.Created(Actor self)
{
conditionManager = self.TraitOrDefault<ConditionManager>();
}
public IEnumerable<IOrderTargeter> Orders
{
get
@@ -195,8 +188,8 @@ namespace OpenRA.Mods.Cnc.Traits
if (target.Type == TargetType.Invalid)
return true;
if (mad.conditionManager != null && !string.IsNullOrEmpty(mad.info.DeployedCondition))
mad.conditionManager.GrantCondition(self, mad.info.DeployedCondition);
if (!string.IsNullOrEmpty(mad.info.DeployedCondition))
self.GrantCondition(mad.info.DeployedCondition);
self.World.AddFrameEndTask(w => EjectDriver());
if (mad.info.ThumpSequence != null)

View File

@@ -115,7 +115,7 @@ namespace OpenRA.Mods.Cnc.Traits
var movement = self.Trait<IPositionable>();
var minefield = GetMinefieldCells(minefieldStart, cell, Info.MinefieldDepth)
.Where(c => movement.CanEnterCell(c, null, BlockedByActor.Immovable) || self.World.FogObscures(c))
.Where(c => movement.CanEnterCell(c, null, BlockedByActor.Immovable) || !self.Owner.Shroud.IsVisible(c))
.OrderBy(c => (c - minefieldStart).LengthSquared).ToList();
self.QueueActivity(order.Queued, new LayMines(self, minefield));
@@ -231,13 +231,14 @@ namespace OpenRA.Mods.Cnc.Traits
minelayers.Max(m => m.Info.TraitInfo<MinelayerInfo>().MinefieldDepth));
var movement = minelayer.Trait<IPositionable>();
var mobile = movement as Mobile;
var pal = wr.Palette(TileSet.TerrainPaletteInternalName);
foreach (var c in minefield)
{
var tile = tileOk;
if (world.FogObscures(c))
tile = tileUnknown;
else if (!movement.CanEnterCell(c, null, BlockedByActor.Immovable))
else if (!movement.CanEnterCell(c, null, BlockedByActor.Immovable) || (mobile != null && !mobile.CanStayInCell(c)))
tile = tileBlocked;
yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c),

View File

@@ -60,7 +60,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
.FirstOrDefault(t => t.EnabledByDefault);
if (renderSprites != null && infantryBody != null)
{
disguiseImage = renderSprites.GetImage(disguiseActor, self.World.Map.Rules.Sequences, disguisePlayer.InternalName);
disguiseImage = renderSprites.GetImage(disguiseActor, self.World.Map.Rules.Sequences, disguisePlayer.Faction.InternalName);
disguiseInfantryBody = infantryBody;
}
}

View File

@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
var wsb = init.Actor.TraitInfos<WithSpriteBodyInfo>().FirstOrDefault();
// Show the correct turret facing
var facing = init.Contains<TurretFacingInit>() ? init.Get<TurretFacingInit>().Value(init.World) : t.InitialFacing;
var facing = WAngle.FromFacing(init.Contains<TurretFacingInit>() ? init.Get<TurretFacingInit>().Value(init.World) : t.InitialFacing);
var anim = new Animation(init.World, image, () => facing);
anim.PlayRepeating(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), wsb.Sequence));
@@ -47,11 +47,11 @@ namespace OpenRA.Mods.Cnc.Traits.Render
{
readonly Turreted turreted;
static Func<int> MakeTurretFacingFunc(Actor self)
static Func<WAngle> MakeTurretFacingFunc(Actor self)
{
// Turret artwork is baked into the sprite, so only the first turret makes sense.
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
return () => turreted.TurretFacing;
return () => WAngle.FromFacing(turreted.TurretFacing);
}
public WithEmbeddedTurretSpriteBody(ActorInitializer init, WithSpriteBodyInfo info)

View File

@@ -46,11 +46,11 @@ namespace OpenRA.Mods.Cnc.Traits.Render
readonly IFacing facing;
readonly Turreted turret;
static Func<int> MakeTurretFacingFunc(Actor self)
static Func<WAngle> MakeTurretFacingFunc(Actor self)
{
// Turret artwork is baked into the sprite, so only the first turret makes sense.
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
return () => turreted.TurretFacing;
return () => WAngle.FromFacing(turreted.TurretFacing);
}
public WithGunboatBody(ActorInitializer init, WithGunboatBodyInfo info)

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.Cnc.Traits.Render
public WithRoof(Actor self, WithRoofInfo info)
{
var rs = self.Trait<RenderSprites>();
var roof = new Animation(self.World, rs.GetImage(self), () => self.Trait<IFacing>().Facing);
var roof = new Animation(self.World, rs.GetImage(self), RenderSprites.MakeFacingFunc(self));
roof.Play(info.Sequence);
rs.Add(new AnimationWithOffset(roof, null, null, 1024));
}

View File

@@ -67,7 +67,7 @@ namespace OpenRA.Mods.Cnc.Traits
if (IsTraitDisabled)
return;
var cash = Util.ApplyPercentageModifiers(amount, modifier);
var cash = OpenRA.Mods.Common.Util.ApplyPercentageModifiers(amount, modifier);
playerResources.GiveCash(cash);
if (Info.ShowTicks && self.Info.HasTraitInfo<IOccupySpaceInfo>())

View File

@@ -179,8 +179,9 @@ namespace OpenRA.Mods.Cnc.Traits
{
if (unit.CanBeViewedByPlayer(manager.Self.Owner))
{
var bounds = unit.TraitsImplementing<IDecorationBounds>().FirstNonEmptyBounds(unit, wr);
yield return new SelectionBoxAnnotationRenderable(unit, bounds, Color.Red);
var decorations = unit.TraitsImplementing<ISelectionDecorations>().FirstEnabledTraitOrDefault();
foreach (var d in decorations.RenderSelectionAnnotations(unit, wr, Color.Red))
yield return d;
}
}
}
@@ -302,8 +303,9 @@ namespace OpenRA.Mods.Cnc.Traits
{
if (unit.CanBeViewedByPlayer(manager.Self.Owner))
{
var bounds = unit.TraitsImplementing<IDecorationBounds>().FirstNonEmptyBounds(unit, wr);
yield return new SelectionBoxAnnotationRenderable(unit, bounds, Color.Red);
var decorations = unit.TraitsImplementing<ISelectionDecorations>().FirstEnabledTraitOrDefault();
foreach (var d in decorations.RenderSelectionAnnotations(unit, wr, Color.Red))
yield return d;
}
}
}

View File

@@ -134,7 +134,7 @@ namespace OpenRA.Mods.Cnc.Traits
int MovementSpeed
{
get { return Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); }
get { return OpenRA.Mods.Common.Util.ApplyPercentageModifiers(Info.Speed, speedModifiers); }
}
public Pair<CPos, SubCell>[] OccupiedCells() { return new[] { Pair.New(TopLeft, SubCell.FullCell) }; }

42
OpenRA.Mods.Cnc/Util.cs Normal file
View File

@@ -0,0 +1,42 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
namespace OpenRA.Mods.Cnc
{
public static class Util
{
// TD and RA used a nonlinear mapping between artwork frames and unit facings for units with 32 facings.
// This table defines the exclusive maximum facing for the i'th sprite frame.
// i.e. sprite frame 1 is used for facings 5-13, sprite frame 2 for 14-21, and so on.
// Sprite frame 0 is used for facings smaller than 5 or larger than 249.
static readonly int[] SpriteFacings =
{
5, 14, 22, 33, 39, 46, 53, 60,
67, 74, 81, 88, 96, 104, 113, 122,
133, 142, 151, 161, 167, 174, 181, 188,
195, 202, 209, 216, 224, 232, 241, 250
};
public static int ClassicQuantizeFacing(int facing, int numFrames, bool useClassicFacingFudge)
{
if (useClassicFacingFudge && numFrames == 32)
{
for (var i = 0; i < SpriteFacings.Length; i++)
if (facing < SpriteFacings[i])
return i;
return 0;
}
return Common.Util.QuantizeFacing(facing, numFrames);
}
}
}

View File

@@ -85,10 +85,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
static string[] overlayActors = new string[]
{
// Fences
"sbag", "cycl", "brik", "fenc", "wood", "wood",
"sbag", "cycl", "brik", "fenc", "wood",
// Fields
"v12", "v13", "v14", "v15", "v16", "v17", "v18"
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
// Crates
"wcrate", "scrate"
};
void UnpackOverlayData(MemoryStream ms)

View File

@@ -73,10 +73,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
static string[] overlayActors = new string[]
{
// Fences
"sbag", "cycl", "brik", "fenc", "wood", "wood",
"sbag", "cycl", "brik", "fenc", "wood",
// Fields
"v12", "v13", "v14", "v15", "v16", "v17", "v18"
"v12", "v13", "v14", "v15", "v16", "v17", "v18",
// Crates
"wcrate", "scrate"
};
void ReadOverlay(IniFile file)
@@ -137,7 +140,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
faction = "gdi";
break;
case "BadGuy":
color = "red"; // TODO: use the grey unit color theme for missions
color = "red";
faction = "nod";
break;
case "Special":

View File

@@ -65,8 +65,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
var name = args.Length > 3 ? args[3] : Path.GetFileNameWithoutExtension(args[2]);
metadata.AppendLine("\tName: {0}".F(name));
metadata.AppendLine("\tId: {0}".F(name.ToUpperInvariant()));
metadata.AppendLine("\tPalette: iso{0}.pal".F(extension));
metadata.AppendLine("\tHeightDebugColors: 00000080, 00004480, 00008880, 0000CC80, 0000FF80, 4400CC80," +
metadata.AppendLine("\tHeightDebugColors: 00000080, 00004480, 00008880, 0000CC80, 0000FF80, 4400CC80," +
" 88008880, CC004480, FF110080, FF550080, FF990080, FFDD0080, DDFF0080, 99FF0080, 55FF0080, 11FF0080");
// Loop over template sets
@@ -96,7 +95,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
using (var s = modData.DefaultFileSystem.Open(templateFilename))
{
data.AppendLine("\tTemplate@{0}:".F(templateIndex));
data.AppendLine("\t\tCategory: {0}".F(sectionCategory));
data.AppendLine("\t\tCategories: {0}".F(sectionCategory));
usedCategories.Add(sectionCategory);
data.AppendLine("\t\tId: {0}".F(templateIndex));

View File

@@ -22,15 +22,13 @@ namespace OpenRA.Mods.Common.Activities
readonly Func<Activity> getInner;
readonly bool isAssaultMove;
AutoTarget autoTarget;
ConditionManager conditionManager;
AttackMove attackMove;
int token = ConditionManager.InvalidConditionToken;
int token = Actor.InvalidConditionToken;
public AttackMoveActivity(Actor self, Func<Activity> getInner, bool assaultMoving = false)
{
this.getInner = getInner;
autoTarget = self.TraitOrDefault<AutoTarget>();
conditionManager = self.TraitOrDefault<ConditionManager>();
attackMove = self.TraitOrDefault<AttackMove>();
isAssaultMove = assaultMoving;
ChildHasPriority = false;
@@ -41,13 +39,13 @@ namespace OpenRA.Mods.Common.Activities
// Start moving.
QueueChild(getInner());
if (conditionManager == null || attackMove == null)
if (attackMove == null)
return;
if (!isAssaultMove && !string.IsNullOrEmpty(attackMove.Info.AttackMoveCondition))
token = conditionManager.GrantCondition(self, attackMove.Info.AttackMoveCondition);
token = self.GrantCondition(attackMove.Info.AttackMoveCondition);
else if (isAssaultMove && !string.IsNullOrEmpty(attackMove.Info.AssaultMoveCondition))
token = conditionManager.GrantCondition(self, attackMove.Info.AssaultMoveCondition);
token = self.GrantCondition(attackMove.Info.AssaultMoveCondition);
}
public override bool Tick(Actor self)
@@ -77,8 +75,8 @@ namespace OpenRA.Mods.Common.Activities
protected override void OnLastRun(Actor self)
{
if (conditionManager != null && token != ConditionManager.InvalidConditionToken)
token = conditionManager.RevokeCondition(self, token);
if (token != Actor.InvalidConditionToken)
token = self.RevokeCondition(token);
}
public override IEnumerable<Target> GetTargets(Actor self)

View File

@@ -305,7 +305,7 @@ namespace OpenRA.Mods.Common.Activities
var newCell = path[path.Count - 1];
path.RemoveAt(path.Count - 1);
return Pair.New(newCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor));
return Pair.New(newCell, mobile.GetAvailableSubCell(nextCell, mobile.FromSubCell, ignoreActor));
}
else if (mobile.IsBlocking)
{
@@ -316,7 +316,7 @@ namespace OpenRA.Mods.Common.Activities
if ((nextCell - newCell).Value.LengthSquared > 2)
path.Add(mobile.ToCell);
return Pair.New(newCell.Value, mobile.GetAvailableSubCell(newCell.Value, SubCell.Any, ignoreActor));
return Pair.New(newCell.Value, mobile.GetAvailableSubCell(newCell.Value, mobile.FromSubCell, ignoreActor));
}
}
@@ -326,7 +326,7 @@ namespace OpenRA.Mods.Common.Activities
hasWaited = false;
path.RemoveAt(path.Count - 1);
return Pair.New(nextCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor));
return Pair.New(nextCell, mobile.GetAvailableSubCell(nextCell, mobile.FromSubCell, ignoreActor));
}
protected override void OnLastRun(Actor self)
@@ -347,10 +347,15 @@ namespace OpenRA.Mods.Common.Activities
}
public override void Cancel(Actor self, bool keepQueue = false)
{
Cancel(self, keepQueue, false);
}
public void Cancel(Actor self, bool keepQueue, bool forceClearPath)
{
// We need to clear the path here in order to prevent MovePart queueing new instances of itself
// when the unit is making a turn.
if (path != null && mobile.CanStayInCell(mobile.ToCell))
if (path != null && (forceClearPath || mobile.CanStayInCell(mobile.ToCell)))
path.Clear();
base.Cancel(self, keepQueue);

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Activities;
@@ -77,7 +78,8 @@ namespace OpenRA.Mods.Common.Activities
protected virtual IEnumerable<CPos> CandidateMovementCells(Actor self)
{
return Util.AdjacentCells(self.World, Target);
return Util.AdjacentCells(self.World, Target)
.Where(c => Mobile.CanStayInCell(c));
}
protected override void OnFirstRun(Actor self)
@@ -119,15 +121,23 @@ namespace OpenRA.Mods.Common.Activities
return TickChild(self);
}
List<CPos> searchCells = new List<CPos>();
int searchCellsTick = -1;
List<CPos> CalculatePathToTarget(Actor self, BlockedByActor check)
{
var targetCells = CandidateMovementCells(self);
var searchCells = new List<CPos>();
var loc = self.Location;
foreach (var cell in targetCells)
if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell))
searchCells.Add(cell);
// PERF: Assume that CandidateMovementCells doesn't change within a tick to avoid repeated queries
// when Move enumerates different BlockedByActor values
if (searchCellsTick != self.World.WorldTick)
{
searchCells.Clear();
searchCellsTick = self.World.WorldTick;
foreach (var cell in CandidateMovementCells(self))
if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell))
searchCells.Add(cell);
}
if (!searchCells.Any())
return NoPath;

View File

@@ -33,13 +33,13 @@ namespace OpenRA.Mods.Common.Activities
{
// We are now in range. Don't move any further!
// HACK: This works around the pathfinder not returning the shortest path
return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self);
return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self) && Mobile.CanStayInCell(self.Location);
}
protected override bool ShouldRepath(Actor self, CPos targetLocation)
{
return lastVisibleTargetLocation != targetLocation && (!AtCorrectRange(self.CenterPosition)
|| !Mobile.CanInteractWithGroundLayer(self));
|| !Mobile.CanInteractWithGroundLayer(self) || !Mobile.CanStayInCell(self.Location));
}
protected override IEnumerable<CPos> CandidateMovementCells(Actor self)
@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Activities
var minCells = minRange.Length / 1024;
return map.FindTilesInAnnulus(lastVisibleTargetLocation, minCells, maxCells)
.Where(c => AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell)));
.Where(c => Mobile.CanStayInCell(c) && AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell)));
}
bool AtCorrectRange(WPos origin)

View File

@@ -21,6 +21,7 @@ namespace OpenRA.Mods.Common.Activities
Actor enterActor;
IHealth enterHealth;
EngineerRepairable enterEngineerRepariable;
public RepairBuilding(Actor self, Target target, EngineerRepairInfo info)
: base(self, target, Color.Yellow)
@@ -32,11 +33,12 @@ namespace OpenRA.Mods.Common.Activities
{
enterActor = targetActor;
enterHealth = targetActor.TraitOrDefault<IHealth>();
enterEngineerRepariable = targetActor.TraitOrDefault<EngineerRepairable>();
// Make sure we can still repair the target before entering
// (but not before, because this may stop the actor in the middle of nowhere)
var stance = self.Owner.Stances[enterActor.Owner];
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || !info.ValidStances.HasStance(stance))
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || enterEngineerRepariable == null || enterEngineerRepariable.IsTraitDisabled || !info.ValidStances.HasStance(stance))
{
Cancel(self, true);
return false;
@@ -52,6 +54,9 @@ namespace OpenRA.Mods.Common.Activities
if (targetActor != enterActor)
return;
if (enterEngineerRepariable.IsTraitDisabled)
return;
if (enterHealth.DamageState == DamageState.Undamaged)
return;

View File

@@ -34,6 +34,8 @@ namespace OpenRA.Mods.Common.Activities
readonly Aircraft aircraft;
readonly bool stayOnResupplier;
readonly bool wasRepaired;
readonly PlayerResources playerResources;
readonly int unitCost;
int remainingTicks;
bool played;
@@ -54,6 +56,10 @@ namespace OpenRA.Mods.Common.Activities
transportCallers = self.TraitsImplementing<ICallForTransport>().ToArray();
move = self.Trait<IMove>();
aircraft = move as Aircraft;
playerResources = self.Owner.PlayerActor.Trait<PlayerResources>();
var valued = self.Info.TraitInfoOrDefault<ValuedInfo>();
unitCost = valued != null ? valued.Cost : 0;
var cannotRepairAtHost = health == null || health.DamageState == DamageState.Undamaged
|| !allRepairsUnits.Any()
@@ -165,6 +171,12 @@ namespace OpenRA.Mods.Common.Activities
public override void Cancel(Actor self, bool keepQueue = false)
{
// HACK: force move activities to ignore the transit-only cells when cancelling
// The idle handler will take over and move them into a safe cell
if (ChildActivity != null)
foreach (var c in ChildActivity.ActivitiesImplementing<Move>())
c.Cancel(self, false, true);
foreach (var t in transportCallers)
t.MovementCancelled(self);
@@ -221,7 +233,6 @@ namespace OpenRA.Mods.Common.Activities
void RepairTick(Actor self)
{
// First active.
var repairsUnits = allRepairsUnits.FirstOrDefault(r => !r.IsTraitDisabled && !r.IsTraitPaused);
if (repairsUnits == null)
{
@@ -248,8 +259,6 @@ namespace OpenRA.Mods.Common.Activities
if (remainingTicks == 0)
{
var valued = self.Info.TraitInfoOrDefault<ValuedInfo>();
var unitCost = valued != null ? valued.Cost : 0;
var hpToRepair = repairable != null && repairable.Info.HpPerStep > 0 ? repairable.Info.HpPerStep : repairsUnits.Info.HpPerStep;
// Cast to long to avoid overflow when multiplying by the health
@@ -261,13 +270,13 @@ namespace OpenRA.Mods.Common.Activities
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", repairsUnits.Info.StartRepairingNotification, self.Owner.Faction.InternalName);
}
if (!self.Owner.PlayerActor.Trait<PlayerResources>().TakeCash(cost, true))
if (!playerResources.TakeCash(cost, true))
{
remainingTicks = 1;
return;
}
self.InflictDamage(host.Actor, new Damage(-hpToRepair));
self.InflictDamage(host.Actor, new Damage(-hpToRepair, repairsUnits.Info.RepairDamageTypes));
remainingTicks = repairsUnits.Info.Interval;
}
else

View File

@@ -31,14 +31,18 @@ namespace OpenRA.Mods.Common.Effects
// Facing is last on these overloads partially for backwards compatibility with previous main ctor revision
// and partially because most effects don't need it. The latter is also the reason for placement of 'delay'.
public SpriteEffect(WPos pos, World world, string image, string sequence, string palette,
bool visibleThroughFog = false, int facing = 0, int delay = 0)
: this(() => pos, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
bool visibleThroughFog = false, int delay = 0)
: this(() => pos, () => WAngle.Zero, world, image, sequence, palette, visibleThroughFog, delay) { }
public SpriteEffect(Actor actor, World world, string image, string sequence, string palette,
bool visibleThroughFog = false, int facing = 0, int delay = 0)
: this(() => actor.CenterPosition, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
bool visibleThroughFog = false, int delay = 0)
: this(() => actor.CenterPosition, () => WAngle.Zero, world, image, sequence, palette, visibleThroughFog, delay) { }
public SpriteEffect(Func<WPos> posFunc, Func<int> facingFunc, World world, string image, string sequence, string palette,
public SpriteEffect(WPos pos, WAngle facing, World world, string image, string sequence, string palette,
bool visibleThroughFog = false, int delay = 0)
: this(() => pos, () => facing, world, image, sequence, palette, visibleThroughFog, delay) { }
public SpriteEffect(Func<WPos> posFunc, Func<WAngle> facingFunc, World world, string image, string sequence, string palette,
bool visibleThroughFog = false, int delay = 0)
{
this.world = world;
@@ -62,16 +66,18 @@ namespace OpenRA.Mods.Common.Effects
world.ScreenMap.Add(this, pos, anim.Image);
initialized = true;
}
else
{
anim.Tick();
anim.Tick();
pos = posFunc();
world.ScreenMap.Update(this, pos, anim.Image);
pos = posFunc();
world.ScreenMap.Update(this, pos, anim.Image);
}
}
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (!visibleThroughFog && world.FogObscures(pos))
if (!initialized || (!visibleThroughFog && world.FogObscures(pos)))
return SpriteRenderable.None;
return anim.Render(pos, wr.Palette(palette));

View File

@@ -67,20 +67,23 @@ namespace OpenRA.Mods.Common.Graphics
return () => orientation;
}
public Func<int> GetFacing()
public Func<WAngle> GetFacing()
{
var facingInfo = Actor.TraitInfoOrDefault<IFacingInfo>();
if (facingInfo == null)
return () => 0;
return () => WAngle.Zero;
// Dynamic facing takes priority
var dynamicInit = dict.GetOrDefault<DynamicFacingInit>();
if (dynamicInit != null)
return dynamicInit.Value(null);
{
var getFacing = dynamicInit.Value(null);
return () => WAngle.FromFacing(getFacing());
}
// Fall back to initial actor facing if an Init isn't available
var facingInit = dict.GetOrDefault<FacingInit>();
var facing = facingInit != null ? facingInit.Value(null) : facingInfo.GetInitialFacing();
var facing = WAngle.FromFacing(facingInit != null ? facingInit.Value(null) : facingInfo.GetInitialFacing());
return () => facing;
}

View File

@@ -95,11 +95,11 @@ namespace OpenRA.Mods.Common.Graphics
{
static readonly WDist DefaultShadowSpriteZOffset = new WDist(-5);
protected Sprite[] sprites;
readonly bool reverseFacings, transpose, useClassicFacingFudge;
readonly bool reverseFacings, transpose;
readonly string sequence;
protected readonly ISpriteSequenceLoader Loader;
readonly string sequence;
public string Name { get; private set; }
public int Start { get; private set; }
public int Length { get; private set; }
@@ -156,7 +156,6 @@ namespace OpenRA.Mods.Common.Graphics
Tick = LoadField(d, "Tick", 40);
transpose = LoadField(d, "Transpose", false);
Frames = LoadField<int[]>(d, "Frames", null);
useClassicFacingFudge = LoadField(d, "UseClassicFacingFudge", false);
var flipX = LoadField(d, "FlipX", false);
var flipY = LoadField(d, "FlipY", false);
@@ -168,11 +167,6 @@ namespace OpenRA.Mods.Common.Graphics
Facings = -Facings;
}
if (useClassicFacingFudge && Facings != 32)
throw new InvalidOperationException(
"{0}: Sequence {1}.{2}: UseClassicFacingFudge is only valid for 32 facings"
.F(info.Nodes[0].Location, sequence, animation));
var offset = LoadField(d, "Offset", float3.Zero);
var blendMode = LoadField(d, "BlendMode", BlendMode.Alpha);
@@ -369,22 +363,22 @@ namespace OpenRA.Mods.Common.Graphics
public Sprite GetSprite(int frame)
{
return GetSprite(Start, frame, 0);
return GetSprite(Start, frame, WAngle.Zero);
}
public Sprite GetSprite(int frame, int facing)
public Sprite GetSprite(int frame, WAngle facing)
{
return GetSprite(Start, frame, facing);
}
public Sprite GetShadow(int frame, int facing)
public Sprite GetShadow(int frame, WAngle facing)
{
return ShadowStart >= 0 ? GetSprite(ShadowStart, frame, facing) : null;
}
protected virtual Sprite GetSprite(int start, int frame, int facing)
protected virtual Sprite GetSprite(int start, int frame, WAngle facing)
{
var f = Util.QuantizeFacing(facing, Facings, useClassicFacingFudge);
var f = GetFacingFrameOffset(facing);
if (reverseFacings)
f = (Facings - f) % Facings;
@@ -398,5 +392,10 @@ namespace OpenRA.Mods.Common.Graphics
return sprites[j];
}
protected virtual int GetFacingFrameOffset(WAngle facing)
{
return Util.QuantizeFacing(facing.Facing, Facings);
}
}
}

View File

@@ -66,18 +66,17 @@ namespace OpenRA.Mods.Common.Graphics
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var wcr = Game.Renderer.WorldRgbaColorRenderer;
var center = wr.Screen3DPosition(centerPosition);
var cr = Game.Renderer.RgbaColorRenderer;
var center = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(centerPosition));
for (var i = 0; i < trailCount; i++)
{
var angle = trailAngle - new WAngle(i * (trailSeparation.Angle <= 512 ? 1 : -1));
var length = radius.Length * new WVec(angle.Cos(), angle.Sin(), 0) / 1024;
var end = wr.Screen3DPosition(centerPosition + length);
var end = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(centerPosition + length));
var alpha = color.A - i * color.A / trailCount;
wcr.DrawLine(center, end, 3, Color.FromArgb(alpha, contrastColor));
wcr.DrawLine(center, end, 1, Color.FromArgb(alpha, color));
cr.DrawLine(center, end, 3, Color.FromArgb(alpha, contrastColor));
cr.DrawLine(center, end, 1, Color.FromArgb(alpha, color));
}
RangeCircleAnnotationRenderable.DrawRangeCircle(wr, centerPosition, radius, 1, color, 3, contrastColor);

View File

@@ -0,0 +1,168 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Graphics
{
public struct IsometricSelectionBarsAnnotationRenderable : IRenderable, IFinalizedRenderable
{
const int BarWidth = 3;
const int BarHeight = 4;
const int BarStride = 5;
static readonly Color EmptyColor = Color.FromArgb(160, 30, 30, 30);
static readonly Color DarkEmptyColor = Color.FromArgb(160, 15, 15, 15);
static readonly Color DarkenColor = Color.FromArgb(24, 0, 0, 0);
static readonly Color LightenColor = Color.FromArgb(24, 255, 255, 255);
readonly WPos pos;
readonly Actor actor;
readonly bool displayHealth;
readonly bool displayExtra;
readonly Polygon bounds;
public IsometricSelectionBarsAnnotationRenderable(Actor actor, Polygon bounds, bool displayHealth, bool displayExtra)
: this(actor.CenterPosition, actor, bounds)
{
this.displayHealth = displayHealth;
this.displayExtra = displayExtra;
}
public IsometricSelectionBarsAnnotationRenderable(WPos pos, Actor actor, Polygon bounds)
: this()
{
this.pos = pos;
this.actor = actor;
this.bounds = bounds;
}
public WPos Pos { get { return pos; } }
public bool DisplayHealth { get { return displayHealth; } }
public bool DisplayExtra { get { return displayExtra; } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return 0; } }
public bool IsDecoration { get { return true; } }
public IRenderable WithPalette(PaletteReference newPalette) { return this; }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBarsAnnotationRenderable(pos + vec, actor, bounds); }
public IRenderable AsDecoration() { return this; }
void DrawExtraBars(WorldRenderer wr)
{
var i = 1;
foreach (var extraBar in actor.TraitsImplementing<ISelectionBar>())
{
var value = extraBar.GetValue();
if (value != 0 || extraBar.DisplayWhenEmpty)
DrawBar(wr, extraBar.GetValue(), extraBar.GetColor(), i++);
}
}
void DrawBar(WorldRenderer wr, float value, Color barColor, int barNum, float? secondValue = null, Color? secondColor = null)
{
var darkColor = Color.FromArgb(barColor.A, barColor.R / 2, barColor.G / 2, barColor.B / 2);
var barAspect = new float2(1f, 0.5f);
var stepAspect = new float2(1f, -0.5f);
var offset = barNum * BarStride * barAspect - new float2(0, BarHeight + 1);
var start = wr.Viewport.WorldToViewPx(bounds.Vertices[1]).ToFloat2() + offset;
var end = wr.Viewport.WorldToViewPx(bounds.Vertices[0]).ToFloat2() + offset;
// HACK: Work around rounding errors that may cause a few-px offset in the end relative to the start
// Force the bar to take a 45 degree angle
end = new float2(end.X, start.Y - (end.X - start.X) / 2);
// Round the cut point to the nearest pixel to avoid potential off-by-one pixel offsets distorting the bar
var cutX = (int)(float2.Lerp(start.X, end.X, value) + 0.5f);
var cut = new float2(cutX, start.Y - (cutX - start.X) / 2);
var cr = Game.Renderer.RgbaColorRenderer;
var da = BarWidth * barAspect;
var db = new int2(0, BarHeight);
var dc = da + db;
// Filled bar
cr.FillRect(start + da, start + dc, cut + dc, cut + da, darkColor);
cr.FillRect(start, start + da, start + dc, start + db, darkColor);
cr.FillRect(start, start + da, cut + da, cut, barColor);
// Faint marks to break the monotony of the solid bar
var dx = BarWidth;
while (dx < cut.X - start.X)
{
var step = start + dx * stepAspect;
cr.DrawLine(step, step + da, 1, DarkenColor);
cr.DrawLine(step + da, step + dc, 1, LightenColor);
dx += BarWidth;
}
// Second bar (e.g. applied damage)
if (secondValue.HasValue && secondColor.HasValue)
{
var secondCutX = (int)(float2.Lerp(start.X, end.X, secondValue.Value) + 0.5f);
var secondCut = new float2(secondCutX, start.Y - (secondCutX - start.X) / 2);
var darkSecond = Color.FromArgb(secondColor.Value.A, secondColor.Value.R / 2, secondColor.Value.G / 2, secondColor.Value.B / 2);
cr.FillRect(cut + da, cut + dc, secondCut + dc, secondCut + da, darkSecond);
cr.FillRect(cut, cut + da, secondCut + da, secondCut, secondColor.Value);
value = secondValue.Value;
cut = secondCut;
}
// Empty bar
if (value < 1)
{
cr.FillRect(cut + da, cut + dc, end + dc, end + da, DarkEmptyColor);
cr.FillRect(cut, cut + da, end + da, end, EmptyColor);
}
}
Color GetHealthColor(IHealth health)
{
if (Game.Settings.Game.UsePlayerStanceColors)
return actor.Owner.PlayerStanceColor(actor);
return health.DamageState == DamageState.Critical ? Color.Red :
health.DamageState == DamageState.Heavy ? Color.Yellow : Color.LimeGreen;
}
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
if (!actor.IsInWorld || actor.IsDead)
return;
var health = actor.TraitOrDefault<IHealth>();
if (DisplayHealth)
{
if (health == null || health.IsDead)
return;
var displayValue = health.DisplayHP != health.HP ? (float?)health.DisplayHP / health.MaxHP : null;
DrawBar(wr, (float)health.HP / health.MaxHP, GetHealthColor(health), 0, displayValue, Color.OrangeRed);
}
if (DisplayExtra)
DrawExtraBars(wr);
}
public void RenderDebugGeometry(WorldRenderer wr) { }
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
}
}

View File

@@ -0,0 +1,83 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.Mods.Common.Graphics
{
public struct IsometricSelectionBoxAnnotationRenderable : IRenderable, IFinalizedRenderable
{
static readonly float2 TLOffset = new float2(-12, -6);
static readonly float2 TROffset = new float2(12, -6);
static readonly float2 TOffset = new float2(0, -13);
static readonly float2[] Offsets =
{
-TROffset, -TLOffset, -TOffset,
TROffset, -TOffset, -TLOffset,
-TLOffset, TOffset, TROffset,
TLOffset, TROffset, TOffset,
-TROffset, TOffset, TLOffset,
TLOffset, -TOffset, -TROffset
};
readonly WPos pos;
readonly Polygon bounds;
readonly Color color;
public IsometricSelectionBoxAnnotationRenderable(Actor actor, Polygon bounds, Color color)
{
pos = actor.CenterPosition;
this.bounds = bounds;
this.color = color;
}
public IsometricSelectionBoxAnnotationRenderable(WPos pos, Polygon bounds, Color color)
{
this.pos = pos;
this.bounds = bounds;
this.color = color;
}
public WPos Pos { get { return pos; } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return 0; } }
public bool IsDecoration { get { return true; } }
public IRenderable WithPalette(PaletteReference newPalette) { return this; }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(WVec vec) { return new IsometricSelectionBoxAnnotationRenderable(pos + vec, bounds, color); }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var screen = bounds.Vertices.Select(v => wr.Viewport.WorldToViewPx(v).ToFloat2()).ToArray();
var tl = new float2(-12, -6);
var tr = new float2(12, -6);
var t = new float2(0, -13);
var cr = Game.Renderer.RgbaColorRenderer;
for (var i = 0; i < 6; i++)
{
cr.DrawLine(new float3[] { screen[i] + Offsets[3 * i], screen[i], screen[i] + Offsets[3 * i + 1] }, 1, color, true);
cr.DrawLine(new float3[] { screen[i], screen[i] + Offsets[3 * i + 2] }, 1, color, true);
}
}
public void RenderDebugGeometry(WorldRenderer wr) { }
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
}
}

View File

@@ -0,0 +1,72 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Graphics
{
public struct UITextRenderable : IRenderable, IFinalizedRenderable
{
readonly SpriteFont font;
readonly WPos effectiveWorldPos;
readonly int2 screenPos;
readonly int zOffset;
readonly Color color;
readonly Color bgDark;
readonly Color bgLight;
readonly string text;
public UITextRenderable(SpriteFont font, WPos effectiveWorldPos, int2 screenPos, int zOffset, Color color, Color bgDark, Color bgLight, string text)
{
this.font = font;
this.effectiveWorldPos = effectiveWorldPos;
this.screenPos = screenPos;
this.zOffset = zOffset;
this.color = color;
this.bgDark = bgDark;
this.bgLight = bgLight;
this.text = text;
}
public UITextRenderable(SpriteFont font, WPos effectiveWorldPos, int2 screenPos, int zOffset, Color color, string text)
: this(font, effectiveWorldPos, screenPos, zOffset, color,
ChromeMetrics.Get<Color>("TextContrastColorDark"),
ChromeMetrics.Get<Color>("TextContrastColorLight"),
text) { }
public WPos Pos { get { return effectiveWorldPos; } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return true; } }
public IRenderable WithPalette(PaletteReference newPalette) { return new UITextRenderable(font, effectiveWorldPos, screenPos, zOffset, color, text); }
public IRenderable WithZOffset(int newOffset) { return new UITextRenderable(font, effectiveWorldPos, screenPos, zOffset, color, text); }
public IRenderable OffsetBy(WVec vec) { return new UITextRenderable(font, effectiveWorldPos + vec, screenPos, zOffset, color, text); }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
font.DrawTextWithContrast(text, screenPos, color, bgDark, bgLight, 1);
}
public void RenderDebugGeometry(WorldRenderer wr)
{
var size = font.Measure(text).ToFloat2();
Game.Renderer.RgbaColorRenderer.DrawRect(screenPos - 0.5f * size, screenPos + 0.5f * size, 1, Color.Red);
}
public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
}
}

View File

@@ -92,7 +92,7 @@ namespace OpenRA.Mods.Common.Lint
if (value == null)
continue;
if (!dict.ContainsKey(value.ToLower()))
if (!dict.ContainsKey(value.ToLowerInvariant()))
emitError("{0}.{1}.{2}: Missing weapon `{3}`."
.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
}
@@ -110,7 +110,7 @@ namespace OpenRA.Mods.Common.Lint
if (value == null)
continue;
if (!dict.ContainsKey(value.ToLower()))
if (!dict.ContainsKey(value.ToLowerInvariant()))
emitError("{0}.{1}.{2}: Missing voice `{3}`."
.F(actorInfo.Name, traitInfo.GetType().Name, fieldInfo.Name, value));
}

View File

@@ -12,7 +12,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Lint
@@ -60,9 +59,6 @@ namespace OpenRA.Mods.Common.Lint
var ungranted = consumed.Except(granted);
if (ungranted.Any())
emitError("Actor type `{0}` consumes conditions that are not granted: {1}".F(actorInfo.Key, ungranted.JoinWith(", ")));
if ((consumed.Any() || granted.Any()) && actorInfo.Value.TraitInfoOrDefault<ConditionManagerInfo>() == null)
emitError("Actor type `{0}` defines conditions but does not include a ConditionManager".F(actorInfo.Key));
}
}
}

View File

@@ -11,6 +11,7 @@
using System;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Lint

View File

@@ -53,19 +53,12 @@ namespace OpenRA.Mods.Common.LoadScreens
}
// Join a server directly
var connect = Launch.GetConnectAddress();
if (!string.IsNullOrEmpty(connect))
var connect = Launch.GetConnectEndPoint();
if (connect != null)
{
var parts = connect.Split(':');
if (parts.Length == 2)
{
var host = parts[0];
var port = Exts.ParseIntegerInvariant(parts[1]);
Game.LoadShellMap();
Game.RemoteDirectConnect(host, port);
return;
}
Game.LoadShellMap();
Game.RemoteDirectConnect(connect);
return;
}
// Start a map directly
@@ -122,6 +115,7 @@ namespace OpenRA.Mods.Common.LoadScreens
}
// Saved settings may have been invalidated by a hardware change
Game.Settings.Graphics.GLProfile = Game.Renderer.GLProfile;
Game.Settings.Graphics.VideoDisplay = Game.Renderer.CurrentDisplay;
// If a ModContent section is defined then we need to make sure that the

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<TargetFramework>net472</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<LangVersion>5</LangVersion>
@@ -20,29 +20,14 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<ItemGroup>
<Reference Include="FuzzyLogicLibrary">
<HintPath>..\thirdparty\download\FuzzyLogicLibrary.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="rix0rrr.BeaconLib">
<HintPath>..\thirdparty\download\rix0rrr.BeaconLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
<Private>False</Private>
</ProjectReference>
<PackageReference Include="OpenRA-FuzzyLogicLibrary" Version="1.0.1" />
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<AdditionalFiles Include="../stylecop.json" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">

View File

@@ -34,9 +34,7 @@ namespace OpenRA.Mods.Common.Orders
world.CancelInputMode();
var queued = mi.Modifiers.HasModifier(Modifiers.Shift);
foreach (var subject in Subjects)
if (subject != target)
yield return new Order(OrderName, subject, Target.FromActor(target), queued);
yield return new Order(OrderName, null, Target.FromActor(target), queued, null, Subjects.Where(s => s != target).ToArray());
}
public override void Tick(World world)

View File

@@ -158,7 +158,7 @@ namespace OpenRA.Mods.Common.Projectiles
if (!string.IsNullOrEmpty(info.Image))
{
anim = new Animation(world, info.Image, new Func<int>(GetEffectiveFacing));
anim = new Animation(world, info.Image, new Func<WAngle>(GetEffectiveFacing));
anim.PlayRepeating(info.Sequences.Random(world.SharedRandom));
}
@@ -176,7 +176,7 @@ namespace OpenRA.Mods.Common.Projectiles
remainingBounces = info.BounceCount;
}
int GetEffectiveFacing()
WAngle GetEffectiveFacing()
{
var at = (float)ticks / (length - 1);
var attitude = angle.Tan() * (1 - 2 * at) / (4 * 1024);
@@ -184,9 +184,11 @@ namespace OpenRA.Mods.Common.Projectiles
var u = (facing % 128) / 128f;
var scale = 512 * u * (1 - u);
return (int)(facing < 128
var effective = (int)(facing < 128
? facing - scale * attitude
: facing + scale * attitude);
return WAngle.FromFacing(effective);
}
public void Tick(World world)
@@ -210,8 +212,8 @@ namespace OpenRA.Mods.Common.Projectiles
if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0)
{
var delayedPos = WPos.LerpQuadratic(source, target, angle, ticks - info.TrailDelay, length);
world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom),
trailPalette, facing: GetEffectiveFacing())));
world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, GetEffectiveFacing(), w,
info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette)));
smokeTicks = info.TrailInterval;
}

View File

@@ -65,13 +65,14 @@ namespace OpenRA.Mods.Common.Projectiles
this.info = info;
this.args = args;
pos = args.Source;
var facing = WAngle.FromFacing(args.Facing);
var convertedVelocity = new WVec(info.Velocity.Y, -info.Velocity.X, info.Velocity.Z);
velocity = convertedVelocity.Rotate(WRot.FromFacing(args.Facing));
velocity = convertedVelocity.Rotate(WRot.FromYaw(facing));
acceleration = new WVec(info.Acceleration.Y, -info.Acceleration.X, info.Acceleration.Z);
if (!string.IsNullOrEmpty(info.Image))
{
anim = new Animation(args.SourceActor.World, info.Image, () => args.Facing);
anim = new Animation(args.SourceActor.World, info.Image, () => facing);
if (!string.IsNullOrEmpty(info.OpenSequence))
anim.PlayThen(info.OpenSequence, () => anim.PlayRepeating(info.Sequences.Random(args.SourceActor.World.SharedRandom)));

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.GameRules;
using OpenRA.Graphics;
@@ -148,8 +149,11 @@ namespace OpenRA.Mods.Common.Projectiles
source = args.CurrentSource();
if (hasLaunchEffect && ticks == 0)
world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, args.CurrentMuzzleFacing, world,
{
Func<WAngle> getMuzzleFacing = () => WAngle.FromFacing(args.CurrentMuzzleFacing());
world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, getMuzzleFacing, world,
info.LaunchEffectImage, info.LaunchEffectSequence, info.LaunchEffectPalette)));
}
// Beam tracks target
if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor))

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.GameRules;
@@ -203,7 +204,7 @@ namespace OpenRA.Mods.Common.Projectiles
WDist distanceCovered;
WDist rangeLimit;
int renderFacing;
WAngle renderFacing;
[Sync]
int hFacing;
@@ -428,7 +429,7 @@ namespace OpenRA.Mods.Common.Projectiles
if (!tp.Trait.DeflectionStances.HasStance(tp.Actor.Owner.Stances[args.SourceActor.Owner]))
return false;
return tp.Actor.World.SharedRandom.Next(100 / tp.Trait.Chance) == 0;
return tp.Actor.World.SharedRandom.Next(100) < tp.Trait.Chance;
}
void ChangeSpeed(int sign = 1)
@@ -835,7 +836,7 @@ namespace OpenRA.Mods.Common.Projectiles
else
move = HomingTick(world, tarDistVec, relTarHorDist);
renderFacing = new WVec(move.X, move.Y - move.Z, 0).Yaw.Facing;
renderFacing = new WVec(move.X, move.Y - move.Z, 0).Yaw;
// Move the missile
var lastPos = pos;
@@ -857,8 +858,8 @@ namespace OpenRA.Mods.Common.Projectiles
// Create the sprite trail effect
if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || info.TrailWhenDeactivated))
{
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom),
trailPalette, facing: renderFacing)));
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, renderFacing, w,
info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette)));
ticksToNextSmoke = info.TrailInterval;
}

View File

@@ -10,8 +10,8 @@
#endregion
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
using OpenRA.Traits;
@@ -31,14 +31,14 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Grant an external condition on this actor and return the revocation token.",
"Conditions must be defined on an ExternalConditions trait on the actor.",
"If duration > 0 the condition will be automatically revoked after the defined number of ticks")]
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
public int GrantCondition(string condition, int duration = 0)
{
var external = externalConditions
.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(Self, this));
if (external == null)
throw new InvalidDataException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
throw new LuaException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
return external.GrantCondition(Self, this, duration);
}

View File

@@ -0,0 +1,59 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Traits;
using OpenRA.Scripting;
namespace OpenRA.Mods.Common.Scripting
{
[ScriptPropertyGroup("Player")]
public class PlayerConditionProperties : ScriptPlayerProperties
{
readonly ExternalCondition[] externalConditions;
public PlayerConditionProperties(ScriptContext context, Player player)
: base(context, player)
{
externalConditions = player.PlayerActor.TraitsImplementing<ExternalCondition>().ToArray();
}
[Desc("Grant an external condition on the player actor and return the revocation token.",
"Conditions must be defined on an ExternalConditions trait on the player actor.",
"If duration > 0 the condition will be automatically revoked after the defined number of ticks.")]
public int GrantCondition(string condition, int duration = 0)
{
var external = externalConditions
.FirstOrDefault(t => t.Info.Condition == condition && t.CanGrantCondition(Player.PlayerActor, this));
if (external == null)
throw new LuaException("Condition `{0}` has not been listed on an enabled ExternalCondition trait".F(condition));
return external.GrantCondition(Player.PlayerActor, this, duration);
}
[Desc("Revoke a condition using the token returned by GrantCondition.")]
public void RevokeCondition(int token)
{
foreach (var external in externalConditions)
if (external.TryRevokeCondition(Player.PlayerActor, this, token))
break;
}
[Desc("Check whether this player actor accepts a specific external condition.")]
public bool AcceptsCondition(string condition)
{
return externalConditions
.Any(t => t.Info.Condition == condition && t.CanGrantCondition(Player.PlayerActor, this));
}
}
}

View File

@@ -398,8 +398,17 @@ namespace OpenRA.Mods.Common.Server
LoadMapSettings(server, server.LobbyInfo.GlobalSettings, server.Map.Rules);
// Reset client states
var selectableFactions = server.Map.Rules.Actors["world"].TraitInfos<FactionInfo>()
.Where(f => f.Selectable)
.Select(f => f.InternalName)
.ToList();
foreach (var c in server.LobbyInfo.Clients)
{
c.State = Session.ClientState.Invalid;
if (!selectableFactions.Contains(c.Faction))
c.Faction = "Random";
}
// Reassign players into new slots based on their old slots:
// - Observers remain as observers

View File

@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Server
public void ServerStarted(S server)
{
if (!server.Ip.Equals(IPAddress.Loopback) && LanGameBeacon != null)
if (server.Type != ServerType.Local && LanGameBeacon != null)
LanGameBeacon.Start();
}

View File

@@ -96,13 +96,18 @@ namespace OpenRA.Mods.Common.SpriteLoaders
{
regions = new List<Rectangle>();
offsets = new List<float2>();
var pngRectangle = new Rectangle(0, 0, png.Width, png.Height);
string frame;
for (var i = 0; png.EmbeddedData.TryGetValue("Frame[" + i + "]", out frame); i++)
{
// Format: x,y,width,height;offsetX,offsetY
var coords = frame.Split(';');
regions.Add(FieldLoader.GetValue<Rectangle>("Region", coords[0]));
var region = FieldLoader.GetValue<Rectangle>("Region", coords[0]);
if (!pngRectangle.Contains(region))
throw new InvalidDataException("Invalid frame regions {0} defined.".F(region));
regions.Add(region);
offsets.Add(FieldLoader.GetValue<float2>("Offset", coords[1]));
}
}
@@ -122,7 +127,7 @@ namespace OpenRA.Mods.Common.SpriteLoaders
if (png.EmbeddedData.ContainsKey("FrameAmount"))
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
else
frameAmount = png.Width / frameSize.Width * png.Height / frameSize.Height;
frameAmount = (png.Width / frameSize.Width) * (png.Height / frameSize.Height);
}
else if (png.EmbeddedData.ContainsKey("FrameAmount"))
{
@@ -141,6 +146,10 @@ namespace OpenRA.Mods.Common.SpriteLoaders
var framesPerRow = png.Width / frameSize.Width;
var rows = (frameAmount + framesPerRow - 1) / framesPerRow;
if (png.Width < frameSize.Width * frameAmount / rows || png.Height < frameSize.Height * rows)
throw new InvalidDataException("Invalid frame size {0} and frame amount {1} defined.".F(frameSize, frameAmount));
regions = new List<Rectangle>();
offsets = new List<float2>();

View File

@@ -196,7 +196,6 @@ namespace OpenRA.Mods.Common.Traits
Repairable repairable;
Rearmable rearmable;
IAircraftCenterPositionOffset[] positionOffsets;
ConditionManager conditionManager;
IDisposable reservation;
IEnumerable<int> speedModifiers;
INotifyMoving[] notifyMoving;
@@ -230,8 +229,8 @@ namespace OpenRA.Mods.Common.Traits
bool airborne;
bool cruising;
int airborneToken = ConditionManager.InvalidConditionToken;
int cruisingToken = ConditionManager.InvalidConditionToken;
int airborneToken = Actor.InvalidConditionToken;
int cruisingToken = Actor.InvalidConditionToken;
MovementType movementTypes;
WPos cachedPosition;
@@ -293,7 +292,6 @@ namespace OpenRA.Mods.Common.Traits
{
repairable = self.TraitOrDefault<Repairable>();
rearmable = self.TraitOrDefault<Rearmable>();
conditionManager = self.TraitOrDefault<ConditionManager>();
speedModifiers = self.TraitsImplementing<ISpeedModifier>().ToArray().Select(sm => sm.GetSpeedModifier());
cachedPosition = self.CenterPosition;
notifyMoving = self.TraitsImplementing<INotifyMoving>().ToArray();
@@ -1116,8 +1114,8 @@ namespace OpenRA.Mods.Common.Traits
return;
airborne = true;
if (conditionManager != null && !string.IsNullOrEmpty(Info.AirborneCondition) && airborneToken == ConditionManager.InvalidConditionToken)
airborneToken = conditionManager.GrantCondition(self, Info.AirborneCondition);
if (!string.IsNullOrEmpty(Info.AirborneCondition) && airborneToken == Actor.InvalidConditionToken)
airborneToken = self.GrantCondition(Info.AirborneCondition);
}
void OnAirborneAltitudeLeft()
@@ -1126,8 +1124,8 @@ namespace OpenRA.Mods.Common.Traits
return;
airborne = false;
if (conditionManager != null && airborneToken != ConditionManager.InvalidConditionToken)
airborneToken = conditionManager.RevokeCondition(self, airborneToken);
if (airborneToken != Actor.InvalidConditionToken)
airborneToken = self.RevokeCondition(airborneToken);
}
#endregion
@@ -1140,8 +1138,8 @@ namespace OpenRA.Mods.Common.Traits
return;
cruising = true;
if (conditionManager != null && !string.IsNullOrEmpty(Info.CruisingCondition) && cruisingToken == ConditionManager.InvalidConditionToken)
cruisingToken = conditionManager.GrantCondition(self, Info.CruisingCondition);
if (!string.IsNullOrEmpty(Info.CruisingCondition) && cruisingToken == Actor.InvalidConditionToken)
cruisingToken = self.GrantCondition(Info.CruisingCondition);
}
void OnCruisingAltitudeLeft()
@@ -1150,8 +1148,8 @@ namespace OpenRA.Mods.Common.Traits
return;
cruising = false;
if (conditionManager != null && cruisingToken != ConditionManager.InvalidConditionToken)
cruisingToken = conditionManager.RevokeCondition(self, cruisingToken);
if (cruisingToken != Actor.InvalidConditionToken)
cruisingToken = self.RevokeCondition(cruisingToken);
}
#endregion

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