Compare commits

...

542 Commits

Author SHA1 Message Date
Matthias Mailänder
71feb90ef2 Mention infrastructure Patreon account. 2023-04-16 21:57:26 +02:00
Matthias Mailänder
f08a0b113e Allow mods to customize application title. 2021-09-19 22:29:50 +02:00
Matthias Mailänder
3a8957c6f3 Remove unused function parameter. 2021-09-19 22:29:50 +02:00
Smittytron
99006a02ed Add destroy triggers for prison actors in Allies03a 2021-09-18 21:26:10 +02:00
Smittytron
77e1e77387 Use FireClusterWarhead with barrels 2021-09-18 21:26:10 +02:00
Matthias Mailänder
0604a58581 Send a ping when the server is shut down
and stop the LAN beacon.
2021-09-18 17:38:52 +01:00
Matthias Mailänder
58e482c05a Fix hashset documentation. 2021-09-18 13:16:25 +01:00
Matthias Mailänder
eba266aecf Use MarkDown tables in trait documentation export. 2021-09-18 12:52:27 +01:00
Matthias Mailänder
5bf4daddec Set the default game name to the player name. 2021-09-15 18:39:24 +02:00
Paul Chote
54c08748e0 Overhaul player disconnect notifications. 2021-09-12 22:53:50 +02:00
abcdefg30
b8e343bee9 Fix Phase Transports not uncloaking while unloading 2021-09-12 10:47:33 +02:00
Vapre
5ae4662f08 RunUnsynced, do not recalculate sync hash on reentry. Cache debug settings. 2021-09-12 10:06:44 +02:00
Paul Chote
b3159d7515 Fix d2k map editor crash if spice is placed at the edge of map. 2021-09-09 10:19:17 +02:00
abcdefg30
a1e62158e2 Add a workaround for "Check around synced code" problems 2021-09-08 20:26:53 +02:00
abcdefg30
777d966958 Remove unnecessary server creation when creating a new map 2021-09-08 20:26:53 +02:00
abcdefg30
0b75991fbc Make the editor use an EchoConnection instead of a local server 2021-09-07 20:45:50 +02:00
Paul Chote
e0e219793f Fix shellmap OrderManager use after dispose. 2021-09-02 23:23:23 +02:00
Paul Chote
8f412f869d Move order latency control to the server. 2021-09-02 23:23:23 +02:00
Paul Chote
6421c17515 Rework IConnection implementations:
* EchoConnection is now a trivial buffer that stores
  and repeats orders directly without serialization.

* NetworkConnection no longer subclasses EchoConnection,
  and now also caches local orders without serialization.

* Replay recording was moved to NetworkConnection
  (it is never used on EchoConnection).
2021-09-02 23:23:23 +02:00
Paul Chote
408f30b5cd Extract OrderIO.SerializeOrders helper.
Note: This changes immediate orders to no longer be
individually framed in their own packets.
2021-09-02 23:23:23 +02:00
teinarss
db74f155bb Update DropClient to always drop Connection 2021-09-02 08:05:37 +02:00
Matthias Mailänder
978de64903 Don't count suicides into the game score. 2021-09-02 08:00:44 +02:00
Matthias Mailänder
e616cd1bcb Save the player color in the introduction panel. 2021-09-02 07:39:54 +02:00
Andre Mohren
2c84c43607 Fixed odd sprite size "frame hopping". 2021-09-02 07:34:13 +02:00
Paul Chote
442d91537e Don't display spectator info for scripted players. 2021-08-28 23:15:02 +02:00
Paul Chote
7f92d64d84 Disable replay player visibility dropdown in singleplayer missions. 2021-08-28 23:15:02 +02:00
Paul Chote
c7700a8a2b Fix DropDownButtonWidget ignoring yaml separator overrides. 2021-08-28 23:15:02 +02:00
Paul Chote
e389c00a11 Remove packet byte wrangling from OrderManager. 2021-08-26 22:00:59 +02:00
Paul Chote
52b597d5d2 Remove order latency checks from BaseBuilderQueueManager. 2021-08-26 22:00:59 +02:00
Paul Chote
8a587ddeab Disable depth-buffer command if EnableDepthBuffer is disabled. 2021-08-25 17:42:44 +02:00
Paul Chote
512dee0ac0 Fix rendering glitches with EnableDepthBuffer disabled. 2021-08-25 17:42:44 +02:00
Matthias Mailänder
024beacafb Don't smear the paste while moving the mouse. 2021-08-22 16:06:21 +02:00
teinarss
b4256df9c1 Cleanup in Connection 2021-08-22 09:43:25 +01:00
reaperrr
c860bf19ee Add GDI 05c
Well, 05eb really, but until we implement campaign progression,
calling it 'c' is the easiest way to differentiate it without breaking
our mission title length/alignment conventions.
2021-08-21 20:34:50 +02:00
Paul Chote
06ea7bf923 Regenerate TS map previews. 2021-08-21 14:16:02 +02:00
Paul Chote
3551eb3128 Fix map preview bounds calculation. 2021-08-21 14:16:02 +02:00
Paul Chote
7f94d67d39 Replace Map.CustomTerrain radar colors with IRadarTerrainLayer.
* TSVeinsRenderer now shows border cells on the radar
* BuildableTerrainLayer now uses the radar colors defined on the individual tiles
* CliffBackImpassabilityLayer no longer overrides the underlying terrain color.
2021-08-21 14:16:02 +02:00
Paul Chote
72c0c7e38b Remove rocks and trees from TS minimap previews. 2021-08-21 14:16:02 +02:00
Paul Chote
2a2785d8c9 Fix model debug sprite rectangle rendering. 2021-08-21 13:45:41 +02:00
Paul Chote
7a3dae428a Work around clipping on ramp type 7. 2021-08-21 13:45:41 +02:00
Paul Chote
dcd3e8d444 Ignore terrain slopes when calculating model shadows.
This is less realistic, but better matches the original
game and is the only practical way to reduce visual issues
caused by long shadows being cast over multiple cells.
2021-08-21 13:45:41 +02:00
Paul Chote
68710e48a6 Add terrain orientation support for Mobile. 2021-08-21 13:45:41 +02:00
Paul Chote
c88d1bbefa Add WRot SLerp and axis-angle ctor.
These allow more complex calculations to be made
using rotations.
2021-08-21 13:45:41 +02:00
Paul Chote
d509d3f5f9 Fix carryall InitialActor creation. 2021-08-20 21:04:18 +02:00
Smittytron
8a4945d20a Polish and standardize Allies01 2021-08-20 20:48:05 +02:00
Andre Mohren
0ea6a5626f Do not enforce mods to use stylecop. 2021-08-20 20:45:31 +02:00
Paul Chote
864cc4becc Fix weather particle physics.
The trait documentation specified that the speed
and offset values are px/tick, but they have actually
always been treated as px/render.

Fix the update logic and rescale the map definitions
to account for the fixed behaviour.
2021-08-20 20:38:37 +02:00
Paul Chote
d2257f9784 Fix macOS compat build dock icon, app name, dark mode support. 2021-08-20 20:34:23 +02:00
Paul Chote
f70457a66f Remove unused symbol lookups. 2021-08-20 20:34:23 +02:00
Paul Chote
278a4acf96 Fix Engine.LaunchPath for macOS compatibility builds. 2021-08-20 20:34:23 +02:00
Paul Chote
be3412ee74 Fix depth offsets for sprite with non-zero ZRamp.
Bibs and other effects that should be drawn at ground level
can now simply define ZRamp: 1, Offset: <X>,<Y>,1, avoiding the
need to account for the Y offset or internal sprite offsets.
2021-08-20 20:17:55 +02:00
Paul Chote
27f9f35efb Fix incorrect world height to screen depth conversion. 2021-08-20 20:17:55 +02:00
Paul Chote
ef1aee5e95 Fix and document the depth buffer calculations. 2021-08-20 20:17:55 +02:00
Paul Chote
962d6496bd Overhaul depth preview rendering:
* Replace Jet color map with grayscale
* Added Shift + \ hotkey to toggle preview
* Added Shift + [, Shift + ] hotkeys to change contrast
* Added Shift + ;, Shift + ' hotkeys to change offset
2021-08-20 20:17:55 +02:00
Paul Chote
91c5f2cabe Add Cityscape map. 2021-08-20 20:17:55 +02:00
Paul Chote
a9c6430924 Fix TS map importer:
- Fix infantry subcells and facing
- Import sandbags/walls.
2021-08-20 20:17:55 +02:00
Vapre
2ea2815529 CallFunc, avoid null check. 2021-08-20 12:31:04 +02:00
Paul Chote
5eb4c6a0bb Set _KDE_NET_WM_DESKTOP_FILE x11 window property.
This explicitly tells KDE to associate the OpenRA window with
the integrated appimage desktop file, allowing it to use the
correct resolution icon for the task switcher.
2021-08-16 20:18:00 +02:00
abcdefg30
4145723ef0 Remove Sdl2HardwareCursorException 2021-08-14 17:11:34 +01:00
abcdefg30
b8ba1b36fe Don't throw an exception when creating a hardware cursor fails 2021-08-14 17:11:34 +01:00
Matthias Mailänder
f63e15f5de Fix igloos being labeled as haystacks in-game. 2021-08-11 19:09:20 -05:00
MustaphaTR
2f44b016b0 Add a condition per tileset. 2021-08-11 19:09:20 -05:00
Matthias Mailänder
b7bba5d55a Restrict sonar pulse to unshrouded water. 2021-08-11 22:46:19 +02:00
teinarss
fba4c5049c Replace Thread.CurrentThread.ManagedThreadId with Environment.CurrentManagedThreadId 2021-08-10 23:02:18 +02:00
Vapre
573a6cf645 FindAndDeliverResources, trivial optimizations. 2021-08-10 23:59:43 +03:00
reaperrr
eff7e803bf Minor MoveOrderTargeter optimization
IsTraitPaused should be cheaper than Map.Contains,
so let's perform the cheaper check first.
2021-08-10 18:26:05 +02:00
reaperrr
58f55b808a Add comment in Mobile
This isn't obvious to people not entirely familiar
with the code.
2021-08-10 18:26:05 +02:00
reaperrr
777a927c04 Cache unchanging values for MoveWithinRange
In theory, CandidateMovementCells could be called
every tick, so let's avoid creating the vars
every time.
2021-08-10 18:26:05 +02:00
Matthias Mailänder
0249116206 Rename instant to fast charge to avoid tab complete clash. 2021-08-09 13:17:47 -05:00
Ivaylo Draganov
2d0e7040db Add separate chat panel for spectators and players
Forces the chat and performance panels to be re-initialized when a
player transitions to spectators. This ensures that spectators don't
get to see faction themed widgets.
2021-08-08 12:50:31 +01:00
Paul Chote
edd3a2eb75 Fix ingame menu tab display. 2021-08-07 13:12:56 -05:00
Paul Chote
29f4f5a0cd Remove redundant ts ingame-info.yaml. 2021-08-07 13:12:56 -05:00
abcdefg30
ba7e1319ac Remove DisableWindowsRenderThread and use threaded renderering for non windowed mode 2021-08-07 14:49:09 +01:00
Ivaylo Draganov
7a1169744e Add tooltips to map name and author in map chooser 2021-08-05 18:36:50 +02:00
abcdefg30
453d59ae16 Defer rollover checks while generating selection decorations 2021-08-05 01:43:35 +03:00
Vapre
35e9fade06 PathGraph, skip closed cells early. Fix #19579. 2021-08-04 20:02:12 +02:00
Paul Chote
8fc042fed1 Fix style nits in OrderEffects. 2021-08-02 21:50:32 +02:00
Paul Chote
2c5fce5e3c Add missing TraitLocation to OrderEffects. 2021-08-02 21:50:32 +02:00
Paul Chote
7a93ff3258 Add support for TS-style tinted target flashes. 2021-08-02 21:50:32 +02:00
Paul Chote
9291263609 Fix indentation in OrderEffects. 2021-08-02 21:50:32 +02:00
Matthias Mailänder
f4a5878a53 Fix veinholes on Sunstroke. 2021-08-01 14:56:53 -05:00
Paul Chote
1ecb3bf99a Don't add empty Translations nodes to maps. 2021-07-31 13:14:46 +02:00
reaperrr
c88994a0dd Fix unarmed civilians being selected as combat units 2021-07-30 09:03:29 -05:00
fruehstueck
29b0999da3 Add GDI 8b 2021-07-30 09:03:29 -05:00
Paul Chote
b08117dc93 Don't report "Primary Building Selected" when nothing changes. 2021-07-29 16:19:53 +02:00
Paul Chote
99322cee8f Set the closest production to Primary when force-targeting rallypoints. 2021-07-29 16:19:53 +02:00
Vapre
e201e410f4 PathGraph, skip closed cells early.
In path finding GetConnections considers connections to already closed cells and calculates the cost for them. The caller afterwards ignores them. These are 15% of all connections.
2021-07-27 14:49:22 +02:00
Paul Chote
24f64ae1a8 Fix an edge case where the wrong sheet may be mapped to a depth sprite. 2021-07-25 19:20:19 +02:00
Ivaylo Draganov
1f3f489328 Show map preview tooltip only if needed 2021-07-25 00:25:08 +01:00
Ivaylo Draganov
31056d4253 Add tooltip to overflowing map title in server browser 2021-07-25 00:25:08 +01:00
Paul Chote
70892a6661 Change DrawSprite calls to provide scales instead of sizes.
This allows us to remove a hacky workaround for calculating
depth offsets when sprites have size.Z == 0.
2021-07-25 00:32:17 +02:00
Paul Chote
8e94e1d5ec Rework WidgetUtil sprite rendering helpers. 2021-07-25 00:32:17 +02:00
Vapre
2e6f444285 Map.Contains check grid type once. 2021-07-24 15:34:56 +02:00
abcdefg30
dcaa658678 Remove an outdated reference to ConditionManager 2021-07-23 11:04:07 -05:00
Hang
249bc4abc8 Fix the hide-map sequence of the d2k crate-effects
Frame 4218 belongs to explosion/nuke sequence, which should not part of crate-effects/hide-map sequence
2021-07-22 23:13:16 +02:00
Hang
757ca0561f Update misc.yaml
Typo Lenght -> Length
2021-07-21 13:36:25 +02:00
Vapre
001134ce59 Do not start DiscordService client if disabled. 2021-07-21 12:13:21 +02:00
Paul Chote
f8ed768e39 Change makefile/packaging "RUNTIME=dotnet" to "RUNTIME=net5". 2021-07-18 20:57:22 +02:00
Chris Harris
1c6ca394c1 Fix duplicate ActorIDs 2021-07-17 23:27:25 +02:00
Vapre
627fd4e68b CellLayer.Index inline CPos.ToMPos. 2021-07-16 09:51:55 +02:00
Paul Chote
7bfe83cce7 Fix WithIdleOverlay PlayerPalette editor rendering. 2021-07-15 12:05:09 +02:00
Matthias Mailänder
d169210531 Display the production overlay only where the unit will exit. 2021-07-14 19:06:22 -05:00
Matthias Mailänder
902006bf53 Fix editor losing copy after each paste. 2021-07-13 21:01:33 -05:00
Mustafa Alperen Seki
dcb70d12e3 Make Harvester conditional. 2021-07-12 15:41:04 +02:00
Matthias Mailänder
cd90c70cdf Fix a null reference exception. 2021-07-12 15:20:34 +02:00
Matthias Mailänder
f1f5df3749 Fix .shp icons being misdetected as .wsa animations. 2021-07-12 11:49:06 +02:00
Matthias Mailänder
b2f18ad0ad Add support for .ogg files. 2021-07-12 00:35:16 +02:00
Matthias Mailänder
af0b7621d2 Minor fixes. 2021-07-12 00:35:16 +02:00
Matthias Mailänder
2bd7869059 Fix haystacks being selectable buildings. 2021-07-12 00:15:38 +02:00
Matthias Mailänder
e1816a6da0 Fix a crash when opening map Chernobyl with the editor. 2021-07-11 16:38:27 +01:00
teinarss
f3777a25e6 Refactor send and receive Orders loop 2021-07-10 23:35:47 +02:00
teinarss
5e1468facb Fix NRE with ConnectionStateChanged 2021-07-08 01:12:05 +02:00
Matthias Mailänder
3e6e5a83f3 I feel like I am back. 2021-07-07 15:20:47 +02:00
Paul Chote
25c095619a Parse Mp3 length from metadata tags. 2021-07-07 15:20:47 +02:00
Matthias Mailänder
407372268d Add support for .mp3 files. 2021-07-07 15:20:47 +02:00
Paul Chote
aa798c252b Remove "X has joined the game." notification when starting a Skirmish. 2021-07-06 13:12:51 +02:00
Ivaylo Draganov
9c658ad36b Increase the opacity of panel-gray-gdi 2021-07-06 10:13:43 +02:00
Ivaylo Draganov
2923077b3d Style TD in-game chat and performance graph with faction colors 2021-07-06 10:13:43 +02:00
Ivaylo Draganov
cbdf6c3747 Add faction suffix support to text fields and scroll panels 2021-07-06 10:13:43 +02:00
Ivaylo Draganov
9687988976 Use pattern matching syntax in AddFactionSuffixLogic 2021-07-06 10:13:43 +02:00
Ivaylo Draganov
64e76e1a90 Make text fields yield keyboard focus on "Esc" in a consistent way
- search fields clear the input and yield if empty
- chat field and actor edit field yield without clearing
2021-07-04 23:37:29 +02:00
reaperrr
df8295fa2c Make aircraft turn speed scale with speed modifiers 2021-07-04 21:26:45 +01:00
reaperrr
0ac277a88d Improve Aircraft TurnSpeed getters readability 2021-07-04 21:26:45 +01:00
reaperrr
5a548d6acc Introduce IdleMovementSpeed
That actually factors in speed modifiers and trait pause/disable.
2021-07-04 21:26:45 +01:00
reaperrr
1262a9c6c9 Minor FlyIdle perf optimization
None of these prerequisites change on the fly,
so cache the result in the activity ctor.
2021-07-04 21:26:45 +01:00
reaperrr
5ecb3eec16 Fix IdleTurnSpeed ignoring trait pause/disable 2021-07-04 21:26:45 +01:00
Ivaylo Draganov
2ea6bfba7b Allow the player to toggle the display of UI Feedback chat pool 2021-07-04 14:02:58 +01:00
Ivaylo Draganov
6af354ff99 Split chat lines into pools
- Add a common class for passing around chat lines
- Add wrapper methods for adding chat lines
- Combine repeated chat lines in the display widget
2021-07-04 14:02:58 +01:00
abc013
0a02bd524a Update DestroyResourceWarhead to support Resource type and amount options. 2021-07-04 11:13:05 +01:00
Paul Chote
f9b058d36b Add click sounds to color widgets. 2021-07-03 12:18:25 -05:00
Paul Chote
bc51461427 Make sure the hue returned by Color.RgbToHsv is positive. 2021-07-03 12:18:25 -05:00
Paul Chote
fca12fd707 Validate custom color picker colors. 2021-07-03 12:18:25 -05:00
teinarss
1f1373509e Refactor server orders 2021-07-03 14:55:03 +01:00
Abdurrahmaan Iqbal
a8900d9860 Rebind chat hotkeys to prevent Tab changing chat mode
Enter/Shift+Enter now toggle team/all chat respectively and Shift+Tab switches chat mode instead of Tab
2021-07-03 14:49:03 +01:00
Abdurrahmaan Iqbal
6967c1fff3 Pass KeyInput to OnKey functions 2021-07-03 14:49:03 +01:00
abc013
2742985520 Prevent saving and starting a map when max player count is exceeded. 2021-07-03 08:25:16 -05:00
Smittytron
347a09e6cf Switch remaining RA missions to Normal default difficulty 2021-07-03 00:25:53 +02:00
Smittytron
e1a28078bb Fix difficulty dropdown for RA mission Sarin Gas 2: Down Under 2021-07-03 00:25:53 +02:00
Smittytron
27562ab88f Fix and standardize TD mission difficulty dropdowns 2021-07-03 00:25:53 +02:00
reaperrr
9371cecc00 Render disabled targetable positions in gray
Instead of not rendering them at all.
Also moved their debug overlay to HitShape.
2021-07-02 10:58:29 +01:00
reaperrr
feba9f2b9f Make orientation caching in HitShape readonly
HitShape Requires<BodyOrientation> anyway.
2021-07-02 10:58:29 +01:00
reaperrr
6d55161043 Show disabled HitShapes in gray
Instead of disabling their debug overlay entirely.
2021-07-02 10:58:29 +01:00
Andre Mohren
7356f2506b Moved flashimage to world trait. 2021-07-01 16:38:51 +02:00
Ivaylo Draganov
ad4425d11e Use cached transforms for images in widgets 2021-07-01 14:21:22 +01:00
Matthias Mailänder
c8ab409d38 Allow mod code to access the buildable terrain overlay. 2021-07-01 12:41:06 +01:00
Smittytron
9a587d2aaa Standardize player naming in Ant01 2021-06-30 23:11:10 +02:00
Smittytron
bcbd2418b4 Adjust hard difficulty in Ant01 2021-06-30 23:11:10 +02:00
Andre Mohren
8a776c7138 32bpp bullets no longer crash without palette. 2021-06-30 22:58:03 +02:00
Andre Mohren
6810469634 Updated copyright years. 2021-06-29 18:33:21 -05:00
VonNah
5a7a09a6a7 Fixed the crumble-overlay of all the buildings. 2021-06-27 15:29:57 +02:00
Leo512bit
e60e7beeef Slower damaged units 2021-06-26 09:24:30 -05:00
Ivaylo Draganov
9313638997 Fix handicap dropdown disabled state 2021-06-25 09:25:17 -05:00
Ivaylo Draganov
1af2ab566e Fix player handicap erroneously set to player team 2021-06-25 09:25:17 -05:00
Andre Mohren
de6b8b6a74 32bpp terrain does not need a palette. 2021-06-25 10:38:53 +02:00
Andre Mohren
62207313a0 32bpp assets dont need a palette to be defined. 2021-06-25 10:33:54 +02:00
teinarss
b3b10729cd Remove ServerExternalMod from OrderManager 2021-06-12 12:50:51 +02:00
teinarss
91f99eeb4c Cleanup in Connect 2021-06-12 12:50:51 +02:00
teinarss
a1b3450b47 Remove Password and Endpoint from OrderManager 2021-06-12 12:50:51 +02:00
Matthias Mailänder
da4bf7f191 Document and test the building placement preview for Dune 2000. 2021-06-12 12:43:03 +02:00
tjk-ws
a893cf9cb6 Unhardcode weapon ammo consumption
fix gh actions
2021-06-11 10:21:24 -05:00
Ivaylo Draganov
9fa5dcc055 Remove unused method in WidgetUtils 2021-06-11 10:16:34 -05:00
abcdefg30
fe146cb77a Remove superfluous Buildable traits from dummy helper actors 2021-06-10 19:22:46 -05:00
Vapre
fc49d6943a OrderIO performance improvement
OrderIO, ToOrderList, skip if frame is empty,
which occurs often each client each frame.
2021-06-09 23:31:27 +02:00
Matthias Mailänder
44b2dda585 Add an editor overlay for unbuildable terrain. 2021-06-06 21:14:44 +02:00
Matthias Mailänder
3980e4fa90 Use consistent and easy to read debug command names.
Reorganize dev cheat command handling.
2021-06-06 18:40:48 +02:00
darkademic
ca2bef3cd1 Updated TD unit speeds to account for move jumpy-ness fix. 2021-06-04 23:20:13 +02:00
darkademic
cbd82c7204 Updated RA unit speeds to account for move jumpy-ness fix. 2021-06-04 23:17:41 +02:00
teinarss
82115c6bf7 Move Text handling to its own class 2021-06-04 21:47:39 +02:00
Paul Chote
2a26ddc622 Replace server Select loop with individual client threads.
This guarantees that any unexpected blocking calls due to network
issues cannot stall the main server thread.
2021-05-30 14:37:25 +02:00
Paul Chote
6535411744 Move SendData to Connection. 2021-05-30 14:37:25 +02:00
Paul Chote
7c02b4d264 Add Connection.EndPoint property. 2021-05-30 14:37:25 +02:00
Paul Chote
7e79e69eae Remove public mutable state from Connection. 2021-05-30 14:37:25 +02:00
Paul Chote
dacacdf130 Remove global fallback to software cursors on error.
We now only fall back for the specific cursors that failed.
2021-05-30 12:01:44 +02:00
abc013
8fede9d6ba Add ValidStances checks to BlocksProjectiles and Gate. 2021-05-27 21:37:37 +02:00
teinarss
d10c592987 Fix crash in OrderManager 2021-05-23 12:44:18 +01:00
reaperrr
acccb01c76 Fix Move regression
If progress == Distance, we must not move again on the same tick,
but still 'return true' to avoid losing a tick in the case
when this is the last Move tick followed by a different activity
(or a new queued Move, for example via waypoints).
2021-05-23 10:49:40 +02:00
Matthias Mailänder
6876fe45e1 Use nameof for additional robustness in trait documentation. 2021-05-22 23:22:31 +01:00
Matthias Mailänder
52a4b5acd7 Fix documentation about GeneratesShroud to CreatesShroud rename. 2021-05-22 23:22:31 +01:00
Smittytron
92b6401360 Remove Thief from Exodus 2021-05-22 23:29:42 +02:00
Smittytron
ea24a15011 Set mission default difficulties to normal 2021-05-22 23:29:42 +02:00
Smittytron
243dc965c0 Move IdleHunt function to campaign-global.lua 2021-05-22 23:29:42 +02:00
Smittytron
f1f9098109 Move TRUK prereq overrides to campaign-rules.yaml 2021-05-22 23:29:42 +02:00
Smittytron
79b3f9bf06 Use proper title case conventions in mission names 2021-05-22 23:29:42 +02:00
Smittytron
dff245a9ce Fix tile error in Soviet09 2021-05-22 23:29:42 +02:00
Smittytron
91f9fa0f1f Add Ant 03 2021-05-22 13:42:09 +02:00
Smittytron
6f919fc232 Change anthill to correct impassable tiles 2021-05-22 13:42:09 +02:00
Smittytron
ce277481c3 Add Aftermath mission In the Nick of Time 2021-05-22 13:31:40 +02:00
Smittytron
d2039a14e1 Add Counterstrike mission Fall of Greece 2: Evacuation 2021-05-22 13:27:46 +02:00
Paul Chote
a27ef16911 Add EngineRootPath csproj property to simplify SDK inheritance. 2021-05-22 11:22:26 +02:00
Paul Chote
dc3dc7df73 Fix map-player bots not working on dedicated servers.
The host is almost never going to be client index 0
on public dedicated servers. Set to the actual host
client ID instead.
2021-05-22 11:18:45 +02:00
abcdefg30
872adbec0a Remove crate crushing from visceroids in TD and TS 2021-05-17 09:57:43 +02:00
Paul Chote
c64cfea179 Allow mods to downscale framebuffer resolution for large world viewports. 2021-05-16 14:22:52 +02:00
Paul Chote
84dff779ac Work around rendering glitches with non-unity pixel scales. 2021-05-16 14:22:52 +02:00
Paul Chote
0d4b81fe6f Set world framebuffer size based on minimum zoom.
This avoids reallocating buffers each time the player changes zoom level.
2021-05-16 14:22:52 +02:00
Paul Chote
0735345674 Set world framebuffer size based on minimum zoom.
This avoids reallocating buffers each time the player changes zoom level.
2021-05-16 14:10:32 +02:00
Paul Chote
bb15bd20c0 Add support for 16 bit floating point textures. 2021-05-16 14:10:32 +02:00
Paul Chote
95f5d162ef Increase SheetCount back to 8.
This was previously decreased to support legacy GPUs
that only supported 8 texture image units and we need
to reserve one of these for the palette texture.

OpenGL 3.X mandates a minimum of 16 (and most most GL2
cards also supported it) so we can now safely increase
this limit.
2021-05-16 14:10:32 +02:00
Smittytron
7967a462a1 Add override to prevent Orca construction in Nod07c 2021-05-16 13:27:39 +02:00
Smittytron
e225785744 Remove Medium Tank overrides from Nod08a and Nod08b 2021-05-16 13:27:39 +02:00
Smittytron
46dcdfa58e Remove Light Tank override from GDI05a 2021-05-16 13:27:39 +02:00
Smittytron
f8debe340f Add building override for Chem Trooper in GDI05b 2021-05-16 13:27:39 +02:00
Smittytron
83f99727a7 Change E3 to Infantry.Nod in GDI03 2021-05-16 13:27:39 +02:00
Smittytron
cbf84f62d4 Use correct overrides in GDI02 2021-05-16 13:27:39 +02:00
VonNah
fa68954dda Fixed incorrect Harkonnen Devastator warhead impact sound. 2021-05-15 23:07:25 +02:00
Paul Chote
98caae106f Move Palette traits to their own directory.
Also adds missing TraitLocation definitions.
2021-05-15 15:29:46 +02:00
Paul Chote
3bc42543fa Decouple color picker palette definitions to their own trait. 2021-05-15 15:29:46 +02:00
Paul Chote
57d955ec72 Change Color.ToAhsv to tuple syntax. 2021-05-15 15:29:46 +02:00
Paul Chote
96e333a30e Fix TS colorpicker turret facing. 2021-05-15 15:29:46 +02:00
Paul Chote
560f1a6466 Restrict player color choices to the hue-saturation plane. 2021-05-15 15:29:46 +02:00
Paul Chote
4042d5b179 Preserve original brightness when remapping player colors. 2021-05-15 15:29:46 +02:00
Paul Chote
9d62ce214c Move color picker actor type from metrics to ColorPickerManager. 2021-05-15 15:29:46 +02:00
Paul Chote
f65de2dd43 Merge ColorPreviewManagerWidget into ColorPickerManager. 2021-05-15 15:29:46 +02:00
Paul Chote
7b58f03f1c Move ColorValidator logic into a new ColorPickerManager trait. 2021-05-15 15:29:46 +02:00
oldnaari
52577c1de9 Fix Atreides silo damaged sequence
Fix the Atreides silo rendering as full silo instead of damaged silo when damaged
2021-05-14 13:43:13 +02:00
reaperrr
fc3f200357 Replace F in OrderManager 2021-05-09 15:00:07 +01:00
Paul Chote
d89f14dcbc Limit resource center queries to 50 maps at a time. 2021-05-09 15:37:30 +02:00
teinarss
96b8273916 Remove FrameData and update OrderManager 2021-05-09 15:08:48 +02:00
Smittytron
eda79d8626 Add Soviet10 2021-05-08 23:57:07 +02:00
teinarss
0c50057220 Update KickSpectators clientCount to be passed as int 2021-05-08 22:20:59 +02:00
teinarss
10676be377 Replace F extension with string interpolation 2021-05-08 22:20:59 +02:00
teinarss
1385aca783 Move ConnectionTarget to its own file 2021-05-08 20:41:40 +02:00
abcdefg30
121959efe4 Update the Desc of AttackBase.FacingTolerance 2021-05-04 10:44:23 -05:00
Paul Chote
8d2ec78713 Replace TerrainType.CustomCursor with Mobile.TerrainCursors. 2021-05-04 11:56:23 +02:00
Paul Chote
01371f2c65 Expose TransformsIntoAircraft move cursor to yaml. 2021-05-04 11:56:23 +02:00
Paul Chote
b344bba59a Expose Aircraft move cursor to yaml. 2021-05-04 11:56:23 +02:00
Paul Chote
a6d393f19b Fix a crash when using legacy GL. 2021-05-04 09:46:31 +02:00
Matthias Mailänder
7e19d6a205 Fix Player and Actor Properties / Commands rendering. 2021-05-04 01:13:18 +02:00
reaperrr
92a9f1e234 Split expansion mission categories in Allied and Soviet
It's not always apparent from the title which faction a mission
belongs to, and players may want to know beforehand
instead of having to guess.
2021-05-03 19:22:37 +01:00
Greg Solo
c39f7e521a Use shell param instead of duplicating the flow 2021-05-03 19:17:03 +01:00
Greg Solo
c9b9efe745 Setup debug make to support vscode debug 2021-05-03 19:17:03 +01:00
Vapre
a5a371f1ff GameSettings, EnableDiscordService. 2021-05-03 11:42:57 +01:00
Matthias Mailänder
b491e892ff Lint check the harvest cursor. 2021-05-03 10:58:35 +01:00
reaperrr
771932354b Remove unused leftovers from GDI08a
Those became redundant due to using global MoveAndHunt
in SendWaves.
2021-05-01 20:48:28 +02:00
reaperrr
89f270b67c GDI08a typo fix 2021-05-01 20:48:28 +02:00
abcdefg30
3276373745 Pause rendering when the window is minimized 2021-04-26 21:56:20 +01:00
teinarss
5667081764 Fix log should be disposed correctly 2021-04-26 21:50:26 +01:00
teinarss
b23d533006 Update Log to wait for items in worker thread 2021-04-26 21:50:26 +01:00
Matthias Mailänder
1f01d0b6b1 Add a Fluent based translation system. 2021-04-24 16:49:17 +02:00
reaperrr
f1a9a5180d Use real (milli)seconds for notifications
Allows for more fine-grained control, while
adding independence from gamespeed and getting
rid of magic * 25 multiplications.
2021-04-21 19:34:16 +02:00
Paul Chote
bb8a634ba8 Fix map installation. 2021-04-21 18:57:44 +02:00
Paul Chote
f9294f0e9e Enable dedicated server lint checks. 2021-04-21 18:57:44 +02:00
Paul Chote
9770967b04 Optimize MapPreview rule loading. 2021-04-21 18:57:44 +02:00
Paul Chote
0bbb32e8ac Rework MapPreview custom rule handling.
The previous asynchronous approach did not work particularly well,
leading to large janks when switching to custom maps or opening the
mission browser.

This commit introduces two key changes:

 * Rule loading for WorldActorInfo and PlayerActorInfo is made
   synchronous, in preparation for the next commit which will
   significantly optimize this path.
 * The full ruleset loading, which is required for map validation,
   is moved to the server-side and managed by a new ServerMapStatusCache.
   The previous syntax check is expanded to include the ability to run
   lint tests.
2021-04-21 18:57:44 +02:00
Paul Chote
61d64287e1 Move LoadMaps after InitializeFonts.
This allows text to be displayed earlier in the loading screen.
2021-04-21 18:57:44 +02:00
Paul Chote
abee274f88 Remove direct access to MapPreview.Rules. 2021-04-21 18:57:44 +02:00
reaperrr
53e6d974f0 Change Crate.Lifetime from 'seconds' to ticks
As far as I could tell, this was the last place that still
used 'seconds' instead of ticks, apart from
some sound notification intervals (which are better
converted to real [milli]seconds).

Also renamed ScaredyCat.PanicLength to PanicDuration for
consistency and easier finding.
2021-04-19 20:03:08 +02:00
reaperrr
e3fd54e147 Replace * 25 in internal tick defaults with actual total
While the idea behind it is understandable,
this was inconsistent with the bulk of other defaults.
2021-04-19 20:03:08 +02:00
Vapre
52d39db84a Fix possible endless loop in replay recorder while opening save file. 2021-04-15 18:10:45 +02:00
reaperrr
27ddae3df9 Fix Move jumpy-ness on MovePart transitions
There were 2 issues at work here, both when progress
would overshoot Distance (which is the usual case,
rather than the exception):
- the overshot progress was passed on by MoveFirstHalf, however
  OnComplete would make the next MovePart start ticking the
  same tick on which the old MovePart reached Distance,
  but move by carryoverProgress +(!!!) terrain speed instead of moving
  by just the left-over carryoverProgress.
- MoveSecondHalf would not pass any overshot progress to the
  next MoveFirstHalf queued by parent Move, leading to
  the next MoveFirstHalf performing a full-speed move the same tick
  MoveSecondHalf finished its last move.
2021-04-15 18:03:42 +02:00
reaperrr
4c7e3d8f3a Rename fraction to distance/progress in MovePart 2021-04-15 18:03:42 +02:00
reaperrr
646495fc5f Simplify MovePart code
InnerActivity and UpdateCenterLocation made this
overly complex and hard to read & debug.

This also fixes a bug that would make an outdated
facing being passed during OnComplete (because
InnerActivity was cached before UpdateCenterLocation
could set the correct final facing).
2021-04-15 18:03:42 +02:00
Mustafa Alperen Seki
a9661a233a Fix Lua error in Har8 when Merc HFac is captured after Palace is killed. 2021-04-13 21:39:24 +02:00
CrazyAlex25
2d05e10819 Modify build properties 2021-04-12 00:44:17 +02:00
Matthias Mailänder
5a0bcc01a6 Add a lint check for trait placement on hardcoded actor names. 2021-04-11 20:20:00 +02:00
Smittytron
0d3c624bbc Standardize usage of AddObjective in RA missions 2021-04-11 12:26:14 +02:00
Smittytron
26fbcf6076 Change fence owner in Soviet01 2021-04-11 12:26:14 +02:00
Smittytron
418fca3d9e Move OnAnyDamaged function to campaign-global.lua 2021-04-11 12:26:14 +02:00
Smittytron
dd366f8cf9 Rename top-o-the-world script file 2021-04-11 12:26:14 +02:00
Smittytron
6b63e88056 Move crate lifetime overrides to campaign-rules.yaml 2021-04-11 12:26:14 +02:00
Smittytron
a96e2fb588 Add Soviet11b 2021-04-10 23:00:31 +02:00
Smittytron
ca2f966c3b Add Soviet 11a 2021-04-10 22:26:40 +02:00
reaperrr
e161d9daa7 Misc TraitDictionary style fixes 2021-04-10 15:59:24 +02:00
reaperrr
aa834db1e3 Make perf.log output for ticking things opt-in
Both writing to perf.log frequently as well as GetTimestamp
aren't free and hurt performance particularly on slower systems
(which can have notably higher output to perf.log, further
amplifying the problem).
Therefore we make simulation perf logging opt-in.

Additionally, logging of the current tick and tick type
(local/net) is removed from debug.log, and some
remnant debug logging for kills and pips is removed
to keep performance-sensitive logging limited to
perf.log.
2021-04-10 15:59:24 +02:00
Paul Chote
a1df91b665 Allow BuildingInfluence to track overlapping buildings in the same cell. 2021-04-10 14:28:31 +02:00
Paul Chote
bc286b78bf Add AnyBuildingAt method to BuildingInfluence. 2021-04-10 14:28:31 +02:00
Paul Chote
19c7e14393 Simplify BuildingInfo.IsCloseEnoughToBase adjacency checks. 2021-04-10 14:28:31 +02:00
reaperrr
1a9dfc0893 Refactor GameSpeed setting
*Remove internal GameSpeed defaults
 Enforce setting values explicitly all the time
 Require definition of a DefaultSpeed

*Remove Global.Timestep default

*Remove the hacky Timestep/OrderLatency setting via LobbyInfo

*Fix shellmaps ignoring mod-defined gamespeeds

*Make DateTimeGlobal use the MapOptions gamespeed
2021-04-09 22:58:14 +01:00
Matthias Mailänder
fe129956bb Make the resource layer optional in the map editor. 2021-04-07 12:19:55 +01:00
reaperrr
eec7de4646 Fix RA infantry shadow being visible when parachuting 2021-04-04 17:06:03 -05:00
teinarss
605181efe4 Update Log to use worker thread 2021-04-03 22:53:30 +01:00
Paul Chote
fc0ed75a94 Fix Firestorm asset installation. 2021-04-03 15:12:18 -05:00
Smittytron
d95c4146e2 Rework objectives in Situation Critical 2021-04-03 17:58:00 +01:00
Vapre
38f0d50648 Minor optimization. Save a call to IsAlliedWith if not disguised. #18791. 2021-04-03 17:12:12 +01:00
reaperrr
808d8e63bc Add GiveUnitCrateActions for ships to RA 2021-04-03 16:22:27 +01:00
reaperrr
55967035d9 Update RA wcrate.shp
Now uses the original colors again.
Also added back a frame without shadow (for
parachuting).
2021-04-03 16:22:27 +01:00
reaperrr
13a101f11f Add a frame with shadow to both TD crate types
And remove the crate.shp in return
(which was just scrate.shp with shadow).

Also fixes WCRATE actor to actually use the correct .shp.
2021-04-03 16:22:27 +01:00
Paul Chote
35a0a3cf90 Remove rcedit after building windows packages. 2021-04-03 12:19:41 +02:00
Smittytron
08d8f5d8d9 Swap c1 and c7 out of CivilianEvacuees in Monster Tank Madness 2021-04-03 11:38:33 +02:00
Smittytron
f7c9eccf7a Add Selectable Class to technicians 2021-04-03 11:38:33 +02:00
teinarss
6ba9e64380 Rename modifiablePalettes to mutablePalettes in HardwarePalette 2021-04-03 11:33:31 +02:00
teinarss
8b0a3ea680 Remove our own impl of ReadOnlyList and update usages 2021-04-03 11:33:31 +02:00
teinarss
e12ff2c59d Remove our own ReadOnlyDictionary and update usages 2021-04-03 11:33:31 +02:00
Smittytron
afbdb395b2 Add SelectableSupportUnit to Thief 2021-04-03 11:24:00 +02:00
teinarss
3d381e6e32 Make SpriteFont.Measure take zero allocations 2021-04-03 11:22:45 +02:00
Matthias Mailänder
a02737107e Add a .wsa file reader. 2021-04-03 11:19:06 +02:00
Matthias Mailänder
590ab88c45 Don't prefer braces (for one liners). 2021-04-03 10:53:08 +02:00
Patrick
7a7c07e9c4 fix AreaBeam + GrantExternalConditionWarhead bug 2021-04-02 13:05:20 +02:00
reaperrr
40aafe586d Move Game.Timestep to Widget
Game.Timestep wasn't used for anything other than
UI anymore anyway, moving it makes this more clear.
2021-04-02 12:00:42 +01:00
abcdefg30
6b93f955a4 Fix a crash in LevelUpCrateAction 2021-04-02 11:57:44 +01:00
reaperrr
75a3bb4f0b CellIsEvaluating perf optimization
If an actor has Mobile, it implements IOccupySpace
so we can use OccupiesSpace to save a trait look-up.
2021-03-27 22:29:13 +00:00
reaperrr
441e18b898 Remove PathHash
This is 9(!) years old and we haven't had
pathfinding-related desyncs in quite a while.

We can still bring this back later if we ever
need it again.
2021-03-27 22:29:13 +00:00
reaperrr
b8e64df4b1 Remove SmokeTrailWhenDamaged
One of the most outdated and limited traits remaining,
which can do nothing LeavesTrails doesn't cover by now.
2021-03-27 18:42:57 +00:00
Matthias Mailänder
d15e7f76fc Port back to Mono.Nat and make discovery async. 2021-03-27 18:36:12 +00:00
Andre Mohren
3f510b6d93 Make ISpriteLoader aware of the source file name. 2021-03-27 17:36:59 +01:00
teinarss
d60c05eff3 Change to use pattern matching 2021-03-27 17:29:20 +01:00
Paul Chote
7c0e4b25ae Specify interaction bounds relative to the mod tile size. 2021-03-27 16:31:50 +01:00
Smittytron
852241d98e Add Allies05c 2021-03-27 14:46:11 +01:00
Smittytron
8deba81214 Add Allies05b 2021-03-27 13:55:27 +01:00
Smittytron
887a093f46 Add Counterstrike mission Siberian Conflict 3: Wasteland 2021-03-27 13:32:40 +01:00
Smittytron
4778dba36d Fix whitespace issues in RA mission browser 2021-03-27 13:25:22 +01:00
Smittytron
438b2240f4 Add AI infantry production to Soviet02a 2021-03-27 13:25:22 +01:00
Smittytron
58a6333834 Add AI infantry production to Soviet02b 2021-03-27 13:25:22 +01:00
Smittytron
f8feec685f Use correct sprite for Moneycrate 2021-03-27 13:25:22 +01:00
Smittytron
79404cd397 Link campaign-global.lua to remaining missions 2021-03-27 13:25:22 +01:00
Smittytron
6a6c5848c2 Remove fake building overrides from Survival01 2021-03-27 13:25:22 +01:00
Smittytron
420c8ebc4c Remove fake building overrides from Allies02 2021-03-27 13:25:22 +01:00
Matthias Mailänder
c0ea95ca46 Update itch user name. 2021-03-27 11:34:03 +00:00
Matthias Mailänder
1a8d971145 Fix a syntax error. 2021-03-27 11:07:35 +00:00
Paul Chote
7afcb9d757 Fix TS infantry player color. 2021-03-27 11:06:12 +01:00
Smittytron
8a5c0736f5 Remove CivilianKilled notification default from TD 2021-03-27 10:58:29 +01:00
Castle
12b6bb9448 Allow BlendMode of RgbaColorRenderer to be changed 2021-03-23 17:07:20 +01:00
reaperrr
e2a6b55d44 Move up Undamaged check in DamageState
A mere int comparison is obviously cheaper than
a comparison between two multiplications,
so pulling this above the checks of other damage states
allows us to bail early for undamaged actors.
2021-03-21 17:37:42 +01:00
reaperrr
c240c0a24b Only apply non-100 damage modifiers in InflictDamage
Profiling has shown that filtering them out early is cheaper
than applying those percentage modifiers anyway.

Additionally, profiling shows foreach loops to be cheaper
than LINQ here as well.
2021-03-21 17:37:42 +01:00
reaperrr
2528b79610 Fix D2k DamagesConcrete overrides 2021-03-21 11:09:41 +00:00
Matthias Mailänder
e13fd4816e Extract the directory if the registry value points to a filename. 2021-03-20 18:42:02 +01:00
Andre Mohren
1f6e0f582a Fixed aiming not propertly stopped. 2021-03-20 18:33:04 +01:00
Matthias Mailänder
bbbed49f82 Add a lint check for cursor definitions. 2021-03-20 17:37:16 +01:00
Matthias Mailänder
cefb2e7cc6 Remove unused trait lookup. 2021-03-20 17:37:16 +01:00
Paul Chote
470bc4e092 Polish deployed mobile sensor array:
- Force temperate palette to avoid glitches on snow maps
- Add missing active animation
2021-03-20 17:15:56 +01:00
Paul Chote
08c7c80bb7 Render building lights as their own tint-ignoring animations. 2021-03-20 17:15:56 +01:00
Paul Chote
5832ec76d4 Render integrated muzzle flashes as their own tint-ignoring animations. 2021-03-20 17:15:56 +01:00
Paul Chote
594e5b80d7 Add WithMakeOverlay trait. 2021-03-20 17:15:56 +01:00
Paul Chote
14a434975a Add facing support to WithAttackOverlay. 2021-03-20 17:15:56 +01:00
Paul Chote
6854b23bcc Add custom palette support to WithSpriteBody. 2021-03-20 17:15:56 +01:00
Paul Chote
0c52d275fa Add TransparentIndex to PaletteFromFile. 2021-03-20 17:15:56 +01:00
Paul Chote
e63b9b4986 Add condition support to ActorLostNotification. 2021-03-20 17:07:29 +01:00
Paul Chote
0e270bec56 Restrict Chronoshiftable trait to Mobile and Husk actors. 2021-03-20 17:04:42 +01:00
Andre Mohren
f5f06b86ad ExplosionOnDamageTransition now Conditional. 2021-03-20 17:02:47 +01:00
Paul Chote
fcc3008b00 Implement proper TS Tiberium and Vein logic. 2021-03-20 16:45:41 +01:00
Paul Chote
0bdd46451e Overhaul resource layer logic:
* ResourceType trait has been removed.
* Simulation-related data is now defined on the
  ResourceLayer (which mods can subclass/replace).
* Support non-money resources by moving the resource
  values to the PlayerResources trait.
* Allow mods to disable the neighbour density override
  and instead always use the map-defined densities.
* Allow mods to define their own resource placement
  logic (e.g. allow resources on slopes) by subclassing
  (Editor)ResourceLayer.
* Improve ability to subclass/override ResourceRenderer
  by exposing more virtual methods.
2021-03-20 16:45:41 +01:00
Paul Chote
c35e9fb016 Remove custom D2k spice variant logic and improve editor cursor sprite. 2021-03-20 16:45:41 +01:00
Paul Chote
80e92849da Replace ResourceType with strings in interfaces/public methods. 2021-03-20 16:45:41 +01:00
Paul Chote
dcd8eccee4 Replace ResourceLayer references with IResourceLayer in traits/warheads. 2021-03-20 16:45:41 +01:00
Paul Chote
5adcbe4c78 Use IResourceLayer for editor resources. 2021-03-20 16:45:41 +01:00
Paul Chote
0b93556c06 Add resource updating methods to IResourceLayer. 2021-03-20 16:45:41 +01:00
Paul Chote
7e9d291223 Add IResourceRenderer interface. 2021-03-20 16:45:41 +01:00
Paul Chote
1dc26a9b8e Pass resource type and count to IAcceptResources instead of the value. 2021-03-20 16:45:41 +01:00
Paul Chote
3dbc6400a6 Use IResourceLayer in BaseBuilderBotModule. 2021-03-20 16:45:41 +01:00
reaperrr
adf1aeb06a Add support for pool-specific ammo pips decoration 2021-03-20 15:31:41 +01:00
Vapre
be224934a5 FieldLoader, use dictionary for GetValue to make it twice as fast. 2021-03-17 18:18:53 +01:00
teinarss
6b74093c04 Add readonly to structs 2021-03-14 15:17:57 +01:00
reaperrr
65c796dec7 Use Mobile.Pathfinder to reduce trait look-ups 2021-03-14 12:28:54 +00:00
reaperrr
503e706d45 Check for IsReloading in HasAnyValidWeapons
For Attack*.CanAttack().

Allows us to drop the additional check for armaments with ammo.
2021-03-14 12:28:54 +00:00
reaperrr
a1f974bd40 Save Mobile trait look-up in AttackBase 2021-03-14 12:28:54 +00:00
Unrud
11171ff649 Makefile: Fail target version when not a git repository 2021-03-13 15:57:17 +00:00
Paul Chote
3ff1888d74 Fail Situation Critical if Volkov is killed before entering the world. 2021-03-13 15:55:24 +00:00
Paul Chote
cbea08e1fe Fix non-relative path handling in install logic. 2021-03-13 15:56:38 +01:00
Paul Chote
b622afd7fd Fix self-contained packaging. 2021-03-13 13:12:31 +01:00
Paul Chote
7694d0842b Fix two outdated comments. 2021-03-12 21:52:16 +01:00
Paul Chote
a90e6940ab Add sequence scale and alpha support to ShroudRenderer. 2021-03-12 21:52:16 +01:00
Paul Chote
96c3825b6a Add alpha support to TerrainSpriteLayer. 2021-03-12 21:52:16 +01:00
Paul Chote
a6467cb515 Fix Nullable type handling in Lua docs. 2021-03-10 17:46:44 +01:00
Paul Chote
d52ba83f96 Replace terniary null checks with coalescing. 2021-03-08 18:11:25 +01:00
reaperrr
2473b8763b Rename methods/activities with Visual in them
While they may be only 'visual' in terms of influence/cell grid,
they all do update CenterPosition, which is essentially the
actual world position of the actor.
'Visual' would imply that it only affects the position where the
actor is drawn, which is inaccurate.
Furthermore, using the term 'Visual' here would make
naming future methods/properties related to visual interpolation
unnecessarily complicated, because that's where we might
need a real 'Visual(Only)Position'.
2021-03-08 11:19:11 +01:00
teinarss
7073279ab8 Replace WebClient with HttpClient 2021-03-07 16:04:57 +00:00
teinarss
ed43071792 Compile engine and mod dlls as NET5 when not using mono 2021-03-07 16:04:57 +00:00
teinarss
4a1e4f3e16 Use expression body syntax 2021-03-07 13:00:52 +00:00
Paul Chote
555c43843b Fix lobby checkbox event rectangle overlapping with scrollbars. 2021-03-05 18:58:51 +01:00
Paul Chote
a0d75d1e63 Fix rendering artifacts with RA's dialog5 background. 2021-03-05 18:33:39 +01:00
reaperrr
1a7a47fa08 Randomize AI idle harvester scan intervals 2021-02-28 18:58:03 +01:00
teinarss
c2279d3071 Change GetField calls to use nameof 2021-02-28 18:43:51 +01:00
teinarss
ed295ae315 Change throw exceptions to use nameof in parameter 2021-02-28 18:43:51 +01:00
teinarss
53b781960c Change FieldLoader.LoadUsing to use nameof 2021-02-28 18:43:51 +01:00
penev92
6b794ba3e5 Added the "missing" song to the D2k TestFiles
Since it's music it's optional, so the players still won't know anything happened, but if they do open the content manager they will have a chance for an automatic installation.
2021-02-28 12:29:15 +00:00
penev92
2f95e56256 Fixed incorrect D2k song installation 2021-02-28 12:29:15 +00:00
pizzaoverhead
c8644adc85 Fix TLS error on Win7. 2021-02-28 10:15:29 +00:00
Paul Chote
8b81078929 Fix lighting effects in "The Pit" map. 2021-02-24 19:20:48 +01:00
Paul Chote
76a10283c4 Fix TS effect translucency/lighting effects. 2021-02-24 19:20:48 +01:00
Paul Chote
0975102e92 Add Alpha support to sequences.
Alpha can specify a single value for the sequence
or values for each frame in the sequence.

AlphaFade: True can be specified to linearly fade
to transparent over the length of the animation.
2021-02-24 19:20:48 +01:00
Paul Chote
445d943549 Remove obsolete shadow palette definitions. 2021-02-20 02:08:40 +01:00
Paul Chote
554486fb0b Replace projectile, WithShadow, WithParachute shadows with tint effects. 2021-02-20 02:08:40 +01:00
penev92
1c1af89bb9 Fixed D2k VQA videos crashing the game 2021-02-19 13:38:12 +01:00
Trevor Nichols
536c130a39 Add MSADPCM audio format decoding support 2021-02-19 01:56:57 +01:00
Matthias Mailänder
5aa67a42c1 Bump Eluant version to support Unicode strings. 2021-02-18 23:16:49 +01:00
Paul Chote
f661d1ba48 Remove OLDLST campaign overrides. 2021-02-14 18:13:21 +01:00
reaperrr
717b9ba1e6 Fix TS GDI temperate radar sprite
Unlike its snow counterpart, the transition between building
and its shadow was jarring, making it look as if there was a
1-pixel gap between them. Fixed that.

Also applied some very subtle polish by replacing some non-
remapable red pixels (nothing worth porting to snow variant, though).
2021-02-14 14:40:58 +00:00
reaperrr
c57cc96145 TS radars Z offset fixes
The lower borders of the sprites were cut off without this.
2021-02-14 14:40:58 +00:00
Smittytron
16fa1ef144 Change Z-offsets that overlap Repair Facility 2021-02-14 14:31:50 +00:00
Smittytron
788f0f3e24 Change Z-offsets that overlap Service Depot 2021-02-14 14:31:50 +00:00
Unrud
ccf9c8fb22 AppStream Metadata: Add violence-worship: moderate 2021-02-14 14:16:53 +00:00
Unrud
ee31146501 Update AppStream metadata for linux packaging
A few small improvements:
* The type `desktop` was renamed to `desktop-appliaction` (a long time ago)
* Add `launchable` to tell how the application can be launched
* Use `https` for homepage link
* Add link to the bugtracker
* Update `oars-1.0` to `oars-1.1`
* Remove all unnecessary `content_attribute` entries with value `none`
2021-02-14 14:16:53 +00:00
Vapre
f176a0ed83 Fix. Call EndGame in one place of server main loop. Ensure UPnP port forward is removed always. 18807. 2021-02-14 13:54:48 +00:00
Matthias Mailänder
e7cfd2765c Make UI cursors configurable. 2021-02-14 13:09:59 +00:00
abcdefg30
96c4554644 Remove GenericSelectTarget 2021-02-14 12:51:12 +00:00
Mustafa Alperen Seki
df94f0ec8b Add InitialActor to Carryall. 2021-02-14 12:47:08 +00:00
Adam Mitchell
9ee7294c81 Improve shroud performance 2021-02-12 20:58:56 +01:00
Smittytron
5cf622fb6e Improve AI response in Nod03b 2021-02-12 20:38:37 +01:00
Smittytron
8711d8799c Improve AI response in Nod03a 2021-02-12 20:38:37 +01:00
Smittytron
e9a6a3afc5 Add clarifying objective to Monster Tank Madness 2021-02-12 02:30:12 +01:00
abcdefg30
9c29264be7 Drop FlyAttackRun targets when we don't have valid armaments against them 2021-02-12 02:17:26 +01:00
abcdefg30
e6c9d5fc96 Don't check IsTraitDisabled twice when calculating IsTargetableBy 2021-02-12 02:17:26 +01:00
Paul Chote
d09476c603 Remove custom palettes from building placement previews. 2021-02-12 02:08:30 +01:00
Orb
5bda6852a4 APC Balance Tweak Commit 2021-02-09 23:07:44 +01:00
Ivaylo Draganov
641b05eb21 Split settings panels logic and add support for custom panels 2021-02-08 11:25:50 +01:00
reaperrr
62fd11d5c4 Fix deployed tick tank sprites
Several pixels used non-remapable red colors.
2021-02-06 22:46:52 +01:00
Smittytron
e807664437 Add TanyaRescued speech notification to Downunder 2021-02-06 01:18:48 +01:00
Smittytron
e46440b94f Correct the ownership of walls in Allies05a 2021-02-06 01:18:48 +01:00
Smittytron
30ea1e23b4 Delay SendReinforcements() to split notifications 2021-02-06 01:18:48 +01:00
Smittytron
4db2fd980a Kill pillboxes with barrels in Soviet01 2021-02-06 01:18:48 +01:00
Smittytron
9580d61fb0 Fix spaces in Monster Tank Madness 2021-02-06 01:18:48 +01:00
Smittytron
27aefcc0ce Fix tabs in Ant01 2021-02-06 01:18:48 +01:00
Smittytron
b8b1a4930c Change BarrelExplode CreateEffect to large_napalm 2021-02-05 23:41:03 +01:00
Smittytron
d7a40f3bdb Change BarrelExplode to use FireDeath 2021-02-05 23:41:03 +01:00
Paul Chote
fb0031d34a Rename remaining Stance references to PlayerRelationship. 2021-02-04 23:14:09 +01:00
Paul Chote
b3821e71dc Fix Tanya's prone firing animations. 2021-02-02 23:05:07 +01:00
Unrud
8a90413b87 Use version from VERSION file
The `VERSION` variable doesn't work with a release tarball, because it requires git to be set correctly.
Instead this reads the version from the `VERSION` file.
2021-02-01 23:13:48 +01:00
abcdefg30
115984054d Increase the reload delay of the prison colt to match the normal colt 2021-01-31 22:35:42 +00:00
abcdefg30
69addda86c Fix the gun sounds not playing in Allies05a 2021-01-31 22:35:42 +00:00
Smittytron
c2379f251d Fix crash in Allies 05a on SamBarrel kill 2021-01-31 18:52:47 +01:00
abcdefg30
3422bf1b59 Remove the 'docs' target from the windows make file 2021-01-30 21:08:25 +00:00
Paul Chote
e830d1e9c2 Fix Concrete placement on invalid terrain. 2021-01-30 14:48:04 +01:00
Smittytron
f767c24601 Remove SpawnActorOnDeath from civilian structures 2021-01-30 13:47:14 +01:00
Paul Chote
2c09b1414c Ignore invalid server entries instead of the entire list. 2021-01-30 13:44:51 +01:00
Paul Chote
df1191db5b Don't crash if a null uid is given to QueryRemoteMapDetails. 2021-01-30 13:44:51 +01:00
Paul Chote
62433ecb8b Prevent AttackPopupTurreted.TickIdle from running while disabled. 2021-01-30 13:16:58 +01:00
Paul Chote
09f680201f Restore missing versioned source code package. 2021-01-30 13:04:23 +01:00
reaperrr
5ba662cfae Fix RA inf death anims playing too fast 2021-01-30 01:23:39 +00:00
Paul Chote
d6d0fd597d Fix Tanya firing animation. 2021-01-29 15:04:42 +01:00
Paul Chote
fec1c813a5 Support burst-specific infantry attack animations. 2021-01-29 15:04:42 +01:00
Paul Chote
547b6d19dc Reduce Tanya firing rate. 2021-01-29 15:04:42 +01:00
Paul Chote
5e6dd50c3b Suppress error messages from make.ps1 clean.
Also removed obsolete ./*/bin removal.
2021-01-29 14:54:40 +01:00
Paul Chote
4251ed69bb Keep Discord join button active for spectators. 2021-01-29 14:38:13 +01:00
Paul Chote
b05ee80d5c Remove first-client check from LobbySettingsNotification.
ClientWithIndex may rarely be null, causing a crash.
In any case we do want to report these changes to the first client, as
somebody else may have changed the settings and left.
2021-01-29 14:35:32 +01:00
Paul Chote
84ced8704d Fix bot-controlled aircraft stalling above cloaked targets. 2021-01-29 14:30:27 +01:00
teinarss
58313520f0 Change renderables to class to avoid boxing 2021-01-29 00:24:27 +01:00
reaperrr
0d8ef1a1dd Make Contrail conditional and public
Also moved it to 'Render' subfolder while at it.
2021-01-28 23:30:45 +01:00
Matthias Mailänder
28ddab7cc2 Disallow building sell during construction. 2021-01-27 22:51:58 +01:00
Matthias Mailänder
8f06b0a836 Add a "structure sold" notification. 2021-01-27 22:51:58 +01:00
Paul Chote
3b768dacf5 Increase map chooser dialog size to match the lobby. 2021-01-25 20:43:53 +01:00
Smittytron
d67b3245e0 Change hospital owner in Nod07b 2021-01-24 10:47:05 +00:00
abcdefg30
a1dd1fc5d1 Fix an compiler error on bleed 2021-01-23 23:15:58 +00:00
Paul Chote
889153ce81 Add shadow rendering for resource sprites. 2021-01-23 20:59:53 +01:00
teinarss
30e5c807b0 Add Map and server name to discord details 2021-01-22 14:14:02 +01:00
Paul Chote
db2fded24d Fix AutoTarget.ChooseTarget ignoring AttackBase.TargetFrozenActors. 2021-01-21 19:25:07 +01:00
Paul Chote
82a9809192 Remove RenderSprites.Scale. 2021-01-21 18:22:11 +01:00
Paul Chote
f6b40b2bce Allow sequences to define a Scale factor. 2021-01-21 18:22:11 +01:00
abcdefg30
fd75e03d9c Use pattern matching in FieldSaver 2021-01-21 18:05:07 +01:00
Paul Chote
d6a05f2ea2 Fix Sardarkaur not attacking while attack-moving. 2021-01-17 19:42:49 +01:00
Paul Chote
f51603e440 Add ShpRemastered sprite loader. 2021-01-16 23:16:34 +01:00
Paul Chote
f341c9a8a6 Add TGA and DDS sprite loaders. 2021-01-16 23:16:34 +01:00
Paul Chote
765944cfa2 Switch macOS and Linux default toolchain to .NET 5. 2021-01-16 22:58:05 +01:00
Paul Chote
15c926b6b9 Move DefineConstants="MONO" into OpenRA.Game.csproj. 2021-01-16 22:58:05 +01:00
teinarss
b486112c98 Fix broken editorconfig 2021-01-16 22:28:17 +01:00
Paul Chote
223a0015a6 Allow terrain depth sprites to be loaded from a second file. 2021-01-16 22:09:08 +01:00
Paul Chote
632af7c7e6 Support mixed indexed/BGRA terrain tiles. 2021-01-16 22:09:08 +01:00
Paul Chote
142870d78a Support multiple sheets in TerrainSpriteLayer. 2021-01-16 22:09:08 +01:00
Paul Chote
b0aa32cd1b Support multiple palettes in TerrainSpriteLayer. 2021-01-16 22:09:08 +01:00
Matthias Mailänder
281229e8b4 Update path according to latest appstream specifications. 2021-01-16 20:02:19 +01:00
Matthias Mailänder
8f0d53ffd9 Document the target platform parameter during install
as it otherwise copies the system binaries.
2021-01-16 20:02:19 +01:00
Matthias Mailänder
ddfa5a4d35 Get rid of unnecessary Makefile variables. 2021-01-16 20:02:19 +01:00
Matthias Mailänder
cbf2e2e2ef Don't contain the build root in the wrappers. 2021-01-16 20:02:19 +01:00
Matthias Mailänder
62803aff88 Create BIN_PATH first on shortcut installation. 2021-01-16 20:02:19 +01:00
VonNah
bc9cd57fa0 Fix vehicle explosion and missile impact sounds in Tiberian Dawn 2021-01-16 01:32:20 +00:00
VonNah
93fa9bdfb8 Fixed an issue where RadarUp and DisablePower/EnablePower use the wrong sound effects.
Signed-off-by: VonNah <vonnahora@gmail.com>
2021-01-16 00:48:35 +00:00
reaperrr
808c5f0951 Remove ScriptTriggers from map deco actors
Removes it from destroyed fields, trees and rocks in RA and C&C.

This trait isn't free in terms of performance due to all the
INotify* calls, and the mentioned actors should rarely
- if ever - need it. Besides, if a mission ever really needs
this on those actors, the trait can be added back via
map rules.
2021-01-16 00:44:03 +00:00
Paul Chote
a70637f28f Fix missing System.Threading.Tasks.Parallel in packaged net5 builds. 2021-01-15 12:18:00 +01:00
Paul Chote
a12e4657ee Update apt metadata before installing dependencies. 2021-01-12 23:09:11 +01:00
Paul Chote
53db1230ab Move default tileset parsing to Mods.Common. 2021-01-11 21:57:55 +01:00
Paul Chote
207e09fea9 Add plumbing for mod-defined terrain loaders. 2021-01-11 21:57:55 +01:00
Paul Chote
b86b638700 Move editor template rendering to TerrainRenderer. 2021-01-11 21:57:55 +01:00
Paul Chote
995c33a942 Remove Ruleset.TileSet. 2021-01-11 21:57:55 +01:00
Paul Chote
6d6efd5fe8 Move tileset image validation to TerrainRendererInfo. 2021-01-11 21:57:55 +01:00
Paul Chote
2782620081 Add ITemplatedTerrainInfo interface. 2021-01-11 21:57:55 +01:00
Paul Chote
be2ca77acf Add ITerrainInfoNotifyMapCreated interface. 2021-01-11 21:57:55 +01:00
Paul Chote
87790069e9 Add ITerrainInfo interface. 2021-01-11 21:57:55 +01:00
Paul Chote
0a374e2264 Move ownership of tile sprites to the terrain renderer. 2021-01-11 21:57:55 +01:00
Paul Chote
2eee911c58 Fix infantry freezing death animations in Top o' the World mission. 2021-01-11 11:32:38 +01:00
Paul Chote
bb65646c4d Fix Grenadier death animation in Sarin Gas 3 mission. 2021-01-11 11:32:38 +01:00
Paul Chote
fea700fb72 Add death types support to the Lua Kill() API. 2021-01-11 11:32:38 +01:00
tovl
560c3230cd Let harvesters only search for refineries when needing to unload. 2021-01-10 23:43:48 +01:00
Paul Chote
e990a83b7a Cancel support power targeting if power cannot be activated. 2021-01-10 22:42:32 +01:00
Paul Chote
02a2624bcc Add a per-player handicap option to the lobby.
Handicaps reduce unit health, firepower, and build speed.
2021-01-10 22:23:52 +01:00
Paul Chote
c7c78eda80 Increase lobby and server list width. 2021-01-10 22:23:52 +01:00
Paul Chote
b1200b8078 Switch make.ps1 to downloading from our GeoIP data mirror. 2021-01-10 21:13:26 +01:00
Matthias Mailänder
0cb25f1044 Fix syntax errors. 2021-01-10 21:06:39 +01:00
Matthias Mailänder
8ee728891d Use the GitHub action variable for clickable links. 2021-01-10 21:06:39 +01:00
Matthias Mailänder
bbaa475287 Update SDL 2 nuget package to match the platform. 2021-01-10 21:04:44 +01:00
Matthias Mailänder
7bc17b59f5 Add a generic video player widget. 2021-01-10 10:21:17 +01:00
Smittytron
514652bb6a Add Aftermath mission Shock Therapy 2021-01-10 02:34:50 +01:00
Paul Chote
3e7665146a Avoid BuildUnit crash if the item is invalidated before the task runs. 2021-01-10 01:34:51 +01:00
Paul Chote
16d0f8a5a6 Add a setting to pause the shellmap. 2021-01-10 00:23:30 +01:00
Paul Chote
aeab9a8116 Group bot AttackMove orders. 2021-01-09 23:55:50 +01:00
Orb
f9a5669eb4 Playtest-Balance-Tweaks Commit 2021-01-09 23:22:49 +01:00
Paul Chote
868736fe1a Remove obsolete --check-runtime-assemblies utility command. 2021-01-09 19:53:22 +01:00
Paul Chote
0984de6bea Change install_assemblies_mono to install all bin contents. 2021-01-09 19:53:22 +01:00
Paul Chote
69ffd70d3f Switch Appimages to net 5. 2021-01-09 19:53:22 +01:00
Paul Chote
2fae69c0ba Run appimagetool directly. 2021-01-09 19:53:22 +01:00
Paul Chote
f1d66a4c70 Use ULFO format for non-compat disk images (requires macOS 10.11+). 2021-01-09 19:53:22 +01:00
Paul Chote
84ce33fe9c Trim unused assemblies to reduce packaged size further. 2021-01-09 19:53:22 +01:00
Paul Chote
e583165dff Replace duplicate runtime files with hardlinks to reduce dmg size. 2021-01-09 19:53:22 +01:00
Paul Chote
7bedba5837 Switch macOS packages to .NET 5. 2021-01-09 19:53:22 +01:00
Paul Chote
8c9b9df125 Change WithColoredOverlay to use a color instead of a palette. 2021-01-06 00:06:51 +01:00
Paul Chote
96641873ae Replace highlight palette with tint effects. 2021-01-06 00:06:51 +01:00
Paul Chote
8edd9de278 Replace ITintableRenderable with IModifyableRenderable. 2021-01-06 00:06:51 +01:00
Paul Chote
67754e8693 Add alpha support to SpriteRenderer. 2021-01-06 00:06:51 +01:00
Paul Chote
b88495c689 Remove palettes from base IRenderable. 2021-01-06 00:06:51 +01:00
Paul Chote
71b13c7b5d Disable AttackPopupTurreted state changes when paused. 2021-01-03 12:46:47 +01:00
nvrnight
b3f39bffce Issue #18936: The game no longer crashes when TextFieldWidget sends Enter key press and onSelect action is null. 2021-01-03 12:26:52 +01:00
Smittytron
b44a9ab5fc Fix palettes in Fall of Greece 1 2021-01-02 22:43:28 +01:00
Smittytron
e34255b0b2 Add campaign-palettes to missions lacking it 2021-01-02 22:43:28 +01:00
Paul Chote
2fee62af4d Fix exit priority ordering. 2021-01-02 15:43:03 +01:00
Paul Chote
fc1032cd9e Start rally point lines at the spawn position instead of the exit cell. 2021-01-02 15:29:18 +01:00
Paul Chote
60195e2842 Prevent Health: 100 from being added to actors. 2021-01-02 15:12:20 +01:00
Paul Chote
6fed31717c Fix map editor inverting preview color channels. 2021-01-02 11:43:55 +01:00
Paul Chote
aee3eb99c3 Fix --dump-sequence-sheets crash. 2021-01-02 11:43:55 +01:00
Paul Chote
3aa6fd3dc4 Add a friendly type name for Nullable<T>. 2021-01-02 11:38:24 +01:00
teinarss
5e74e58b22 Add support for dotnet core for Windows 2021-01-01 19:42:01 +01:00
abcdefg30
fef7a018f2 Add an update rule 2020-12-31 12:09:56 +01:00
abcdefg30
1d4891b017 Rename several MP traits 2020-12-31 12:09:56 +01:00
Ivaylo Draganov
e9a803e3c1 Update launch.json to work with the new launcher 2020-12-30 17:31:37 +01:00
Nah
4abe7d5895 Update defaults.yaml
Fix the Building's SoundOnDamageTransition DamagedSounds with the correct one and add a second sound file to DestroyedSounds for more variation like in the original Tiberian Dawn.
2020-12-30 14:06:56 +00:00
abcdefg30
cd68db5c11 Move myes1 and mrise1 from Select to Move Mechanic Voices 2020-12-30 02:01:00 +00:00
abcdefg30
ae9f437bb8 Enable mechanic voices from Aftermath 2020-12-30 02:01:00 +00:00
Smittytron
a047893049 Remove out of bounds actors from Exodus 2020-12-30 01:51:27 +00:00
Smittytron
5b2733decf Fix lag issue on Exodus by narrowing exit area 2020-12-30 01:51:27 +00:00
Ivaylo Draganov
ff60540fac Add player random faction info in the objectives panel 2020-12-30 01:44:50 +00:00
abcdefg30
a68a91bb39 Explicitly return on failure in make.ps1 2020-12-29 21:26:07 +01:00
abcdefg30
0bf8d2241f Correct Shock Trooper Move and Select voices 2020-12-29 21:15:53 +01:00
Orb
06850e6271 Fix-MT-Sound-Commit 2020-12-28 10:56:20 +01:00
Paul Chote
62fa3b7c9c Rename SpriteFrameType enums. 2020-12-25 18:51:25 +01:00
Paul Chote
ce09b402d0 Fix definition and use of non-indexed sprite color channels.
Our SpriteFrameType names refer to the byte channel order rather than
the bit order, meaning that SpriteFrameType.BGRA corresponds to the
standard Color.ToArgb() etc byte order when the (little-endian) integer
is read as 4 individual bytes.

The previous code did not account for the fact that non-indexed Png
uses big-endian storage for its RGBA colours, and that SheetBuilder
had the color channels incorrectly swapped to match and cancel this out.

New SpriteFrameType enums are introduced to distinguish between BGRA
(little-endian) and RGBA (big-endian) formats, and also for 24bit data
without alpha. The channel swizzling / alpha creation is now handled
when copying into the texture atlas, removing the need for non-png
ISpriteLoader implementations to allocate an additional temporary array
and reorder the channels during load.
2020-12-25 18:51:25 +01:00
Paul Chote
6e7ad9df25 Remove vestigial translation plumbing.
This was never completed to the level required to
be properly used ingame.
2020-12-25 16:18:28 +01:00
Matthias Mailänder
fb20479379 Add .vxl support to the asset browser. 2020-12-25 00:00:11 +00:00
Matthias Mailänder
9d181e88d2 Name the files that cause crashes. 2020-12-25 00:00:11 +00:00
Vapre
e90fc1ef39 As proposed by Leonardo-Ferreira. 2020-12-24 23:43:35 +00:00
Vapre
ce013f17d6 Server DispatchOrdersToClients create frame once for all clients.
Avoid creating frame data per client connection. Avoid
the allocation of a memory stream and setting frame header
and copying frame data.
2020-12-24 23:43:35 +00:00
Vapre
78253ce284 Activity, fixes.
Do not call SkipDoneActivities method recursively via the
NextActivity property. Rather use the nextActivity member.
Avoiding additional function calls and a recursively
growing stack.

Do not call ChildActivity and NextActivity properties
twice in a row. Once to test for null and after to access
it's value. It will cause the complete list of activities
to be traversed twice looking for non done activities.

Replace Queue method with a version that does not the
NextActivity property causing an extra call to
SkipDoneActivities. Avoid calling Queue recursively.

Similar replace QueueChild with a version that does
not call additional methods.

Note that ActivitiesImplementing returns only non
done activities. The method name does not suggest this.

Please consider making NextActivity a method to cleary indicate it
involves the logic of skipping Done activities. To let
the called know it is 'expensive'.

Please consider renaming the protected property ChildActivity to
FirstChildActivityNotDone to avoid it being used as childActivity.

Please consider maintaining a pointer to the first
non done activity. This avoids the need the each time find it.
2020-12-24 23:02:07 +00:00
Taryn Hill
2671e40c1d feat: ActorSpawnManager.SpawnInterval supports 1 or 2 values
Providing 2 values creates a range from which a value is randomly selected.
2020-12-24 22:15:15 +00:00
Matthias Mailänder
04cda69ef9 This compression is actually not yet supported. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
b4c483ce1a Fix channels. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
9a9f58d744 Support 8 bit .aud files. 2020-12-24 22:05:37 +00:00
Matthias Mailänder
d38fe542a2 Improve performance. 2020-12-24 15:56:18 +00:00
Matthias Mailänder
80503fbf36 Bump SharpZipLib. 2020-12-24 13:48:21 +00:00
Paul Chote
99a23b4056 Fix an incorrect comment in install_assemblies_mono 2020-12-24 12:08:56 +01:00
Matthias Mailänder
13a7de4b6b Allow a system chat label override. 2020-12-24 10:01:54 +00:00
teinarss
73bba97aaa Update MasterServerPinger to modern approach 2020-12-22 20:57:40 +01:00
abcdefg30
d6e9cdab5b Add the 9th Dark Tournament map as "Oil Spill" 2020-12-21 21:08:39 +01:00
Paul Chote
1a177bc2de Remove unused variables from Map.SavePreview. 2020-12-19 13:07:01 +01:00
Paul Chote
e0b3e631fe Remove obsolete null checks. 2020-12-19 13:07:01 +01:00
Paul Chote
2518a353af Add lint test for invalid map tiles. 2020-12-19 13:07:01 +01:00
Paul Chote
989800efff Fix missing tiles in upstream maps. 2020-12-19 13:07:01 +01:00
Paul Chote
c02846e2cb Replace invalid tiles on map load. 2020-12-19 13:07:01 +01:00
2045 changed files with 50276 additions and 19300 deletions

View File

@@ -55,6 +55,8 @@ dotnet_naming_symbols.types.applicable_accessibilities = public, internal, priva
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.parameters.applicable_kinds = parameter
# Naming rules
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
@@ -78,6 +80,10 @@ dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = war
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case
dotnet_naming_rule.parameters.severity = warning
dotnet_naming_rule.parameters.symbols = parameters
dotnet_naming_rule.parameters.style = camel_case
# Naming rules
#require a space before the colon for bases or interfaces in a type declaration
@@ -100,6 +106,9 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#don't prefer braces (for one liners)
dotnet_diagnostic.IDE0011.severity = none
; 4-column tab indentation
[*.yaml]
indent_style = tab

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: orahosting

View File

@@ -6,6 +6,29 @@ on:
branches: [ bleed ]
jobs:
linux:
name: Linux (.NET 5.0)
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Check Code
run: |
make check
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make test
linux-mono:
name: Linux (mono)
runs-on: ubuntu-20.04
@@ -17,28 +40,34 @@ jobs:
- name: Check Code
run: |
mono --version
make check
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult bin/OpenRA.Test.dll
make RUNTIME=mono check
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make test
# check-scripts does not depend on .net/mono, so is not needed here
make RUNTIME=mono test
windows:
name: Windows (Framework 4.7)
name: Windows (.NET 5.0)
runs-on: windows-2019
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Check Code
shell: powershell
run: |
# Work around runtime failures on the GH Actions runner
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
.\make.ps1 check
Invoke-Expression "$home\.nuget\packages\nunit.consolerunner\3.11.1\tools\nunit3-console.exe --noresult bin/OpenRA.Test.dll"
dotnet build OpenRA.Test\OpenRA.Test.csproj -c Debug --nologo -p:TargetPlatform=win-x64
dotnet test bin\OpenRA.Test.dll --test-adapter-path:.
- name: Check Mods
run: |

View File

@@ -15,17 +15,15 @@ jobs:
if: github.repository == 'openra/openra'
steps:
- name: Download Packages
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64.exe"
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}-x64-winportable.zip" -O "OpenRA-${GIT_TAG}-x64-win-itch.zip"
wget -q "https://github.com${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-${GIT_TAG}.dmg"
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Dune-2000-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Red-Alert-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${GIT_TAG}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${GIT_TAG}/packaging/.itch.toml"
zip -u "OpenRA-${GIT_TAG}-x64-win-itch.zip" .itch.toml
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64.exe"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}-x64-winportable.zip" -O "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-${{ github.event.inputs.tag }}.dmg"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Dune-2000-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Red-Alert-x86_64.AppImage"
wget -q "https://github.com/${{ github.repository }}/releases/download/${{ github.event.inputs.tag }}/OpenRA-Tiberian-Dawn-x86_64.AppImage"
wget -q "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.tag }}/packaging/.itch.toml"
zip -u "OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip" .itch.toml
- name: Publish Windows Installer
uses: josephbmanley/butler-publish-itchio-action@master
@@ -33,9 +31,9 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: win
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe"
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64.exe
- name: Publish Windows Itch Bundle
uses: josephbmanley/butler-publish-itchio-action@master
@@ -43,7 +41,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: itch
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
@@ -53,9 +51,9 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: macos
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg"
PACKAGE: OpenRA-${{ github.event.inputs.tag }}.dmg
- name: Publish RA AppImage
uses: josephbmanley/butler-publish-itchio-action@master
@@ -63,7 +61,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-ra
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
@@ -73,7 +71,7 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-cnc
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
@@ -83,6 +81,6 @@ jobs:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-d2k
ITCH_GAME: openra
ITCH_USER: openra-developers
ITCH_USER: openra
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage

View File

@@ -8,6 +8,30 @@ on:
- 'devtest-*'
jobs:
source:
name: Source Tarball
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
- name: Package Source
run: |
mkdir -p build/source
./packaging/source/buildpackage.sh "${GIT_TAG}" "${PWD}/build/source"
- name: Upload Packages
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
file_glob: true
file: build/source/*
linux:
name: Linux AppImages
runs-on: ubuntu-20.04
@@ -15,6 +39,11 @@ jobs:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
@@ -39,6 +68,11 @@ jobs:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Prepare Environment
run: echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
@@ -69,10 +103,16 @@ jobs:
- name: Clone Repository
uses: actions/checkout@v2
- name: Install .NET 5
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Prepare Environment
run: |
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
sudo apt install nsis
sudo apt-get update
sudo apt-get install nsis wine64
- name: Package Installers
run: |

64
.vscode/launch.json vendored
View File

@@ -3,62 +3,46 @@
"configurations": [
{
"name": "Launch (TD)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=cnc"],
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
},
"args": ["Game.Mod=cnc", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (RA)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ra"],
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
},
"args": ["Game.Mod=ra", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (D2k)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=d2k"],
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
},
"args": ["Game.Mod=d2k", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
{
"name": "Launch (TS)",
"type": "clr",
"linux": {
"type": "mono"
},
"osx": {
"type": "mono"
},
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ts"],
"program": "${workspaceRoot}/bin/OpenRA.dll",
"windows": {
"program": "${workspaceRoot}/bin/OpenRA.exe",
},
"args": ["Game.Mod=ts", "Engine.EngineDir=.."],
"preLaunchTask": "build",
},
]

2
.vscode/tasks.json vendored
View File

@@ -4,7 +4,7 @@
{
"label": "build",
"command": "make",
"args": ["all"],
"args": ["all", "CONFIGURATION=Debug"],
"windows": {
"command": "make.cmd"
}

19
AUTHORS
View File

@@ -4,6 +4,7 @@ hard work of many contributors.
The OpenRA developers are:
* Chris Forbes (chrisf)
* Lukas Franke (abcdefg30)
* Matthias Mailänder (Mailaender)
* Paul Chote (pchote)
* Reaperrr
@@ -13,7 +14,6 @@ Previous developers included:
* Curtis Shmyr (hamb)
* Daniel Hernandez (Mancano)
* Igor Popov (ihptru)
* Matthias Mailänder (Mailaender)
* Megan Bowra-Dean (beedee)
* Mike Bundy (kehaar)
* Oliver Brakmann (obrakmann)
@@ -173,9 +173,17 @@ under the MIT license.
Using FuzzyLogicLibrary (fuzzynet) by Dmitry
Kaluzhny and released under the GNU GPL terms.
Using Open.Nat by Lucas Ontivero, based on the work
of Alan McGovern and Ben Motmans and distributed
under the MIT license.
Using Mono.Nat by Alan McGovern, Ben Motmans,
Nicholas Terry distributed under the MIT license.
Using MP3Sharp by Robert Bruke and Zane Wagner
licensed under the GNU LGPL Version 3.
Using TagLib# by Stephen Shaw licensed under the
GNU LGPL Version 2.1.
Using NVorbis by Andrew Ward distributed under
the MIT license.
Using ICSharpCode.SharpZipLib initially by Mike
Krueger and distributed under the GNU GPL terms.
@@ -191,6 +199,9 @@ distributed under MIT License.
Using ANGLE distributed under the BS3 3-Clause license.
Using Pfim developed by Nick Babcock
distributed under the MIT license.
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.

54
Directory.Build.props Normal file
View File

@@ -0,0 +1,54 @@
<Project>
<PropertyGroup>
<OutputType>Library</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<LangVersion>7.3</LangVersion>
<DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EngineRootPath Condition="'$(EngineRootPath)' == ''">..</EngineRootPath>
<OutputPath>$(EngineRootPath)/bin</OutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>$(EngineRootPath)/OpenRA.ruleset</CodeAnalysisRuleSet>
<Nullable>disable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework Condition="'$(Mono)' == ''">net5.0</TargetFramework>
<TargetFramework Condition="'$(Mono)' != ''">netstandard2.1</TargetFramework>
</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>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
<Compile Include="**/*.cs" Exclude="$(DefaultItemExcludes)" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
<!-- Disable code style analysis on Release builds to improve compile-time performance -->
<ItemGroup Condition="'$(Configuration)'=='Release'">
<Analyzer Remove="@(Analyzer)" />
</ItemGroup>
</Target>
<!-- StyleCop -->
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<AdditionalFiles Include="$(EngineRootPath)/stylecop.json" />
</ItemGroup>
</Project>

View File

@@ -8,104 +8,76 @@ Windows
Compiling OpenRA requires the following dependencies:
* [Windows PowerShell >= 4.0](http://microsoft.com/powershell) (included by default in recent Windows 10 versions)
* [.NET Framework 4.7.2 (Developer Pack)](https://dotnet.microsoft.com/download/dotnet-framework/net472) (or via Visual Studio 2017)
* [.NET Core 2.2 SDK](https://dotnet.microsoft.com/download/dotnet-core/2.2) (or via Visual Studio 2017)
* [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) (or via Visual Studio)
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.
To compile OpenRA, open the `OpenRA.sln` solution in the main folder, build it from the command-line with `dotnet` or use the Makefile analogue command `make all` scripted in PowerShell syntax.
Run the game with `launch-game.cmd`. It can be handed arguments that specify the exact mod one wishes to run, for example, run `launch-game.cmd Game.Mod=ra` to launch Red Alert, `launch-game.cmd Game.Mod=cnc` to start Tiberian dawn or `launch-game.cmd Game.Mod=d2k` to launch Dune 2000.
Linux
=====
Mono, version 5.18 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
.NET 5 or Mono (version 6.4 or later) is required to compile OpenRA. We recommend using .NET 5 when possible, as Mono is poorly packaged by most Linux distributions (e.g. missing the required `msbuild` toolchain), and has been deprecated as a standalone project.
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.
The [.NET 5 download page](https://dotnet.microsoft.com/download/dotnet/5.0) provides repositories for various package managers and binary releases for several architectures. If you prefer to use Mono, we suggest adding the [upstream repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version and the `msbuild` toolchain.
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). 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)
If you choose to use system libraries, or your system is not x86_64, you will need to install [SDL 2](https://www.libsdl.org/download-2.0.php), [FreeType](http://gnuwin32.sourceforge.net/packages/freetype.htm), [OpenAL](https://openal-soft.org/), and [liblua 5.1](http://luabinaries.sourceforge.net/download.html) before compiling OpenRA.
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`.
These can be installed using your package manager on various distros:
Arch Linux
----------
It is important to note there is an unofficial [`openra-git`](https://aur.archlinux.org/packages/openra-git) package in the Arch User Repository (AUR) of Arch Linux. If manually compiling is the way you wish to go the build and runtime dependencies can be installed with:
<details><summary>Arch Linux</summary>
```
sudo pacman -S mono openal libgl freetype2 sdl2 lua51 xdg-utils zenity
sudo pacman -S openal libgl freetype2 sdl2 lua51
```
Debian/Ubuntu
-------------
:warning: The `mono` packages in the Ubuntu < 19.04 and Debian < 10 repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases if needed.
</details>
<details><summary>Debian/Ubuntu</summary>
```
sudo apt install mono-devel libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0 xdg-utils zenity wget
sudo apt install libfreetype6 libopenal1 liblua5.1-0 libsdl2-2.0-0
```
Fedora
------
:warning: The `mono` packages in the Fedora repositories are too old to support OpenRA. :warning:
See the instructions under the *Linux* section above to upgrade `mono` using the upstream releases.
</details>
<details><summary>Fedora</summary>
```
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
sudo dnf install SDL2 freetype "lua = 5.1" openal-soft
```
Gentoo
------
</details>
<details><summary>Gentoo</summary>
```
sudo emerge -av dev-lang/mono dev-dotnet/libgdiplus media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/jpeg virtual/opengl '=dev-lang/lua-5.1.5*' x11-misc/xdg-utils gnome-extra/zenity
sudo emerge -av media-libs/freetype:2 media-libs/libsdl2 media-libs/openal virtual/opengl '=dev-lang/lua-5.1.5*'
```
Mageia
------
</details>
<details><summary>Mageia</summary>
```
sudo dnf install "pkgconfig(mono)" SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft xdg-utils zenity
sudo dnf install SDL2 freetype "lib*lua5.1" "lib*freetype2" "lib*sdl2.0_0" openal-soft
```
openSUSE
--------
</details>
<details><summary>openSUSE</summary>
```
sudo zypper in mono-devel openal-soft freetype2 SDL2 lua51 xdg-utils zenity
sudo zypper in openal-soft freetype2 SDL2 lua51
```
Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)
----------------------------------------------------
</details>
<details><summary>Red Hat Enterprise Linux (and rebuilds, e.g. CentOS)</summary>
The EPEL repository is required in order for the following command to run properly.
```
sudo yum install "pkgconfig(mono)" SDL2 freetype "lua = 5.1" openal-soft xdg-utils zenity
sudo yum install SDL2 freetype "lua = 5.1" openal-soft
```
</details>
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`.
macOS
=====
Before compiling OpenRA you must install the following dependencies:
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.
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`)
[.NET 5](https://dotnet.microsoft.com/download/dotnet/5.0) or [Mono](https://www.mono-project.com/download/stable/#download-mac) (version 6.4 or later) is required to compile OpenRA. We recommend using .NET 5 unless you are running a very old version of macOS (10.9 through 10.12).
To compile OpenRA, run `make` from the command line (or `make RUNTIME=mono` if using Mono). Run with `./launch-game.sh`.

112
Makefile
View File

@@ -1,19 +1,26 @@
############################# INSTRUCTIONS #############################
#
# to compile, run:
# make [DEBUG=true]
# make
#
# to compile using Mono (version 6.4 or greater) instead of .NET 5, run:
# make RUNTIME=mono
#
# to compile using system libraries for native dependencies, run:
# make [DEBUG=true] TARGETPLATFORM=unix-generic
# make [RUNTIME=net5] TARGETPLATFORM=unix-generic
#
# to check the official mods for erroneous yaml files, run:
# make test
# make [RUNTIME=net5] test
#
# to check the engine and official mod dlls for code style violations, run:
# make check
# make [RUNTIME=net5] check
#
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
# make [prefix=/foo] [bindir=/bar/bin] install
# make [RUNTIME=net5] [prefix=/foo] [bindir=/bar/bin] install
#
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000
# using system libraries for native dependencies, run:
# make [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install
#
# to install Linux startup scripts, desktop files, icons, and MIME metadata
# make install-linux-shortcuts
@@ -25,18 +32,6 @@
# make help
#
############################## TOOLCHAIN ###############################
#
# List of .NET assemblies that we can guarantee exist
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.exe OpenRA.Utility.exe OpenRA.Server.exe OpenRA.Platforms.Default.dll OpenRA.Game.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.Core.dll DiscordRPC.dll Newtonsoft.Json.dll
# These are shipped in our custom minimal mono runtime and also available in the full system-installed .NET/mono stack
# This list *must* be kept in sync with the files packaged by the AppImageSupport and OpenRALauncherOSX repositories
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll
######################### UTILITIES/SETTINGS ###########################
#
# Install locations for local installs and downstream packaging
@@ -48,20 +43,21 @@ bindir ?= $(prefix)/bin
libdir ?= $(prefix)/lib
gameinstalldir ?= $(libdir)/openra
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
DATA_INSTALL_DIR = $(DESTDIR)$(datadir)
OPENRA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
# Toolchain
CWD = $(shell pwd)
MSBUILD = msbuild -verbosity:m -nologo
DOTNET = dotnet
MONO = mono
RM = rm
RM_R = $(RM) -r
RM_F = $(RM) -f
RM_RF = $(RM) -rf
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
RUNTIME ?= net5
CONFIGURATION ?= Release
# Only for use in target version:
VERSION := $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || (c=$$(git rev-parse --short HEAD 2>/dev/null) && echo git-$$c))
# Detect target platform for dependencies if not given by the user
ifndef TARGETPLATFORM
@@ -78,36 +74,44 @@ endif
endif
endif
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.exe
##################### DEVELOPMENT BUILDS AND TESTS #####################
#
all:
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.18."; exit 1)
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM)
@echo "Compiling in ${CONFIGURATION} mode..."
ifeq ($(RUNTIME), mono)
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 6.4."; exit 1)
@$(MSBUILD) -t:Build -restore -p:Configuration=${CONFIGURATION} -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true
else
@$(DOTNET) build -c ${CONFIGURATION} -nologo -p:TargetPlatform=$(TARGETPLATFORM)
endif
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@./fetch-geoip.sh
# dotnet clean and msbuild -t:Clean leave files that cause problems when switching between mono/dotnet
# Deleting the intermediate / output directories ensures the build directory is actually clean
clean:
@-$(RM_RF) ./bin ./*/bin ./*/obj
@$(MSBUILD) -t:Clean
@-$(RM_RF) ./bin ./*/obj
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -restore -p:Configuration=Debug
@echo
@echo "Checking runtime assemblies..."
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
ifeq ($(RUNTIME), mono)
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true
else
@$(DOTNET) build -c Debug -nologo -p:TargetPlatform=$(TARGETPLATFORM)
endif
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@echo
@echo "Checking for explicit interface violations..."
@$(OPENRA_UTILITY) all --check-explicit-interfaces
@./utility.sh all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
@./utility.sh all --check-conditional-trait-interface-overrides
check-scripts:
@echo
@@ -119,48 +123,58 @@ check-scripts:
test: all
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@$(OPENRA_UTILITY) ts --check-yaml
@./utility.sh ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@$(OPENRA_UTILITY) d2k --check-yaml
@./utility.sh d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@$(OPENRA_UTILITY) cnc --check-yaml
@./utility.sh cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@$(OPENRA_UTILITY) ra --check-yaml
@./utility.sh ra --check-yaml
############# LOCAL INSTALLATION AND DOWNSTREAM PACKAGING ##############
#
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
@sh -c '. ./packaging/functions.sh; set_engine_version $(VERSION) .'
@sh -c '. ./packaging/functions.sh; set_mod_version $(VERSION) mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
ifeq ($(VERSION),)
$(error Unable to determine new version (requires git or override of variable VERSION))
endif
@sh -c '. ./packaging/functions.sh; set_engine_version "$(VERSION)" .'
@sh -c '. ./packaging/functions.sh; set_mod_version "$(VERSION)" mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml'
install:
@sh -c '. ./packaging/functions.sh; install_assemblies_mono $(CWD) $(OPENRA_INSTALL_DIR) $(TARGETPLATFORM) True True True'
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(OPENRA_INSTALL_DIR) cnc d2k ra'
@sh -c '. ./packaging/functions.sh; install_assemblies $(CWD) $(DESTDIR)$(gameinstalldir) $(TARGETPLATFORM) $(RUNTIME) True True True'
@sh -c '. ./packaging/functions.sh; install_data $(CWD) $(DESTDIR)$(gameinstalldir) cnc d2k ra'
install-linux-shortcuts:
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) $(OPENRA_INSTALL_DIR) $(BIN_INSTALL_DIR) $(DATA_INSTALL_DIR) $(VERSION) cnc d2k ra'
@sh -c '. ./packaging/functions.sh; install_linux_shortcuts $(CWD) "$(DESTDIR)" "$(gameinstalldir)" "$(bindir)" "$(datadir)" "$(shell head -n1 VERSION)" cnc d2k ra'
install-linux-appdata:
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) $(DATA_INSTALL_DIR) cnc d2k ra'
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) "$(DESTDIR)" "$(datadir)" cnc d2k ra'
help:
@echo 'to compile, run:'
@echo ' make [DEBUG=true]'
@echo ' make'
@echo
@echo 'to compile using Mono (version 6.4 or greater) instead of .NET 5, run:'
@echo ' make RUNTIME=mono'
@echo
@echo 'to compile using system libraries for native dependencies, run:'
@echo ' make [DEBUG=true] TARGETPLATFORM=unix-generic'
@echo ' make [RUNTIME=net5] TARGETPLATFORM=unix-generic'
@echo
@echo 'to check the official mods for erroneous yaml files, run:'
@echo ' make test'
@echo ' make [RUNTIME=net5] test'
@echo
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make test'
@echo ' make [RUNTIME=net5] check'
@echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
@echo ' make [prefix=/foo] install'
@echo ' make [RUNTIME=net5] [prefix=/foo] [TARGETPLATFORM=unix-generic] install'
@echo
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000'
@echo 'using system libraries for native dependencies, run:'
@echo ' make [RUNTIME=net5] [prefix=/foo] [bindir=/bar/bin] TARGETPLATFORM=unix-generic install'
@echo
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
@echo ' make install-linux-shortcuts'

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -53,15 +53,15 @@ namespace OpenRA.Activities
Activity childActivity;
protected Activity ChildActivity
{
get { return SkipDoneActivities(childActivity); }
private set { childActivity = value; }
get => SkipDoneActivities(childActivity);
private set => childActivity = value;
}
Activity nextActivity;
public Activity NextActivity
{
get { return SkipDoneActivities(nextActivity); }
private set { nextActivity = value; }
get => SkipDoneActivities(nextActivity);
private set => nextActivity = value;
}
internal static Activity SkipDoneActivities(Activity first)
@@ -74,14 +74,14 @@ namespace OpenRA.Activities
// drop valid activities queued after it. Walk the queue until we find a valid activity or
// (more likely) run out of activities.
while (first != null && first.State == ActivityState.Done)
first = first.NextActivity;
first = first.nextActivity;
return first;
}
public bool IsInterruptible { get; protected set; }
public bool ChildHasPriority { get; protected set; }
public bool IsCanceling { get { return State == ActivityState.Canceling; } }
public bool IsCanceling => State == ActivityState.Canceling;
bool finishing;
bool firstRunCompleted;
bool lastRun;
@@ -95,7 +95,7 @@ namespace OpenRA.Activities
public Activity TickOuter(Actor self)
{
if (State == ActivityState.Done)
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} after it had already completed.");
if (State == ActivityState.Queued)
{
@@ -105,7 +105,7 @@ namespace OpenRA.Activities
}
if (!firstRunCompleted)
throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
throw new InvalidOperationException($"Actor {self} attempted to tick activity {GetType()} before running its OnFirstRun method.");
// Only run the parent tick when the child is done.
// We must always let the child finish on its own before continuing.
@@ -120,7 +120,8 @@ namespace OpenRA.Activities
lastRun = Tick(self);
// Avoid a single tick delay if the childactivity was just queued.
if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
var ca = ChildActivity;
if (ca != null && ca.State == ActivityState.Queued)
{
if (ChildHasPriority)
lastRun = TickChild(self) && finishing;
@@ -206,18 +207,18 @@ namespace OpenRA.Activities
public void Queue(Activity activity)
{
if (NextActivity != null)
NextActivity.Queue(activity);
else
NextActivity = activity;
var it = this;
while (it.nextActivity != null)
it = it.nextActivity;
it.nextActivity = activity;
}
public void QueueChild(Activity activity)
{
if (ChildActivity != null)
ChildActivity.Queue(activity);
if (childActivity != null)
childActivity.Queue(activity);
else
ChildActivity = activity;
childActivity = activity;
}
/// <summary>
@@ -269,15 +270,21 @@ namespace OpenRA.Activities
public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
{
if (includeChildren && ChildActivity != null)
foreach (var a in ChildActivity.ActivitiesImplementing<T>())
yield return a;
// Skips Done child and next activities
if (includeChildren)
{
var ca = ChildActivity;
if (ca != null)
foreach (var a in ca.ActivitiesImplementing<T>())
yield return a;
}
if (this is T)
yield return (T)(object)this;
if (NextActivity != null)
foreach (var a in NextActivity.ActivitiesImplementing<T>())
var na = NextActivity;
if (na != null)
foreach (var a in na.ActivitiesImplementing<T>())
yield return a;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -26,7 +26,7 @@ namespace OpenRA.Activities
public override bool Tick(Actor self)
{
a?.Invoke();
a.Invoke();
return true;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Eluant;
@@ -23,9 +24,18 @@ using OpenRA.Traits;
namespace OpenRA
{
[Flags]
public enum SystemActors
{
Player = 0,
EditorPlayer = 1,
World = 2,
EditorWorld = 4
}
public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
{
internal struct SyncHash
internal readonly struct SyncHash
{
public readonly ISync Trait;
readonly Func<object, int> hashFunction;
@@ -48,8 +58,8 @@ namespace OpenRA
Activity currentActivity;
public Activity CurrentActivity
{
get { return Activity.SkipDoneActivities(currentActivity); }
private set { currentActivity = value; }
get => Activity.SkipDoneActivities(currentActivity);
private set => currentActivity = value;
}
public int Generation;
@@ -59,19 +69,13 @@ namespace OpenRA
public IOccupySpace OccupiesSpace { get; private set; }
public ITargetable[] Targetables { get; private set; }
public bool IsIdle { get { return CurrentActivity == null; } }
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
public bool IsIdle => CurrentActivity == null;
public bool IsDead => Disposed || (health != null && health.IsDead);
public CPos Location { get { return OccupiesSpace.TopLeft; } }
public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
public CPos Location => OccupiesSpace.TopLeft;
public WPos CenterPosition => OccupiesSpace.CenterPosition;
public WRot Orientation
{
get
{
return facing != null ? facing.Orientation : WRot.None;
}
}
public WRot Orientation => facing?.Orientation ?? WRot.None;
/// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
@@ -121,7 +125,7 @@ namespace OpenRA
.FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null)
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
throw new InvalidDataException($"Duplicate initializer '{duplicateInit.Key.Name}'");
var init = new ActorInitializer(this, initDict);
@@ -246,7 +250,7 @@ namespace OpenRA
continue;
if (creationActivity != null)
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
throw new InvalidOperationException($"More than one enabled ICreationActivity trait: {creationActivity.GetType().Name} and {ica.GetType().Name}");
var activity = ica.GetCreationActivity();
if (activity == null)
@@ -365,8 +369,7 @@ namespace OpenRA
public override bool Equals(object obj)
{
var o = obj as Actor;
return o != null && Equals(o);
return obj is Actor o && Equals(o);
}
public bool Equals(Actor other)
@@ -528,7 +531,7 @@ namespace OpenRA
{
// PERF: Avoid LINQ.
foreach (var targetable in Targetables)
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
if (targetable.TargetableBy(this, byActor))
return true;
return false;
@@ -589,7 +592,7 @@ namespace OpenRA
public int RevokeCondition(int token)
{
if (!conditionTokens.TryGetValue(token, out var condition))
throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, this));
throw new InvalidOperationException($"Attempting to revoke condition with invalid token {token} for {this}.");
conditionTokens.Remove(token);
UpdateConditionState(condition, token, true);
@@ -615,8 +618,8 @@ namespace OpenRA
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
{
get { return luaInterface.Value[runtime, keyValue]; }
set { luaInterface.Value[runtime, keyValue] = value; }
get => luaInterface.Value[runtime, keyValue];
set => luaInterface.Value[runtime, keyValue] = value;
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
@@ -629,7 +632,7 @@ namespace OpenRA
public LuaValue ToString(LuaRuntime runtime)
{
return "Actor ({0})".F(this);
return $"Actor ({this})";
}
public bool HasScriptProperty(string name)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -16,7 +16,7 @@ using OpenRA.Scripting;
namespace OpenRA
{
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
public readonly struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
{
// Coordinates are packed in a 32 bit signed int
// X and Y are 12 bits (signed): -2048...2047
@@ -25,13 +25,13 @@ namespace OpenRA
public readonly int Bits;
// X is padded to MSB, so bit shift does the correct sign extension
public int X { get { return Bits >> 20; } }
public int X => Bits >> 20;
// Align Y with a short, cast, then shift the rest of the way
// The signed short bit shift does the correct sign extension
public int Y { get { return ((short)(Bits >> 4)) >> 4; } }
public int Y => ((short)(Bits >> 4)) >> 4;
public byte Layer { get { return (byte)Bits; } }
public byte Layer => (byte)Bits;
public CPos(int bits) { Bits = bits; }
public CPos(int x, int y)
@@ -90,7 +90,7 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CPos a) || !right.TryGetClrValue(out CVec b))
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
throw new LuaException($"Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a + b);
}
@@ -99,7 +99,7 @@ namespace OpenRA
{
var rightType = right.WrappedClrType();
if (!left.TryGetClrValue(out CPos a))
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
if (rightType == typeof(CPos))
{
@@ -112,7 +112,7 @@ namespace OpenRA
return new LuaCustomClrObject(a - b);
}
throw new LuaException("Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType.Name));
throw new LuaException($"Attempted to call CPos.Subtract(CPos, (CPos|CVec)) with invalid arguments ({left.WrappedClrType().Name}, {rightType.Name})");
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
@@ -132,14 +132,11 @@ namespace OpenRA
case "X": return X;
case "Y": return Y;
case "Layer": return Layer;
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
default: throw new LuaException($"CPos does not define a member '{key}'");
}
}
set
{
throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
}
set => throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
}
#endregion

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -17,7 +17,7 @@ using OpenRA.Scripting;
namespace OpenRA
{
public struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
public readonly struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CVec>
{
public readonly int X, Y;
@@ -42,8 +42,8 @@ namespace OpenRA
public CVec Sign() { return new CVec(Math.Sign(X), Math.Sign(Y)); }
public CVec Abs() { return new CVec(Math.Abs(X), Math.Abs(Y)); }
public int LengthSquared { get { return X * X + Y * Y; } }
public int Length { get { return Exts.ISqrt(LengthSquared); } }
public int LengthSquared => X * X + Y * Y;
public int Length => Exts.ISqrt(LengthSquared);
public CVec Clamp(Rectangle r)
{
@@ -76,7 +76,7 @@ namespace OpenRA
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
throw new LuaException($"Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a + b);
}
@@ -84,7 +84,7 @@ namespace OpenRA
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
if (!left.TryGetClrValue(out CVec a) || !right.TryGetClrValue(out CVec b))
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
throw new LuaException($"Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({left.WrappedClrType().Name}, {right.WrappedClrType().Name})");
return new LuaCustomClrObject(a - b);
}
@@ -110,14 +110,11 @@ namespace OpenRA
{
case "X": return X;
case "Y": return Y;
default: throw new LuaException("CVec does not define a member '{0}'".F(key));
default: throw new LuaException($"CVec does not define a member '{key}'");
}
}
set
{
throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
}
set => throw new LuaException("CVec is read-only. Use CVec.New to create a new value");
}
#endregion

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -15,6 +15,6 @@ namespace OpenRA
{
public class DefaultPlayer : IGlobalModData
{
public readonly Color Color = Color.FromAhsl(0, 0, 238);
public readonly Color Color = Color.FromArgb(0xEE, 0xEE, 0xEE);
}
}

View File

@@ -1,96 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.ComponentModel;
using System.Net;
namespace OpenRA
{
public class Download
{
readonly object syncObject = new object();
WebClient wc;
public static string FormatErrorMessage(Exception e)
{
var ex = e as WebException;
if (ex == null)
return e.Message;
switch (ex.Status)
{
case WebExceptionStatus.RequestCanceled:
return "Cancelled";
case WebExceptionStatus.NameResolutionFailure:
return "DNS lookup failed";
case WebExceptionStatus.Timeout:
return "Connection timeout";
case WebExceptionStatus.ConnectFailure:
return "Cannot connect to remote server";
case WebExceptionStatus.ProtocolError:
return "File not found on remote server";
default:
return ex.Message;
}
}
void EnableTLS12OnWindows()
{
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
// so we must use the enum's constant value directly
if (Platform.CurrentPlatform == PlatformType.Windows)
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
}
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadFileAsync(new Uri(url), path);
}
}
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadDataAsync(new Uri(url));
}
}
void DisposeWebClient()
{
lock (syncObject)
{
wc.Dispose();
wc = null;
}
}
public void CancelAsync()
{
lock (syncObject)
wc?.CancelAsync();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -290,10 +290,15 @@ namespace OpenRA
}
}
public ExternalMod this[string key] { get { return mods[key]; } }
public int Count { get { return mods.Count; } }
public ICollection<string> Keys { get { return mods.Keys; } }
public ICollection<ExternalMod> Values { get { return mods.Values; } }
public ExternalMod this[string key] => mods[key];
public int Count => mods.Count;
public ICollection<string> Keys => mods.Keys;
public ICollection<ExternalMod> Values => mods.Values;
IEnumerable<string> IReadOnlyDictionary<string, ExternalMod>.Keys => ((IReadOnlyDictionary<string, ExternalMod>)mods).Keys;
IEnumerable<ExternalMod> IReadOnlyDictionary<string, ExternalMod>.Values => ((IReadOnlyDictionary<string, ExternalMod>)mods).Values;
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
public IEnumerator<KeyValuePair<string, ExternalMod>> GetEnumerator() { return mods.GetEnumerator(); }
public bool TryGetValue(string key, out ExternalMod value) { return mods.TryGetValue(key, out value); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -159,7 +159,7 @@ namespace OpenRA
if (xs.Count == 0)
{
if (throws)
throw new ArgumentException("Collection must not be empty.", "ts");
throw new ArgumentException("Collection must not be empty.", nameof(ts));
else
return default(T);
}
@@ -236,7 +236,7 @@ namespace OpenRA
{
if (!e.MoveNext())
if (throws)
throw new ArgumentException("Collection must not be empty.", "ts");
throw new ArgumentException("Collection must not be empty.", nameof(ts));
else
return default(T);
t = e.Current;
@@ -278,7 +278,7 @@ namespace OpenRA
public static int ISqrt(int number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
{
if (number < 0)
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
return (int)ISqrt((uint)number, round);
}
@@ -319,7 +319,7 @@ namespace OpenRA
public static long ISqrt(long number, ISqrtRoundMode round = ISqrtRoundMode.Floor)
{
if (number < 0)
throw new InvalidOperationException("Attempted to calculate the square root of a negative integer: {0}".F(number));
throw new InvalidOperationException($"Attempted to calculate the square root of a negative integer: {number}");
return (long)ISqrt((ulong)number, round);
}
@@ -429,8 +429,8 @@ namespace OpenRA
// If any duplicates were found, throw a descriptive error
if (dupKeys.Count > 0)
{
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => "{0}: [{1}]".F(logKey(p.Key), string.Join(",", p.Value))));
var msg = "{0}, duplicate values found for the following keys: {1}".F(debugName, badKeysFormatted);
var badKeysFormatted = string.Join(", ", dupKeys.Select(p => $"{logKey(p.Key)}: [{string.Join(",", p.Value)}]"));
var msg = $"{debugName}, duplicate values found for the following keys: {badKeysFormatted}";
throw new ArgumentException(msg);
}
@@ -511,8 +511,7 @@ namespace OpenRA
public static bool IsTraitEnabled<T>(this T trait)
{
var disabledTrait = trait as IDisabledTrait;
return disabledTrait == null || !disabledTrait.IsTraitDisabled;
return !(trait is IDisabledTrait disabledTrait) || !disabledTrait.IsTraitDisabled;
}
public static T FirstEnabledTraitOrDefault<T>(this IEnumerable<T> ts)
@@ -534,6 +533,50 @@ namespace OpenRA
return default(T);
}
public static LineSplitEnumerator SplitLines(this string str, char separator)
{
return new LineSplitEnumerator(str.AsSpan(), separator);
}
}
public ref struct LineSplitEnumerator
{
ReadOnlySpan<char> str;
readonly char separator;
public LineSplitEnumerator(ReadOnlySpan<char> str, char separator)
{
this.str = str;
this.separator = separator;
Current = default;
}
public LineSplitEnumerator GetEnumerator() => this;
public bool MoveNext()
{
var span = str;
// Reach the end of the string
if (span.Length == 0)
return false;
var index = span.IndexOf(separator);
if (index == -1)
{
// The remaining string is an empty string
str = ReadOnlySpan<char>.Empty;
Current = span;
return true;
}
Current = span.Slice(0, index);
str = span.Slice(index + 1);
return true;
}
public ReadOnlySpan<char> Current { get; private set; }
}
public static class Enum<T>

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -25,6 +25,8 @@ namespace OpenRA
{
public static class FieldLoader
{
const char SplitComma = ',';
[Serializable]
public class MissingFieldsException : YamlException
{
@@ -56,26 +58,465 @@ namespace OpenRA
public static Func<string, Type, string, object> InvalidValueAction = (s, t, f) =>
{
throw new YamlException("FieldLoader: Cannot parse `{0}` into `{1}.{2}` ".F(s, f, t));
throw new YamlException($"FieldLoader: Cannot parse `{s}` into `{f}.{t}` ");
};
public static Action<string, Type> UnknownFieldAction = (s, f) =>
{
throw new NotImplementedException("FieldLoader: Missing field `{0}` on `{1}`".F(s, f.Name));
throw new NotImplementedException($"FieldLoader: Missing field `{s}` on `{f.Name}`");
};
static readonly ConcurrentCache<Type, FieldLoadInfo[]> TypeLoadInfo =
new ConcurrentCache<Type, FieldLoadInfo[]>(BuildTypeLoadInfo);
static readonly ConcurrentCache<MemberInfo, bool> MemberHasTranslateAttribute =
new ConcurrentCache<MemberInfo, bool>(member => member.HasAttribute<TranslateAttribute>());
static readonly ConcurrentCache<string, BooleanExpression> BooleanExpressionCache =
new ConcurrentCache<string, BooleanExpression>(expression => new BooleanExpression(expression));
static readonly ConcurrentCache<string, IntegerExpression> IntegerExpressionCache =
new ConcurrentCache<string, IntegerExpression>(expression => new IntegerExpression(expression));
static readonly object TranslationsLock = new object();
static Dictionary<string, string> translations;
static readonly Dictionary<Type, Func<string, Type, string, MemberInfo, object>> TypeParsers =
new Dictionary<Type, Func<string, Type, string, MemberInfo, object>>()
{
{ typeof(int), ParseInt },
{ typeof(ushort), ParseUShort },
{ typeof(long), ParseLong },
{ typeof(float), ParseFloat },
{ typeof(decimal), ParseDecimal },
{ typeof(string), ParseString },
{ typeof(Color), ParseColor },
{ typeof(Hotkey), ParseHotkey },
{ typeof(HotkeyReference), ParseHotkeyReference },
{ typeof(WDist), ParseWDist },
{ typeof(WVec), ParseWVec },
{ typeof(WVec[]), ParseWVecArray },
{ typeof(WPos), ParseWPos },
{ typeof(WAngle), ParseWAngle },
{ typeof(WRot), ParseWRot },
{ typeof(CPos), ParseCPos },
{ typeof(CVec), ParseCVec },
{ typeof(CVec[]), ParseCVecArray },
{ typeof(BooleanExpression), ParseBooleanExpression },
{ typeof(IntegerExpression), ParseIntegerExpression },
{ typeof(Enum), ParseEnum },
{ typeof(bool), ParseBool },
{ typeof(int2[]), ParseInt2Array },
{ typeof(Size), ParseSize },
{ typeof(int2), ParseInt2 },
{ typeof(float2), ParseFloat2 },
{ typeof(float3), ParseFloat3 },
{ typeof(Rectangle), ParseRectangle },
{ typeof(DateTime), ParseDateTime }
};
static readonly Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>> GenericTypeParsers =
new Dictionary<Type, Func<string, Type, string, MiniYaml, MemberInfo, object>>()
{
{ typeof(HashSet<>), ParseHashSetOrList },
{ typeof(List<>), ParseHashSetOrList },
{ typeof(Dictionary<,>), ParseDictionary },
{ typeof(BitSet<>), ParseBitSet },
{ typeof(Nullable<>), ParseNullable },
};
static object ParseInt(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseUShort(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseLong(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01f : 1f);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseDecimal(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01m : 1m);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseString(string fieldName, Type fieldType, string value, MemberInfo field)
{
return value;
}
static object ParseColor(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null && Color.TryParse(value, out var color))
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHotkey(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Hotkey.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHotkeyReference(string fieldName, Type fieldType, string value, MemberInfo field)
{
return Game.ModData.Hotkeys[value];
}
static object ParseWDist(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (WDist.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWVec(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WVec(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length % 3 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new WVec[parts.Length / 3];
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx)
&& WDist.TryParse(parts[3 * i + 1], out var ry)
&& WDist.TryParse(parts[3 * i + 2], out var rz))
vecs[i] = new WVec(rx, ry, rz);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWPos(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx)
&& WDist.TryParse(parts[1], out var ry)
&& WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWAngle(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseWRot(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr)
&& Exts.TryParseIntegerInvariant(parts[1], out var rp)
&& Exts.TryParseIntegerInvariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCPos(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCVec(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseCVecArray(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new CVec[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx)
&& int.TryParse(parts[2 * i + 1], out var ry))
vecs[i] = new CVec(rx, ry);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseBooleanExpression(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
try
{
return BooleanExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseIntegerExpression(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
try
{
return IntegerExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseEnum(string fieldName, Type fieldType, string value, MemberInfo field)
{
try
{
return Enum.Parse(fieldType, value, true);
}
catch (ArgumentException)
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
static object ParseBool(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
return result;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseInt2Array(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var ints = new int2[parts.Length / 2];
for (var i = 0; i < ints.Length; i++)
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
return ints;
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseSize(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseInt2(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return InvalidValueAction(value, fieldType, fieldName);
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat2(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
float xx = 0;
float yy = 0;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
return new float2(xx, yy);
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseFloat3(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
// z component is optional for compatibility with older float2 definitions
float z = 0;
if (parts.Length > 2)
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
return new float3(x, y, z);
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseRectangle(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
return new Rectangle(
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseIntegerInvariant(parts[2]),
Exts.ParseIntegerInvariant(parts[3]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseDateTime(string fieldName, Type fieldType, string value, MemberInfo field)
{
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
return dt;
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseHashSetOrList(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
return set;
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
var addMethod = fieldType.GetMethod("Add", fieldType.GetGenericArguments());
for (var i = 0; i < parts.Length; i++)
addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) });
return set;
}
static object ParseDictionary(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
var dict = Activator.CreateInstance(fieldType);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod("Add", arguments);
foreach (var node in yaml.Nodes)
{
var key = GetValue(fieldName, arguments[0], node.Key, field);
var val = GetValue(fieldName, arguments[1], node.Value, field);
addMethod.Invoke(dict, new[] { key, val });
}
return dict;
}
static object ParseBitSet(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
if (value != null)
{
var parts = value.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
}
return InvalidValueAction(value, fieldType, fieldName);
}
static object ParseNullable(string fieldName, Type fieldType, string value, MiniYaml yaml, MemberInfo field)
{
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
public static void Load(object self, MiniYaml my)
{
@@ -180,402 +621,46 @@ namespace OpenRA
public static object GetValue(string fieldName, Type fieldType, MiniYaml yaml, MemberInfo field)
{
var value = yaml.Value?.Trim();
if (fieldType.IsGenericType)
{
if (GenericTypeParsers.TryGetValue(fieldType.GetGenericTypeDefinition(), out var parseFuncGeneric))
return parseFuncGeneric(fieldName, fieldType, value, yaml, field);
}
else
{
if (TypeParsers.TryGetValue(fieldType, out var parseFunc))
return parseFunc(fieldName, fieldType, value, field);
if (fieldType == typeof(int))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(ushort))
{
if (ushort.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
if (fieldType == typeof(long))
{
if (long.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float))
{
if (value != null && float.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01f : 1f);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(decimal))
{
if (value != null && decimal.TryParse(value.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
return res * (value.Contains('%') ? 0.01m : 1m);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(string))
{
if (field != null && MemberHasTranslateAttribute[field] && value != null)
return Regex.Replace(value, "@[^@]+@", m => Translate(m.Value.Substring(1, m.Value.Length - 2)), RegexOptions.Compiled);
return value;
}
else if (fieldType == typeof(Color))
{
if (value != null && Color.TryParse(value, out var color))
return color;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(Hotkey))
{
if (Hotkey.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(HotkeyReference))
{
return Game.ModData.Hotkeys[value];
}
else if (fieldType == typeof(WDist))
{
if (WDist.TryParse(value, out var res))
return res;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WVec))
{
if (value != null)
if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
{
var parts = value.Split(',');
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WVec(rx, ry, rz);
}
if (value == null)
return Array.CreateInstance(fieldType.GetElementType(), 0);
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
var parts = value.Split(SplitComma, options);
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
for (var i = 0; i < parts.Length; i++)
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WVec[]))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length % 3 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new WVec[parts.Length / 3];
for (var i = 0; i < vecs.Length; ++i)
{
if (WDist.TryParse(parts[3 * i], out var rx) && WDist.TryParse(parts[3 * i + 1], out var ry) && WDist.TryParse(parts[3 * i + 2], out var rz))
vecs[i] = new WVec(rx, ry, rz);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WPos))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length == 3)
{
if (WDist.TryParse(parts[0], out var rx) && WDist.TryParse(parts[1], out var ry) && WDist.TryParse(parts[2], out var rz))
return new WPos(rx, ry, rz);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WAngle))
{
if (Exts.TryParseIntegerInvariant(value, out var res))
return new WAngle(res);
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(WRot))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length == 3)
{
if (Exts.TryParseIntegerInvariant(parts[0], out var rr) && Exts.TryParseIntegerInvariant(parts[1], out var rp) && Exts.TryParseIntegerInvariant(parts[2], out var ry))
return new WRot(new WAngle(rr), new WAngle(rp), new WAngle(ry));
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CPos))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new CPos(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CVec))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new CVec(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(CVec[]))
{
if (value != null)
{
var parts = value.Split(',');
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var vecs = new CVec[parts.Length / 2];
for (var i = 0; i < vecs.Length; i++)
{
if (int.TryParse(parts[2 * i], out var rx) && int.TryParse(parts[2 * i + 1], out var ry))
vecs[i] = new CVec(rx, ry);
}
return vecs;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(BooleanExpression))
{
if (value != null)
{
try
{
return BooleanExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(IntegerExpression))
{
if (value != null)
{
try
{
return IntegerExpressionCache[value];
}
catch (InvalidDataException e)
{
throw new YamlException(e.Message);
}
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsEnum)
var conv = TypeDescriptor.GetConverter(fieldType);
if (conv.CanConvertFrom(typeof(string)))
{
try
{
return Enum.Parse(fieldType, value, true);
return conv.ConvertFromInvariantString(value);
}
catch (ArgumentException)
catch
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
else if (fieldType == typeof(bool))
{
if (bool.TryParse(value.ToLowerInvariant(), out var result))
return result;
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(int2[]))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length % 2 != 0)
return InvalidValueAction(value, fieldType, fieldName);
var ints = new int2[parts.Length / 2];
for (var i = 0; i < ints.Length; i++)
ints[i] = new int2(Exts.ParseIntegerInvariant(parts[2 * i]), Exts.ParseIntegerInvariant(parts[2 * i + 1]));
return ints;
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsArray && fieldType.GetArrayRank() == 1)
{
if (value == null)
return Array.CreateInstance(fieldType.GetElementType(), 0);
var options = field != null && field.HasAttribute<AllowEmptyEntriesAttribute>() ?
StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries;
var parts = value.Split(new char[] { ',' }, options);
var ret = Array.CreateInstance(fieldType.GetElementType(), parts.Length);
for (var i = 0; i < parts.Length; i++)
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
}
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
{
var set = Activator.CreateInstance(fieldType);
if (value == null)
return set;
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var addMethod = fieldType.GetMethod("Add", fieldType.GetGenericArguments());
for (var i = 0; i < parts.Length; i++)
addMethod.Invoke(set, new[] { GetValue(fieldName, fieldType.GetGenericArguments()[0], parts[i].Trim(), field) });
return set;
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
var dict = Activator.CreateInstance(fieldType);
var arguments = fieldType.GetGenericArguments();
var addMethod = fieldType.GetMethod("Add", arguments);
foreach (var node in yaml.Nodes)
{
var key = GetValue(fieldName, arguments[0], node.Key, field);
var val = GetValue(fieldName, arguments[1], node.Value, field);
addMethod.Invoke(dict, new[] { key, val });
}
return dict;
}
else if (fieldType == typeof(Size))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new Size(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(int2))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return InvalidValueAction(value, fieldType, fieldName);
return new int2(Exts.ParseIntegerInvariant(parts[0]), Exts.ParseIntegerInvariant(parts[1]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float2))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float xx = 0;
float yy = 0;
if (float.TryParse(parts[0].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var res))
xx = res * (parts[0].Contains('%') ? 0.01f : 1f);
if (float.TryParse(parts[1].Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out res))
yy = res * (parts[1].Contains('%') ? 0.01f : 1f);
return new float2(xx, yy);
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(float3))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
float.TryParse(parts[0], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var x);
float.TryParse(parts[1], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out var y);
// z component is optional for compatibility with older float2 definitions
float z = 0;
if (parts.Length > 2)
float.TryParse(parts[2], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out z);
return new float3(x, y, z);
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType == typeof(Rectangle))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
return new Rectangle(
Exts.ParseIntegerInvariant(parts[0]),
Exts.ParseIntegerInvariant(parts[1]),
Exts.ParseIntegerInvariant(parts[2]),
Exts.ParseIntegerInvariant(parts[3]));
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>))
{
if (value != null)
{
var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var ctor = fieldType.GetConstructor(new[] { typeof(string[]) });
return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() });
}
return InvalidValueAction(value, fieldType, fieldName);
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
return null;
var innerType = fieldType.GetGenericArguments().First();
var innerValue = GetValue("Nullable<T>", innerType, value, field);
return fieldType.GetConstructor(new[] { innerType }).Invoke(new[] { innerValue });
}
else if (fieldType == typeof(DateTime))
{
if (DateTime.TryParseExact(value, "yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dt))
return dt;
return InvalidValueAction(value, fieldType, fieldName);
}
else
{
var conv = TypeDescriptor.GetConverter(fieldType);
if (conv.CanConvertFrom(typeof(string)))
{
try
{
return conv.ConvertFromInvariantString(value);
}
catch
{
return InvalidValueAction(value, fieldType, fieldName);
}
}
}
UnknownFieldAction("[Type] {0}".F(value), fieldType);
UnknownFieldAction($"[Type] {value}", fieldType);
return null;
}
@@ -661,7 +746,7 @@ namespace OpenRA
{
public static readonly SerializeAttribute Default = new SerializeAttribute(true);
public bool IsDefault { get { return this == Default; } }
public bool IsDefault => this == Default;
public readonly bool Serialize;
public string YamlName;
@@ -686,7 +771,7 @@ namespace OpenRA
{
var method = type.GetMethod(Loader, Flags);
if (method == null)
throw new InvalidOperationException("{0} does not specify a loader function '{1}'".F(type.Name, Loader));
throw new InvalidOperationException($"{type.Name} does not specify a loader function '{Loader}'");
return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), method);
}
@@ -694,34 +779,8 @@ namespace OpenRA
return null;
}
}
public static string Translate(string key)
{
if (string.IsNullOrEmpty(key))
return key;
lock (TranslationsLock)
{
if (translations == null)
return key;
if (!translations.TryGetValue(key, out var value))
return key;
return value;
}
}
public static void SetTranslations(IDictionary<string, string> translations)
{
lock (TranslationsLock)
FieldLoader.translations = new Dictionary<string, string>(translations);
}
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class TranslateAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -72,32 +72,14 @@ namespace OpenRA
return "";
var t = v.GetType();
if (t == typeof(Color))
{
return ((Color)v).ToString();
}
if (t == typeof(Rectangle))
{
var r = (Rectangle)v;
return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height);
}
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>))
{
return ((IEnumerable<string>)v).Select(FormatValue).JoinWith(", ");
}
if (t.IsArray && t.GetArrayRank() == 1)
{
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
{
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
// This is only for documentation generation
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
@@ -112,17 +94,14 @@ namespace OpenRA
var formattedKey = FormatValue(key);
var formattedValue = FormatValue(value);
result += "{0}: {1}{2}".F(formattedKey, formattedValue, Environment.NewLine);
result += $"{formattedKey}: {formattedValue}{Environment.NewLine}";
}
return result;
}
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Primitives.Cache<,>))
return ""; // TODO
if (t == typeof(DateTime))
return ((DateTime)v).ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
if (v is DateTime d)
return d.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
// Try the TypeConverter
var conv = TypeDescriptor.GetConverter(t);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -17,6 +17,7 @@ using System.Net;
using System.Text;
using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.FileFormats
@@ -25,12 +26,15 @@ namespace OpenRA.FileFormats
{
static readonly byte[] Signature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
public int Width { get; set; }
public int Height { get; set; }
public Color[] Palette { get; set; }
public byte[] Data { get; set; }
public int Width { get; private set; }
public int Height { get; private set; }
public Color[] Palette { get; private set; }
public byte[] Data { get; private set; }
public SpriteFrameType Type { get; private set; }
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
public int PixelStride => Type == SpriteFrameType.Indexed8 ? 1 : Type == SpriteFrameType.Rgb24 ? 3 : 4;
public Png(Stream s)
{
if (!Verify(s))
@@ -38,9 +42,8 @@ namespace OpenRA.FileFormats
s.Position += 8;
var headerParsed = false;
var isPaletted = false;
var is24Bit = false;
var data = new List<byte>();
Type = SpriteFrameType.Rgba32;
while (true)
{
@@ -65,14 +68,12 @@ namespace OpenRA.FileFormats
var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadByte();
isPaletted = IsPaletted(bitDepth, colorType);
is24Bit = colorType == PngColorType.Color;
if (IsPaletted(bitDepth, colorType))
Type = SpriteFrameType.Indexed8;
else if (colorType == PngColorType.Color)
Type = SpriteFrameType.Rgb24;
var dataLength = Width * Height;
if (!isPaletted)
dataLength *= 4;
Data = new byte[dataLength];
Data = new byte[Width * Height * PixelStride];
var compression = ms.ReadByte();
/*var filter = */ms.ReadByte();
@@ -133,39 +134,28 @@ namespace OpenRA.FileFormats
{
using (var ds = new InflaterInputStream(ns))
{
var pxStride = isPaletted ? 1 : is24Bit ? 3 : 4;
var srcStride = Width * pxStride;
var destStride = Width * (isPaletted ? 1 : 4);
var pxStride = PixelStride;
var rowStride = Width * pxStride;
var prevLine = new byte[srcStride];
var prevLine = new byte[rowStride];
for (var y = 0; y < Height; y++)
{
var filter = (PngFilter)ds.ReadByte();
var line = ds.ReadBytes(srcStride);
var line = ds.ReadBytes(rowStride);
for (var i = 0; i < srcStride; i++)
for (var i = 0; i < rowStride; i++)
line[i] = i < pxStride
? UnapplyFilter(filter, line[i], 0, prevLine[i], 0)
: UnapplyFilter(filter, line[i], line[i - pxStride], prevLine[i], prevLine[i - pxStride]);
if (is24Bit)
{
// Fold alpha channel into RGB data
for (var i = 0; i < line.Length / 3; i++)
{
Array.Copy(line, 3 * i, Data, y * destStride + 4 * i, 3);
Data[y * destStride + 4 * i + 3] = 255;
}
}
else
Array.Copy(line, 0, Data, y * destStride, line.Length);
Array.Copy(line, 0, Data, y * rowStride, rowStride);
prevLine = line;
}
}
}
if (isPaletted && Palette == null)
if (Type == SpriteFrameType.Indexed8 && Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
return;
@@ -175,7 +165,7 @@ namespace OpenRA.FileFormats
}
}
public Png(byte[] data, int width, int height, Color[] palette = null,
public Png(byte[] data, SpriteFrameType type, int width, int height, Color[] palette = null,
Dictionary<string, string> embeddedData = null)
{
var expectLength = width * height;
@@ -185,11 +175,46 @@ namespace OpenRA.FileFormats
if (data.Length != expectLength)
throw new InvalidDataException("Input data does not match expected length");
Type = type;
Width = width;
Height = height;
Palette = palette;
Data = data;
switch (type)
{
case SpriteFrameType.Indexed8:
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
// Data is already in a compatible format
Data = data;
if (type == SpriteFrameType.Indexed8)
Palette = palette;
break;
}
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
{
// Convert to big endian
Data = new byte[data.Length];
var stride = PixelStride;
for (var i = 0; i < width * height; i++)
{
Data[stride * i] = data[stride * i + 2];
Data[stride * i + 1] = data[stride * i + 1];
Data[stride * i + 2] = data[stride * i + 0];
if (type == SpriteFrameType.Bgra32)
Data[stride * i + 3] = data[stride * i + 3];
}
break;
}
default:
throw new InvalidDataException($"Unhandled SpriteFrameType {type}");
}
if (embeddedData != null)
EmbeddedData = embeddedData;
@@ -274,9 +299,8 @@ namespace OpenRA.FileFormats
header.Write(IPAddress.HostToNetworkOrder(Height));
header.WriteByte(8); // Bit depth
var colorType = Palette != null
? PngColorType.Indexed | PngColorType.Color
: PngColorType.Color | PngColorType.Alpha;
var colorType = Type == SpriteFrameType.Indexed8 ? PngColorType.Indexed | PngColorType.Color :
Type == SpriteFrameType.Rgb24 ? PngColorType.Color : PngColorType.Color | PngColorType.Alpha;
header.WriteByte((byte)colorType);
header.WriteByte(0); // Compression
@@ -286,7 +310,7 @@ namespace OpenRA.FileFormats
WritePngChunk(output, "IHDR", header);
}
bool alphaPalette = false;
var alphaPalette = false;
if (Palette != null)
{
using (var palette = new MemoryStream())
@@ -318,12 +342,12 @@ namespace OpenRA.FileFormats
{
using (var compressed = new DeflaterOutputStream(data))
{
var stride = Width * (Palette != null ? 1 : 4);
var rowStride = Width * PixelStride;
for (var y = 0; y < Height; y++)
{
// Write uncompressed scanlines for simplicity
compressed.WriteByte(0);
compressed.Write(Data, y * stride, stride);
compressed.Write(Data, y * rowStride, rowStride);
}
compressed.Flush();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -28,7 +28,7 @@ namespace OpenRA.FileFormats
public ReplayMetadata(GameInformation info)
{
if (info == null)
throw new ArgumentNullException("info");
throw new ArgumentNullException(nameof(info));
GameInfo = info;
}
@@ -44,7 +44,7 @@ namespace OpenRA.FileFormats
// Read version
var version = fs.ReadInt32();
if (version != MetaVersion)
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
throw new NotSupportedException($"Metadata version {version} is not supported");
// Read game info (max 100K limit as a safeguard against corrupted files)
var data = fs.ReadString(Encoding.UTF8, 1024 * 100);

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -28,7 +28,7 @@ namespace OpenRA.FileSystem
public class FileSystem : IReadOnlyFileSystem
{
public IEnumerable<IReadOnlyPackage> MountedPackages { get { return mountedPackages.Keys; } }
public IEnumerable<IReadOnlyPackage> MountedPackages => mountedPackages.Keys;
readonly Dictionary<IReadOnlyPackage, int> mountedPackages = new Dictionary<IReadOnlyPackage, int>();
readonly Dictionary<string, IReadOnlyPackage> explicitMounts = new Dictionary<string, IReadOnlyPackage>();
readonly string modID;
@@ -95,7 +95,7 @@ namespace OpenRA.FileSystem
name = name.Substring(1);
if (!installedMods.TryGetValue(name, out var mod))
throw new InvalidOperationException("Could not load mod '{0}'. Available mods: {1}".F(name, installedMods.Keys.JoinWith(", ")));
throw new InvalidOperationException($"Could not load mod '{name}'. Available mods: {installedMods.Keys.JoinWith(", ")}");
package = mod.Package;
modPackages.Add(package);
@@ -104,7 +104,7 @@ namespace OpenRA.FileSystem
{
package = OpenPackage(name);
if (package == null)
throw new InvalidOperationException("Could not open package '{0}', file not found or its format is not supported.".F(name));
throw new InvalidOperationException($"Could not open package '{name}', file not found or its format is not supported.");
}
Mount(package, explicitName);
@@ -203,7 +203,7 @@ namespace OpenRA.FileSystem
public Stream Open(string filename)
{
if (!TryOpen(filename, out var s))
throw new FileNotFoundException("File not found: {0}".F(filename), filename);
throw new FileNotFoundException($"File not found: {filename}", filename);
return s;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -27,7 +27,7 @@ namespace OpenRA.FileSystem
Directory.CreateDirectory(path);
}
public string Name { get { return path; } }
public string Name => path;
public IEnumerable<string> Contents
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -141,7 +141,7 @@ namespace OpenRA.FileSystem
sealed class ZipFolder : IReadOnlyPackage
{
public string Name { get { return path; } }
public string Name => path;
public ReadOnlyZipFile Parent { get; private set; }
readonly string path;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -22,7 +22,7 @@ namespace OpenRA
public class Fonts : IGlobalModData
{
[FieldLoader.LoadUsing("LoadFonts")]
[FieldLoader.LoadUsing(nameof(LoadFonts))]
public readonly Dictionary<string, FontData> FontList;
static object LoadFonts(MiniYaml y)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -19,7 +19,6 @@ using System.Net;
using System.Reflection;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
@@ -32,7 +31,6 @@ namespace OpenRA
public static class Game
{
public const int NetTickScale = 3; // 120 ms net tick for 40 ms local tick
public const int Timestep = 40;
public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms
public static InstalledMods Mods { get; private set; }
@@ -42,6 +40,7 @@ namespace OpenRA
public static Settings Settings;
public static CursorManager Cursor;
public static bool HideCursor;
static WorldRenderer worldRenderer;
static string modLaunchWrapper;
@@ -56,7 +55,6 @@ namespace OpenRA
public static string EngineVersion { get; private set; }
public static LocalPlayerProfile LocalPlayerProfile;
static Task discoverNat;
static bool takeScreenshot = false;
static Benchmark benchmark = null;
@@ -64,12 +62,18 @@ namespace OpenRA
public static OrderManager JoinServer(ConnectionTarget endpoint, string password, bool recordReplay = true)
{
var connection = new NetworkConnection(endpoint);
var newConnection = new NetworkConnection(endpoint);
if (recordReplay)
connection.StartRecording(() => { return TimestampedFilename(); });
newConnection.StartRecording(() => { return TimestampedFilename(); });
var om = new OrderManager(endpoint, password, connection);
var om = new OrderManager(newConnection);
JoinInner(om);
CurrentServerSettings.Password = password;
CurrentServerSettings.Target = endpoint;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager, password, newConnection);
return om;
}
@@ -81,34 +85,53 @@ namespace OpenRA
static void JoinInner(OrderManager om)
{
OrderManager?.Dispose();
// HACK: The shellmap World and OrderManager are owned by the main menu's WorldRenderer instead of Game.
// This allows us to switch Game.OrderManager from the shellmap to the new network connection when joining
// a lobby, while keeping the OrderManager that runs the shellmap intact.
// A matching check in World.Dispose (which is called by WorldRenderer.Dispose) makes sure that we dispose
// the shellmap's OM when a lobby game actually starts.
if (OrderManager?.World == null || OrderManager.World.Type != WorldType.Shellmap)
OrderManager?.Dispose();
OrderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(OrderManager);
}
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager(new ConnectionTarget(), "", new ReplayConnection(replayFile)));
JoinInner(new OrderManager(new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager(new ConnectionTarget(), "", new EchoConnection()));
JoinInner(new OrderManager(new EchoConnection()));
// Add a spectator client for the local player
// On the shellmap this player is controlling the map via scripted orders
OrderManager.LobbyInfo.Clients.Add(new Session.Client
{
Index = OrderManager.Connection.LocalClientId,
Name = Settings.Player.Name,
PreferredColor = Settings.Player.Color,
Color = Settings.Player.Color,
Faction = "Random",
SpawnPoint = 0,
Team = 0,
State = Session.ClientState.Ready
});
}
// More accurate replacement for Environment.TickCount
static Stopwatch stopwatch = Stopwatch.StartNew();
public static long RunTime { get { return stopwatch.ElapsedMilliseconds; } }
public static long RunTime => stopwatch.ElapsedMilliseconds;
public static int RenderFrame = 0;
public static int NetFrameNumber { get { return OrderManager.NetFrameNumber; } }
public static int LocalTick { get { return OrderManager.LocalFrameNumber; } }
public static int NetFrameNumber => OrderManager.NetFrameNumber;
public static int LocalTick => OrderManager.LocalFrameNumber;
public static event Action<ConnectionTarget> OnRemoteDirectConnect = _ => { };
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
public static event Action<OrderManager, string, NetworkConnection> ConnectionStateChanged = (om, pass, conn) => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId { get { return OrderManager.Connection.LocalClientId; } }
public static int LocalClientId => OrderManager.Connection.LocalClientId;
public static void RemoteDirectConnect(ConnectionTarget endpoint)
{
@@ -164,6 +187,7 @@ namespace OpenRA
using (new PerfTimer("PrepareMap"))
map = ModData.PrepareMap(mapUID);
using (new PerfTimer("NewWorld"))
OrderManager.World = new World(ModData, map, OrderManager, type);
@@ -186,11 +210,9 @@ namespace OpenRA
Ui.MouseFocusWidget = null;
Ui.KeyboardFocusWidget = null;
OrderManager.LocalFrameNumber = 0;
OrderManager.LastTickTime = RunTime;
OrderManager.StartGame();
worldRenderer.RefreshPalette();
Cursor.SetCursor("default");
Cursor.SetCursor(ChromeMetrics.Get<string>("DefaultCursor"));
// Now loading is completed, now is the ideal time to run a GC and compact the LOH.
// - All the temporary garbage created during loading can be collected.
@@ -206,7 +228,7 @@ namespace OpenRA
public static void RestartGame()
{
var replay = OrderManager.Connection as ReplayConnection;
var replayName = replay != null ? replay.Filename : null;
var replayName = replay?.Filename;
var lobbyInfo = OrderManager.LobbyInfo;
// Reseed the RNG so this isn't an exact repeat of the last game
@@ -214,7 +236,7 @@ namespace OpenRA
var orders = new[]
{
Order.Command("sync_lobby {0}".F(lobbyInfo.Serialize())),
Order.Command($"sync_lobby {lobbyInfo.Serialize()}"),
Order.Command("startgame")
};
@@ -297,6 +319,7 @@ namespace OpenRA
EngineVersion = "Unknown";
Console.WriteLine("Engine version is {0}", EngineVersion);
Console.WriteLine("Runtime: {0}", Platform.RuntimeVersion);
// Special case handling of Game.Mod argument: if it matches a real filesystem path
// then we use this to override the mod search path, and replace it with the mod id
@@ -329,9 +352,16 @@ namespace OpenRA
try
{
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
var assembly = Assembly.LoadFile(rendererPath);
#if !MONO
var loader = new AssemblyLoader(rendererPath);
var platformType = loader.LoadDefaultAssembly().GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#else
var assembly = Assembly.LoadFile(rendererPath);
var platformType = assembly.GetTypes().SingleOrDefault(t => typeof(IPlatform).IsAssignableFrom(t));
#endif
if (platformType == null)
throw new InvalidOperationException("Platform dll must include exactly one IPlatform implementation.");
@@ -352,8 +382,7 @@ namespace OpenRA
}
}
if (Settings.Server.DiscoverNatDevices)
discoverNat = UPnP.DiscoverNatDevices(Settings.Server.NatDiscoveryTimeout);
Nat.Initialize();
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ?
@@ -399,13 +428,14 @@ namespace OpenRA
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
InitializeMod(modID, args);
Ui.InitializeTranslation();
}
public static void InitializeMod(string mod, Arguments args)
{
// Clear static state if we have switched mods
LobbyInfoChanged = () => { };
ConnectionStateChanged = om => { };
ConnectionStateChanged = (om, p, conn) => { };
BeforeGameStart = () => { };
OnRemoteDirectConnect = endpoint => { };
delayedActions = new ActionQueue();
@@ -429,7 +459,7 @@ namespace OpenRA
throw new InvalidOperationException("Game.Mod argument missing.");
if (!Mods.ContainsKey(mod))
throw new InvalidOperationException("Unknown or invalid mod '{0}'.".F(mod));
throw new InvalidOperationException($"Unknown or invalid mod '{mod}'.");
Console.WriteLine("Loading mod: {0}", mod);
@@ -442,19 +472,22 @@ namespace OpenRA
if (!ModData.LoadScreen.BeforeLoad())
return;
using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps();
ModData.InitializeLoaders(ModData.DefaultFileSystem);
Renderer.InitializeFonts(ModData);
using (new PerfTimer("LoadMaps"))
ModData.MapCache.LoadMaps();
var grid = ModData.Manifest.Contains<MapGrid>() ? ModData.Manifest.Get<MapGrid>() : null;
Renderer.InitializeDepthBuffer(grid);
Cursor?.Dispose();
Cursor = new CursorManager(ModData.CursorProvider);
var metadata = ModData.Manifest.Metadata;
if (!string.IsNullOrEmpty(metadata.WindowTitle))
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
PerfHistory.Items["render"].HasNormalTick = false;
PerfHistory.Items["batches"].HasNormalTick = false;
PerfHistory.Items["render_world"].HasNormalTick = false;
@@ -464,31 +497,18 @@ namespace OpenRA
JoinLocal();
try
{
discoverNat?.Wait();
}
catch (Exception e)
{
Console.WriteLine("NAT discovery failed: {0}", e.Message);
Log.Write("nat", e.ToString());
}
ChromeMetrics.TryGet("ChatMessageColor", out chatMessageColor);
ChromeMetrics.TryGet("SystemMessageColor", out systemMessageColor);
ModData.LoadScreen.StartGame(args);
}
public static void LoadEditor(string mapUid)
{
JoinLocal();
StartGame(mapUid, WorldType.Editor);
}
public static void LoadShellMap()
{
var shellmap = ChooseShellmap();
using (new PerfTimer("StartGame"))
{
StartGame(shellmap, WorldType.Shellmap);
@@ -543,8 +563,7 @@ namespace OpenRA
// Note: These delayed actions should only be used by widgets or disposing objects
// - things that depend on a particular world should be queuing them on the world actor.
static volatile ActionQueue delayedActions = new ActionQueue();
static Color systemMessageColor = Color.White;
static Color chatMessageColor = Color.White;
public static void RunAfterTick(Action a) { delayedActions.Add(a, RunTime); }
public static void RunAfterDelay(int delayMilliseconds, Action a) { delayedActions.Add(a, RunTime + delayMilliseconds); }
@@ -561,7 +580,7 @@ namespace OpenRA
Log.Write("debug", "Taking screenshot " + path);
Renderer.SaveScreenshot(path);
Debug("Saved screenshot " + filename);
TextNotificationsManager.Debug("Saved screenshot " + filename);
}
}
@@ -571,48 +590,29 @@ namespace OpenRA
var world = orderManager.World;
var uiTickDelta = tick - Ui.LastTickTime;
if (uiTickDelta >= Timestep)
if (Ui.LastTickTime.ShouldAdvance(tick))
{
// Explained below for the world tick calculation
var integralTickTimestep = (uiTickDelta / Timestep) * Timestep;
Ui.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : Timestep;
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, Ui.Tick);
Ui.LastTickTime.AdvanceTickTime(tick);
Sync.RunUnsynced(world, Ui.Tick);
Cursor.Tick();
}
var worldTimestep = world == null ? Timestep : world.IsLoadingGameSave ? 1 : world.Timestep;
var worldTickDelta = tick - orderManager.LastTickTime;
if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
if (orderManager.LastTickTime.ShouldAdvance(tick))
{
using (new PerfSample("tick_time"))
{
// Tick the world to advance the world time to match real time:
// If dt < TickJankThreshold then we should try and catch up by repeatedly ticking
// If dt >= TickJankThreshold then we should accept the jank and progress at the normal rate
// dt is rounded down to an integer tick count in order to preserve fractional tick components.
var integralTickTimestep = (worldTickDelta / worldTimestep) * worldTimestep;
orderManager.LastTickTime += integralTickTimestep >= TimestepJankThreshold ? integralTickTimestep : worldTimestep;
orderManager.LastTickTime.AdvanceTickTime(tick);
Sound.Tick();
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, orderManager.TickImmediate);
Sync.RunUnsynced(world, orderManager.TickImmediate);
if (world == null)
return;
var isNetTick = LocalTick % NetTickScale == 0;
if (!isNetTick || orderManager.IsReadyForNextFrame)
if (orderManager.TryTick())
{
++orderManager.LocalFrameNumber;
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
if (isNetTick)
orderManager.Tick();
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () =>
Sync.RunUnsynced(world, () =>
{
world.OrderGenerator.Tick(world);
});
@@ -621,12 +621,10 @@ namespace OpenRA
PerfHistory.Tick();
}
else if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = RunTime;
// Wait until we have done our first world Tick before TickRendering
if (orderManager.LocalFrameNumber > 0)
Sync.RunUnsynced(Settings.Debug.SyncCheckUnsyncedCode, world, () => world.TickRender(worldRenderer));
Sync.RunUnsynced(world, () => world.TickRender(worldRenderer));
}
benchmark?.Tick(LocalTick);
@@ -637,10 +635,10 @@ namespace OpenRA
{
PerformDelayedActions();
if (OrderManager.Connection.ConnectionState != lastConnectionState)
if (OrderManager.Connection is NetworkConnection nc && nc.ConnectionState != lastConnectionState)
{
lastConnectionState = OrderManager.Connection.ConnectionState;
ConnectionStateChanged(OrderManager);
lastConnectionState = nc.ConnectionState;
ConnectionStateChanged(OrderManager, null, nc);
}
InnerLogicTick(OrderManager);
@@ -777,9 +775,14 @@ namespace OpenRA
while (state == RunStatus.Running)
{
// Ideal time between logic updates. Timestep = 0 means the game is paused
// but we still call LogicTick() because it handles pausing internally.
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Timestep;
var logicInterval = Ui.Timestep;
var logicWorld = worldRenderer?.World;
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
logicInterval = logicWorld.IsLoadingGameSave ? 1 :
logicWorld.IsReplay ? logicWorld.ReplayTimestep :
logicWorld.Timestep;
// Ideal time between screen updates
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;
@@ -817,8 +820,7 @@ namespace OpenRA
var haveSomeTimeUntilNextLogic = now < nextLogic;
var isTimeToRender = now >= nextRender;
if ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender)
if (!Renderer.WindowIsSuspended && ((isTimeToRender && haveSomeTimeUntilNextLogic) || forceRender))
{
nextRender = now + renderInterval;
@@ -833,6 +835,19 @@ namespace OpenRA
RenderTick();
renderBeforeNextTick = false;
}
// Simulate a render tick if it was time to render but we skip actually rendering
if (Renderer.WindowIsSuspended && isTimeToRender)
{
// Make sure that nextUpdate is set to a proper minimum interval
nextRender = now + renderInterval;
// Still process SDL events to allow a restore to come through
Renderer.Window.PumpInput(new NullInputHandler());
// Ensure that we still logic tick despite not rendering
renderBeforeNextTick = false;
}
}
else
Thread.Sleep((int)(nextUpdate - now));
@@ -874,26 +889,6 @@ namespace OpenRA
state = RunStatus.Success;
}
public static void AddSystemLine(string text)
{
AddSystemLine("Battlefield Control", text);
}
public static void AddSystemLine(string name, string text)
{
OrderManager.AddChatLine(name, systemMessageColor, text, systemMessageColor);
}
public static void AddChatLine(string name, Color nameColor, string text)
{
OrderManager.AddChatLine(name, nameColor, text, chatMessageColor);
}
public static void Debug(string s, params object[] args)
{
AddSystemLine("Debug", string.Format(s, args));
}
public static void Disconnect()
{
OrderManager.World?.TraitDict.PrintReport();
@@ -965,8 +960,8 @@ namespace OpenRA
{
var orders = new List<Order>
{
Order.Command("option gamespeed {0}".F("default")),
Order.Command("state {0}".F(Session.ClientState.Ready))
Order.Command("option gamespeed default"),
Order.Command($"state {Session.ClientState.Ready}")
};
var path = Platform.ResolvePath(launchMap);
@@ -974,7 +969,7 @@ namespace OpenRA
ModData.MapCache.SingleOrDefault(m => m.Package.Name == path);
if (map == null)
throw new InvalidOperationException("Could not find map '{0}'.".F(launchMap));
throw new InvalidOperationException($"Could not find map '{launchMap}'.");
CreateAndStartLocalServer(map.Uid, orders);
}
@@ -988,4 +983,11 @@ namespace OpenRA
}
}
}
public static class CurrentServerSettings
{
public static string Password;
public static ConnectionTarget Target;
public static ExternalMod ServerExternalMod;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -33,12 +33,13 @@ namespace OpenRA
public DateTime EndTimeUtc;
/// <summary>Gets the game's duration, from the time the game started until the replay recording stopped.</summary>
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
public TimeSpan Duration => EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero;
public IList<Player> Players { get; private set; }
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
public MapPreview MapPreview => Game.ModData.MapCache[MapUid];
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
readonly Dictionary<OpenRA.Player, Player> playersByRuntime;
@@ -75,7 +76,7 @@ namespace OpenRA
}
catch (YamlException)
{
Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
Log.Write("debug", $"GameInformation deserialized invalid MiniYaml:\n{data}");
throw;
}
}
@@ -88,7 +89,7 @@ namespace OpenRA
};
for (var i = 0; i < Players.Count; i++)
nodes.Add(new MiniYamlNode("Player@{0}".F(i), FieldSaver.Save(Players[i])));
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
return nodes.WriteToString();
}
@@ -97,10 +98,10 @@ namespace OpenRA
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
{
if (runtimePlayer == null)
throw new ArgumentNullException("runtimePlayer");
throw new ArgumentNullException(nameof(runtimePlayer));
if (lobbyInfo == null)
throw new ArgumentNullException("lobbyInfo");
throw new ArgumentNullException(nameof(lobbyInfo));
// We don't care about spectators and map players
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
@@ -123,6 +124,7 @@ namespace OpenRA
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
Color = runtimePlayer.Color,
Team = client.Team,
Handicap = client.Handicap,
SpawnPoint = runtimePlayer.SpawnPoint,
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
@@ -166,6 +168,7 @@ namespace OpenRA
/// <summary>The team ID on start-up, or 0 if the player is not part of a team.</summary>
public int Team;
public int SpawnPoint;
public int Handicap;
/// <summary>True if the faction was chosen at random; otherwise, false.</summary>
public bool IsRandomFaction;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -61,7 +61,7 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException("Actor type {0}: {1}".F(name, e.Message));
throw new YamlException($"Actor type {name}: {e.Message}");
}
}
@@ -76,8 +76,7 @@ namespace OpenRA
static TraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my)
{
if (!string.IsNullOrEmpty(my.Value))
throw new YamlException("Junk value `{0}` on trait node {1}"
.F(my.Value, traitName));
throw new YamlException($"Junk value `{my.Value}` on trait node {traitName}");
// HACK: The linter does not want to crash when a trait doesn't exist but only print an error instead
// ObjectCreator will only return null to signal us to abort here if the linter is running
@@ -89,7 +88,7 @@ namespace OpenRA
try
{
if (traitInstance.Length > 1)
info.GetType().GetField("InstanceName").SetValue(info, traitInstance[1]);
info.GetType().GetField(nameof(info.InstanceName)).SetValue(info, traitInstance[1]);
FieldLoader.Load(info, my);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -22,12 +22,12 @@ namespace OpenRA
{
public class Ruleset
{
public readonly IReadOnlyDictionary<string, ActorInfo> Actors;
public readonly ActorInfoDictionary Actors;
public readonly IReadOnlyDictionary<string, WeaponInfo> Weapons;
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
public readonly TileSet TileSet;
public readonly ITerrainInfo TerrainInfo;
public readonly SequenceProvider Sequences;
public readonly IReadOnlyDictionary<string, MiniYamlNode> ModelSequences;
@@ -37,16 +37,16 @@ namespace OpenRA
IReadOnlyDictionary<string, SoundInfo> voices,
IReadOnlyDictionary<string, SoundInfo> notifications,
IReadOnlyDictionary<string, MusicInfo> music,
TileSet tileSet,
ITerrainInfo terrainInfo,
SequenceProvider sequences,
IReadOnlyDictionary<string, MiniYamlNode> modelSequences)
{
Actors = actors;
Actors = new ActorInfoDictionary(actors);
Weapons = weapons;
Voices = voices;
Notifications = notifications;
Music = music;
TileSet = tileSet;
TerrainInfo = terrainInfo;
Sequences = sequences;
ModelSequences = modelSequences;
@@ -60,15 +60,14 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException("Actor type {0}: {1}".F(a.Name, e.Message));
throw new YamlException($"Actor type {a.Name}: {e.Message}");
}
}
}
foreach (var weapon in Weapons)
{
var projectileLoaded = weapon.Value.Projectile as IRulesetLoaded<WeaponInfo>;
if (projectileLoaded != null)
if (weapon.Value.Projectile is IRulesetLoaded<WeaponInfo> projectileLoaded)
{
try
{
@@ -76,14 +75,13 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException("Projectile type {0}: {1}".F(weapon.Key, e.Message));
throw new YamlException($"Projectile type {weapon.Key}: {e.Message}");
}
}
foreach (var warhead in weapon.Value.Warheads)
{
var cacher = warhead as IRulesetLoaded<WeaponInfo>;
if (cacher != null)
if (warhead is IRulesetLoaded<WeaponInfo> cacher)
{
try
{
@@ -91,7 +89,7 @@ namespace OpenRA
}
catch (YamlException e)
{
throw new YamlException("Weapon type {0}: {1}".F(weapon.Key, e.Message));
throw new YamlException($"Weapon type {weapon.Key}: {e.Message}");
}
}
}
@@ -117,7 +115,7 @@ namespace OpenRA
if (filterNode != null)
yamlNodes = yamlNodes.Where(k => !filterNode(k));
return new ReadOnlyDictionary<string, T>(yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">"));
return yamlNodes.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
}
public static Ruleset LoadDefaults(ModData modData)
@@ -171,10 +169,10 @@ namespace OpenRA
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
{
var dr = modData.DefaultRules;
var ts = modData.DefaultTileSets[tileSet];
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
var sequences = modData.DefaultSequences[tileSet];
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences, dr.ModelSequences);
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, terrainInfo, sequences, dr.ModelSequences);
}
public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet,
@@ -203,8 +201,8 @@ namespace OpenRA
var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music,
k => new MusicInfo(k.Key, k.Value));
// TODO: Add support for merging custom tileset modifications
var ts = modData.DefaultTileSets[tileSet];
// TODO: Add support for merging custom terrain modifications
var terrainInfo = modData.DefaultTerrainInfo[tileSet];
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
@@ -215,7 +213,7 @@ namespace OpenRA
modelSequences = MergeOrDefault("ModelSequences", fileSystem, m.ModelSequences, mapModelSequences, dr.ModelSequences,
k => k);
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences, modelSequences);
ruleset = new Ruleset(actors, weapons, voices, notifications, music, terrainInfo, sequences, modelSequences);
};
if (modData.IsOnMainThread)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -121,10 +121,10 @@ namespace OpenRA.GameRules
[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
public readonly bool TargetActorCenter = false;
[FieldLoader.LoadUsing("LoadProjectile")]
[FieldLoader.LoadUsing(nameof(LoadProjectile))]
public readonly IProjectileInfo Projectile;
[FieldLoader.LoadUsing("LoadWarheads")]
[FieldLoader.LoadUsing(nameof(LoadWarheads))]
public readonly List<IWarhead> Warheads = new List<IWarhead>();
public WeaponInfo(string name, MiniYaml content)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -10,27 +10,49 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
public class GameSpeed
{
[Translate]
public readonly string Name = "Default";
public readonly int Timestep = 40;
public readonly int OrderLatency = 3;
[FieldLoader.Require]
public readonly string Name;
[FieldLoader.Require]
public readonly int Timestep;
[FieldLoader.Require]
public readonly int OrderLatency;
}
public class GameSpeeds : IGlobalModData
{
[FieldLoader.LoadUsing("LoadSpeeds")]
[FieldLoader.Require]
public readonly string DefaultSpeed;
[FieldLoader.LoadUsing(nameof(LoadSpeeds))]
public readonly Dictionary<string, GameSpeed> Speeds;
static object LoadSpeeds(MiniYaml y)
{
var ret = new Dictionary<string, GameSpeed>();
foreach (var node in y.Nodes)
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
if (speedsNode == null)
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
foreach (var node in speedsNode.Value.Nodes)
{
try
{
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
}
catch (FieldLoader.MissingFieldsException e)
{
var label = e.Missing.Length > 1 ? "Required properties missing" : "Required property missing";
throw new YamlException($"Error parsing GameSpeed {node.Key}: {label}: {e.Missing.JoinWith(", ")}");
}
}
return ret;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -49,28 +49,32 @@ namespace OpenRA.Graphics
this.paused = paused;
}
public int CurrentFrame { get { return backwards ? CurrentSequence.Length - frame - 1 : frame; } }
public Sprite Image { get { return CurrentSequence.GetSprite(CurrentFrame, facingFunc()); } }
public int CurrentFrame => backwards ? CurrentSequence.Length - frame - 1 : frame;
public Sprite Image => CurrentSequence.GetSprite(CurrentFrame, facingFunc());
public IRenderable[] Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale)
public IRenderable[] Render(WPos pos, in WVec offset, int zOffset, PaletteReference palette)
{
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration, CurrentSequence.IgnoreWorldTint);
var tintModifiers = CurrentSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None;
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, CurrentSequence.Scale, alpha, float3.Ones, tintModifiers, IsDecoration);
if (CurrentSequence.ShadowStart >= 0)
{
var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc());
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true, CurrentSequence.IgnoreWorldTint);
var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, CurrentSequence.Scale, 1f, float3.Ones, tintModifiers, true);
return new IRenderable[] { shadowRenderable, imageRenderable };
}
return new IRenderable[] { imageRenderable };
}
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, WVec offset, int zOffset, PaletteReference palette, float scale)
public IRenderable[] RenderUI(WorldRenderer wr, int2 pos, in WVec offset, int zOffset, PaletteReference palette, float scale = 1f)
{
scale *= CurrentSequence.Scale;
var screenOffset = (scale * wr.ScreenVectorComponents(offset)).XY.ToInt2();
var imagePos = pos + screenOffset - new int2((int)(scale * Image.Size.X / 2), (int)(scale * Image.Size.Y / 2));
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale);
var alpha = CurrentSequence.GetAlpha(CurrentFrame);
var imageRenderable = new UISpriteRenderable(Image, WPos.Zero + offset, imagePos, CurrentSequence.ZOffset + zOffset, palette, scale, alpha);
if (CurrentSequence.ShadowStart >= 0)
{
@@ -83,8 +87,9 @@ namespace OpenRA.Graphics
return new IRenderable[] { imageRenderable };
}
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, WVec offset, float scale)
public Rectangle ScreenBounds(WorldRenderer wr, WPos pos, in WVec offset)
{
var scale = CurrentSequence.Scale;
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset);
var cb = CurrentSequence.Bounds;
return Rectangle.FromLTRB(
@@ -96,7 +101,7 @@ namespace OpenRA.Graphics
public IRenderable[] Render(WPos pos, PaletteReference palette)
{
return Render(pos, WVec.Zero, 0, palette, 1f);
return Render(pos, WVec.Zero, 0, palette);
}
public void Play(string sequenceName)
@@ -107,7 +112,7 @@ namespace OpenRA.Graphics
int CurrentSequenceTickOrDefault()
{
const int DefaultTick = 40; // 25 fps == 40 ms
return CurrentSequence != null ? CurrentSequence.Tick : DefaultTick;
return CurrentSequence?.Tick ?? DefaultTick;
}
void PlaySequence(string sequenceName)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -35,21 +35,21 @@ namespace OpenRA.Graphics
ZOffset = zOffset;
}
public IRenderable[] Render(Actor self, WorldRenderer wr, PaletteReference pal, float scale)
public IRenderable[] Render(Actor self, WorldRenderer wr, PaletteReference pal)
{
var center = self.CenterPosition;
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
var z = (ZOffset != null) ? ZOffset(center + offset) : 0;
return Animation.Render(center, offset, z, pal, scale);
var z = ZOffset?.Invoke(center + offset) ?? 0;
return Animation.Render(center, offset, z, pal);
}
public Rectangle ScreenBounds(Actor self, WorldRenderer wr, float scale)
public Rectangle ScreenBounds(Actor self, WorldRenderer wr)
{
var center = self.CenterPosition;
var offset = OffsetFunc != null ? OffsetFunc() : WVec.Zero;
var offset = OffsetFunc?.Invoke() ?? WVec.Zero;
return Animation.ScreenBounds(wr, center, offset, scale);
return Animation.ScreenBounds(wr, center, offset);
}
public static implicit operator AnimationWithOffset(Animation a)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -52,7 +52,7 @@ namespace OpenRA.Graphics
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
}
public static IReadOnlyDictionary<string, Collection> Collections { get; private set; }
public static IReadOnlyDictionary<string, Collection> Collections => collections;
static Dictionary<string, Collection> collections;
static Dictionary<string, (Sheet Sheet, int Density)> cachedSheets;
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
@@ -77,8 +77,6 @@ namespace OpenRA.Graphics
cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, (Sheet, int)>();
Collections = new ReadOnlyDictionary<string, Collection>(collections);
var chrome = MiniYaml.Merge(modData.Manifest.Chrome
.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -69,9 +69,16 @@ namespace OpenRA.Graphics
// Hotspot is specified relative to the center of the frame
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
// SheetBuilder expects data in BGRA
var data = FrameToBGRA(kv.Key, f, palette);
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
// Resolve indexed data to real colours
var data = f.Data;
var type = f.Type;
if (type == SpriteFrameType.Indexed8)
{
data = ConvertIndexedToBgra(kv.Key, f, palette);
type = SpriteFrameType.Bgra32;
}
c.Sprites[c.Length++] = sheetBuilder.Add(data, type, f.Size, 0, hotspot);
// Bounds relative to the hotspot
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
@@ -99,34 +106,28 @@ namespace OpenRA.Graphics
// Dispose any existing cursors to avoid leaking native resources
ClearHardwareCursors();
try
foreach (var kv in cursors)
{
foreach (var kv in cursors)
var template = kv.Value;
for (var i = 0; i < template.Sprites.Length; i++)
{
var template = kv.Value;
for (var i = 0; i < template.Sprites.Length; i++)
if (template.Cursors[i] != null)
template.Cursors[i].Dispose();
// Calculate the padding to position the frame within sequenceBounds
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
var hardwareCursor = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
if (hardwareCursor != null)
template.Cursors[i] = hardwareCursor;
else
{
if (template.Cursors[i] != null)
template.Cursors[i].Dispose();
// Calculate the padding to position the frame within sequenceBounds
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
Log.Write("debug", $"Failed to initialize hardware cursor for {template.Name}.");
Console.WriteLine($"Failed to initialize hardware cursor for {template.Name}.");
}
}
}
catch (Exception e)
{
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
Log.Write("debug", "Error was: " + e.Message);
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
Console.WriteLine("Error was: " + e.Message);
ClearHardwareCursors();
}
hardwareCursorsDoubled = graphicSettings.CursorDouble;
}
@@ -170,10 +171,11 @@ namespace OpenRA.Graphics
if (cursor != null && frame >= cursor.Cursors.Length)
frame %= cursor.Cursors.Length;
if (cursor == null || isLocked)
var hardwareCursor = cursor?.Cursors[frame];
if (hardwareCursor == null || isLocked)
Game.Renderer.Window.SetHardwareCursor(null);
else
Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
Game.Renderer.Window.SetHardwareCursor(hardwareCursor);
}
public void Render(Renderer renderer)
@@ -189,17 +191,17 @@ namespace OpenRA.Graphics
// Render cursor in software
var doubleCursor = graphicSettings.CursorDouble;
var cursorSprite = cursor.Sprites[frame % cursor.Length];
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
var cursorScale = doubleCursor ? 2 : 1;
// Cursor is rendered in native window coordinates
// Apply same scaling rules as hardware cursors
if (Game.Renderer.NativeWindowScale > 1.5f)
cursorSize = 2 * cursorSize;
cursorScale *= 2;
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
mousePos,
cursorSize / Game.Renderer.WindowScale);
cursorScale / Game.Renderer.WindowScale);
}
public void Lock()
@@ -217,33 +219,27 @@ namespace OpenRA.Graphics
Update();
}
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
public static byte[] ConvertIndexedToBgra(string name, ISpriteFrame frame, ImmutablePalette palette)
{
// Data is already in BGRA format
if (frame.Type == SpriteFrameType.BGRA)
return frame.Data;
if (frame.Type != SpriteFrameType.Indexed8)
throw new ArgumentException("ConvertIndexedToBgra requires input frames to be indexed.", nameof(frame));
// Cursors may be either native BGRA or Indexed.
// Indexed sprites are converted to BGRA using the referenced palette.
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
if (frame.Type == SpriteFrameType.Indexed && palette == null)
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
if (palette == null)
throw new InvalidOperationException($"Cursor sequence `{name}` attempted to load an indexed sprite but does not define Palette");
var width = frame.Size.Width;
var height = frame.Size.Height;
var data = new byte[4 * width * height];
for (var j = 0; j < height; j++)
unsafe
{
for (var i = 0; i < width; i++)
// Cast the data to an int array so we can copy the src data directly
fixed (byte* bd = &data[0])
{
var rgba = palette[frame.Data[j * width + i]];
var k = 4 * (j * width + i);
// Convert RGBA to BGRA
data[k] = (byte)(rgba >> 16);
data[k + 1] = (byte)(rgba >> 8);
data[k + 2] = (byte)(rgba >> 0);
data[k + 3] = (byte)(rgba >> 24);
var rgba = (uint*)bd;
for (var j = 0; j < height; j++)
for (var i = 0; i < width; i++)
rgba[j * width + i] = palette[frame.Data[j * width + i]];
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -31,15 +31,14 @@ namespace OpenRA.Graphics
// Overwrite previous definitions if there are duplicates
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
foreach (var p in modData.DefaultRules.Actors["world"].TraitInfos<IProvidesCursorPaletteInfo>())
foreach (var p in modData.DefaultRules.Actors[SystemActors.World].TraitInfos<IProvidesCursorPaletteInfo>())
if (p.Palette != null)
pals[p.Palette] = p;
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
.Where(p => p != null)
.Distinct()
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem))
.AsReadOnly();
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
var cursors = new Dictionary<string, CursorSequence>();
@@ -47,7 +46,7 @@ namespace OpenRA.Graphics
foreach (var sequence in s.Value.Nodes)
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
Cursors = cursors.AsReadOnly();
Cursors = cursors;
}
public bool HasCursorSequence(string cursor)
@@ -60,7 +59,7 @@ namespace OpenRA.Graphics
try { return Cursors[cursor]; }
catch (KeyNotFoundException)
{
throw new InvalidOperationException("Cursor does not have a sequence `{0}`".F(cursor));
throw new InvalidOperationException($"Cursor does not have a sequence `{cursor}`");
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -18,72 +18,93 @@ namespace OpenRA.Graphics
public sealed class HardwarePalette : IDisposable
{
public ITexture Texture { get; private set; }
public ITexture ColorShifts { get; private set; }
public int Height { get; private set; }
readonly Dictionary<string, ImmutablePalette> palettes = new Dictionary<string, ImmutablePalette>();
readonly Dictionary<string, MutablePalette> modifiablePalettes = new Dictionary<string, MutablePalette>();
readonly IReadOnlyDictionary<string, MutablePalette> readOnlyModifiablePalettes;
readonly Dictionary<string, MutablePalette> mutablePalettes = new Dictionary<string, MutablePalette>();
readonly Dictionary<string, int> indices = new Dictionary<string, int>();
byte[] buffer = new byte[0];
float[] colorShiftBuffer = new float[0];
public HardwarePalette()
{
Texture = Game.Renderer.Context.CreateTexture();
readOnlyModifiablePalettes = modifiablePalettes.AsReadOnly();
ColorShifts = Game.Renderer.Context.CreateTexture();
}
public bool Contains(string name)
{
return modifiablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
return mutablePalettes.ContainsKey(name) || palettes.ContainsKey(name);
}
public IPalette GetPalette(string name)
{
if (modifiablePalettes.TryGetValue(name, out var mutable))
if (mutablePalettes.TryGetValue(name, out var mutable))
return mutable.AsReadOnly();
if (palettes.TryGetValue(name, out var immutable))
return immutable;
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
throw new InvalidOperationException($"Palette `{name}` does not exist");
}
public int GetPaletteIndex(string name)
{
if (!indices.TryGetValue(name, out var ret))
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
throw new InvalidOperationException($"Palette `{name}` does not exist");
return ret;
}
public void AddPalette(string name, ImmutablePalette p, bool allowModifiers)
{
if (palettes.ContainsKey(name))
throw new InvalidOperationException("Palette {0} has already been defined".F(name));
throw new InvalidOperationException($"Palette {name} has already been defined");
int index = palettes.Count;
// PERF: the first row in the palette textures is reserved as a placeholder for non-indexed sprites
// that do not have a color-shift applied. This provides a quick shortcut to avoid querying the
// color-shift texture for every pixel only to find that most are not shifted.
var index = palettes.Count + 1;
indices.Add(name, index);
palettes.Add(name, p);
if (palettes.Count > Height)
if (index >= Height)
{
Height = Exts.NextPowerOf2(palettes.Count);
Height = Exts.NextPowerOf2(index + 1);
Array.Resize(ref buffer, Height * Palette.Size * 4);
Array.Resize(ref colorShiftBuffer, Height * 4);
}
if (allowModifiers)
modifiablePalettes.Add(name, new MutablePalette(p));
mutablePalettes.Add(name, new MutablePalette(p));
else
CopyPaletteToBuffer(index, p);
}
public void ReplacePalette(string name, IPalette p)
{
if (modifiablePalettes.ContainsKey(name))
CopyPaletteToBuffer(indices[name], modifiablePalettes[name] = new MutablePalette(p));
if (mutablePalettes.ContainsKey(name))
CopyPaletteToBuffer(indices[name], mutablePalettes[name] = new MutablePalette(p));
else if (palettes.ContainsKey(name))
CopyPaletteToBuffer(indices[name], palettes[name] = new ImmutablePalette(p));
else
throw new InvalidOperationException("Palette `{0}` does not exist".F(name));
throw new InvalidOperationException($"Palette `{name}` does not exist");
CopyBufferToTexture();
}
public void SetColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
var index = GetPaletteIndex(name);
colorShiftBuffer[4 * index + 0] = hueOffset;
colorShiftBuffer[4 * index + 1] = satOffset;
colorShiftBuffer[4 * index + 2] = minHue;
colorShiftBuffer[4 * index + 3] = maxHue;
}
public bool HasColorShift(string name)
{
var index = GetPaletteIndex(name);
return colorShiftBuffer[4 * index + 2] != 0 || colorShiftBuffer[4 * index + 3] != 0;
}
public void Initialize()
{
CopyModifiablePalettesToBuffer();
@@ -97,26 +118,27 @@ namespace OpenRA.Graphics
void CopyModifiablePalettesToBuffer()
{
foreach (var kvp in modifiablePalettes)
foreach (var kvp in mutablePalettes)
CopyPaletteToBuffer(indices[kvp.Key], kvp.Value);
}
void CopyBufferToTexture()
{
Texture.SetData(buffer, Palette.Size, Height);
ColorShifts.SetFloatData(colorShiftBuffer, 1, Height);
}
public void ApplyModifiers(IEnumerable<IPaletteModifier> paletteMods)
{
foreach (var mod in paletteMods)
mod.AdjustPalette(readOnlyModifiablePalettes);
mod.AdjustPalette(mutablePalettes);
// Update our texture with the changes.
CopyModifiablePalettesToBuffer();
CopyBufferToTexture();
// Reset modified palettes back to their original colors, ready for next time.
foreach (var kvp in modifiablePalettes)
foreach (var kvp in mutablePalettes)
{
var originalPalette = palettes[kvp.Key];
var modifiedPalette = kvp.Value;
@@ -127,6 +149,7 @@ namespace OpenRA.Graphics
public void Dispose()
{
Texture.Dispose();
ColorShifts.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -10,6 +10,7 @@
#endregion
using System;
using System.Collections.Generic;
using OpenRA.FileSystem;
using OpenRA.Primitives;
@@ -29,7 +30,7 @@ namespace OpenRA.Graphics
Rectangle AggregateBounds { get; }
}
public struct ModelRenderData
public readonly struct ModelRenderData
{
public readonly int Start;
public readonly int Count;
@@ -45,6 +46,7 @@ namespace OpenRA.Graphics
public interface IModelCache : IDisposable
{
IModel GetModel(string model);
IModel GetModelSequence(string model, string sequence);
bool HasModelSequence(string model, string sequence);
IVertexBuffer<Vertex> VertexBuffer { get; }
@@ -62,10 +64,15 @@ namespace OpenRA.Graphics
class PlaceholderModelCache : IModelCache
{
public IVertexBuffer<Vertex> VertexBuffer { get { throw new NotImplementedException(); } }
public IVertexBuffer<Vertex> VertexBuffer => throw new NotImplementedException();
public void Dispose() { }
public IModel GetModel(string model)
{
throw new NotImplementedException();
}
public IModel GetModelSequence(string model, string sequence)
{
throw new NotImplementedException();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public struct ModelAnimation
public readonly struct ModelAnimation
{
public readonly IModel Model;
public readonly Func<WVec> OffsetFunc;
@@ -46,12 +46,6 @@ namespace OpenRA.Graphics
xy.Y + (int)(r.Bottom * scale));
}
public bool IsVisible
{
get
{
return DisableFunc == null || !DisableFunc();
}
}
public bool IsVisible => DisableFunc == null || !DisableFunc();
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -42,6 +42,7 @@ namespace OpenRA.Graphics
static readonly float[] ZVector = new float[] { 0, 0, 1, 1 };
static readonly float[] FlipMtx = Util.ScaleMatrix(1, -1, 1);
static readonly float[] ShadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
static readonly float[] GroundNormal = { 0, 0, 1, 1 };
readonly Renderer renderer;
readonly IShader shader;
@@ -80,7 +81,7 @@ namespace OpenRA.Graphics
public ModelRenderProxy RenderAsync(
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
in WRot groundOrientation, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
{
if (!isInFrame)
@@ -92,7 +93,10 @@ namespace OpenRA.Graphics
// Correct for bogus light source definition
var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
var ground = Util.MakeFloatMatrix(groundOrientation.AsMatrix());
var shadowTransform = Util.MatrixMultiply(Util.MatrixMultiply(lightPitch, lightYaw), Util.MatrixInverse(ground));
var groundNormal = Util.MatrixVectorMultiply(ground, GroundNormal);
var invShadowTransform = Util.MatrixInverse(shadowTransform);
var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
@@ -205,7 +209,7 @@ namespace OpenRA.Graphics
var t = m.Model.TransformationMatrix(i, frame);
var it = Util.MatrixInverse(t);
if (it == null)
throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
throw new InvalidOperationException($"Failed to invert the transformed matrix of frame {i} during RenderAsync.");
// Transform light vector from shadow -> world -> limb coords
var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -44,7 +44,8 @@ namespace OpenRA.Graphics
{
IPalette palette;
public ReadOnlyPalette(IPalette palette) { this.palette = palette; }
public uint this[int index] { get { return palette[index]; } }
public uint this[int index] => palette[index];
public void CopyToArray(Array destination, int destinationOffset)
{
palette.CopyToArray(destination, destinationOffset);
@@ -56,28 +57,25 @@ namespace OpenRA.Graphics
{
readonly uint[] colors = new uint[Palette.Size];
public uint this[int index]
{
get { return colors[index]; }
}
public uint this[int index] => colors[index];
public void CopyToArray(Array destination, int destinationOffset)
{
Buffer.BlockCopy(colors, 0, destination, destinationOffset * 4, Palette.Size * 4);
}
public ImmutablePalette(string filename, int[] remap)
public ImmutablePalette(string filename, int[] remapTransparent, int[] remap)
{
using (var s = File.OpenRead(filename))
LoadFromStream(s, remap);
LoadFromStream(s, remapTransparent, remap);
}
public ImmutablePalette(Stream s, int[] remapShadow)
public ImmutablePalette(Stream s, int[] remapTransparent, int[] remapShadow)
{
LoadFromStream(s, remapShadow);
LoadFromStream(s, remapTransparent, remapShadow);
}
void LoadFromStream(Stream s, int[] remapShadow)
void LoadFromStream(Stream s, int[] remapTransparent, int[] remapShadow)
{
using (var reader = new BinaryReader(s))
for (var i = 0; i < Palette.Size; i++)
@@ -94,7 +92,9 @@ namespace OpenRA.Graphics
colors[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
}
colors[0] = 0; // Convert black background to transparency.
foreach (var i in remapTransparent)
colors[i] = 0;
foreach (var i in remapShadow)
colors[i] = 140u << 24;
}
@@ -126,8 +126,8 @@ namespace OpenRA.Graphics
public uint this[int index]
{
get { return colors[index]; }
set { colors[index] = value; }
get => colors[index];
set => colors[index] = value;
}
public void CopyToArray(Array destination, int destinationOffset)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -18,8 +18,8 @@ namespace OpenRA.Graphics
public readonly string Name;
public IPalette Palette { get; internal set; }
public float TextureIndex { get { return index / hardwarePalette.Height; } }
public float TextureMidIndex { get { return (index + 0.5f) / hardwarePalette.Height; } }
public float TextureIndex => index / hardwarePalette.Height;
public float TextureMidIndex => (index + 0.5f) / hardwarePalette.Height;
public PaletteReference(string name, int index, IPalette palette, HardwarePalette hardwarePalette)
{
@@ -28,5 +28,7 @@ namespace OpenRA.Graphics
this.index = index;
this.hardwarePalette = hardwarePalette;
}
public bool HasColorShift => hardwarePalette.HasColorShift(Name);
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -59,6 +59,7 @@ namespace OpenRA
int DisplayCount { get; }
int CurrentDisplay { get; }
bool HasInputFocus { get; }
bool IsSuspended { get; }
event Action<float, float, float, float> OnWindowScaleChanged;
@@ -71,6 +72,7 @@ namespace OpenRA
IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot, bool pixelDouble);
void SetHardwareCursor(IHardwareCursor cursor);
void SetWindowTitle(string title);
void SetRelativeMouseMode(bool mode);
void SetScaleModifier(float scale);
@@ -124,6 +126,7 @@ namespace OpenRA
{
void SetData(uint[,] colors);
void SetData(byte[] colors, int width, int height);
void SetFloatData(float[] data, int width, int height);
byte[] GetData();
Size Size { get; }
TextureScaleFilter ScaleFilter { get; set; }
@@ -145,7 +148,7 @@ namespace OpenRA
TriangleList,
}
public struct Range<T>
public readonly struct Range<T>
{
public readonly T Start, End;
public Range(T start, T end) { Start = start; End = end; }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -18,43 +18,35 @@ namespace OpenRA.Graphics
{
public class PlayerColorRemap : IPaletteRemap
{
Dictionary<int, Color> remapColors;
readonly int[] remapIndices;
readonly float hue;
readonly float saturation;
public static int GetRemapIndex(int[] ramp, int i)
public PlayerColorRemap(int[] remapIndices, float hue, float saturation)
{
return ramp[i];
}
public PlayerColorRemap(int[] ramp, Color c, float rampFraction)
{
var h = c.GetHue() / 360.0f;
var s = c.GetSaturation();
var l = c.GetBrightness();
// Increase luminosity if required to represent the full ramp
var rampRange = (byte)((1 - rampFraction) * l);
var c1 = Color.FromAhsl(h, s, Math.Max(rampRange, l));
var c2 = Color.FromAhsl(h, s, (byte)Math.Max(0, l - rampRange));
var baseIndex = ramp[0];
var remapRamp = ramp.Select(r => r - ramp[0]);
var rampMaxIndex = ramp.Length - 1;
// reversed remapping
if (ramp[0] > ramp[rampMaxIndex])
{
baseIndex = ramp[rampMaxIndex];
for (var i = rampMaxIndex; i > 0; i--)
remapRamp = ramp.Select(r => r - ramp[rampMaxIndex]);
}
remapColors = remapRamp.Select((x, i) => (baseIndex + i, Exts.ColorLerp(x / (float)ramp.Length, c1, c2)))
.ToDictionary(u => u.Item1, u => u.Item2);
this.remapIndices = remapIndices;
this.hue = hue;
this.saturation = saturation;
}
public Color GetRemappedColor(Color original, int index)
{
return remapColors.TryGetValue(index, out var c)
? c : original;
if (!remapIndices.Contains(index))
return original;
// Color remapping is applied in a linear color space, so start
// by undoing the pre-multiplied alpha and gamma corrections
var (r, g, b) = original.ToLinear();
// Calculate the brightness (i.e HSV value) of the original colour
// This inlines the single line of Color.RgbToHsv() that we need
var value = Math.Max(Math.Max(r, g), b);
// Construct the new RGB color
(r, g, b) = Color.HsvToRgb(hue, saturation, value);
// Convert linear back to SRGB and pre-multiply by the alpha
return Color.FromLinear(original.A, r, g, b);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -9,6 +9,7 @@
*/
#endregion
using System;
using OpenRA.Primitives;
namespace OpenRA.Graphics
@@ -16,21 +17,38 @@ namespace OpenRA.Graphics
public interface IRenderable
{
WPos Pos { get; }
PaletteReference Palette { get; }
int ZOffset { get; }
bool IsDecoration { get; }
IRenderable WithPalette(PaletteReference newPalette);
IRenderable WithZOffset(int newOffset);
IRenderable OffsetBy(WVec offset);
IRenderable OffsetBy(in WVec offset);
IRenderable AsDecoration();
IFinalizedRenderable PrepareRender(WorldRenderer wr);
}
public interface ITintableRenderable
public interface IPalettedRenderable : IRenderable
{
IRenderable WithTint(in float3 newTint);
PaletteReference Palette { get; }
IPalettedRenderable WithPalette(PaletteReference newPalette);
}
[Flags]
public enum TintModifiers
{
None = 0,
IgnoreWorldTint = 1,
ReplaceColor = 2
}
public interface IModifyableRenderable : IRenderable
{
float Alpha { get; }
float3 Tint { get; }
TintModifiers TintModifiers { get; }
IModifyableRenderable WithAlpha(float newAlpha);
IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers);
}
public interface IFinalizedRenderable

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor, BlendMode blendMode = BlendMode.Alpha)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
@@ -52,10 +52,10 @@ namespace OpenRA.Graphics
vertices[4] = new Vertex(end - corner + Offset, er, eg, eb, ea, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, sr, sg, sb, sa, 0, 0);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
}
public void DrawLine(in float3 start, in float3 end, float width, Color color)
public void DrawLine(in float3 start, in float3 end, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float2(-delta.Y, delta.X);
@@ -72,7 +72,7 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(end + corner + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(end - corner + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(start - corner + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
}
/// <summary>
@@ -90,7 +90,7 @@ namespace OpenRA.Graphics
return new float3(x / d, y / d, 0.5f * (a.Z + b.Z));
}
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color)
void DrawDisconnectedLine(IEnumerable<float3> points, float width, Color color, BlendMode blendMode)
{
using (var e = points.GetEnumerator())
{
@@ -101,13 +101,13 @@ namespace OpenRA.Graphics
while (e.MoveNext())
{
var point = e.Current;
DrawLine(lastPoint, point, width, color);
DrawLine(lastPoint, point, width, color, blendMode);
lastPoint = point;
}
}
}
void DrawConnectedLine(float3[] points, float width, Color color, bool closed)
void DrawConnectedLine(float3[] points, float width, Color color, bool closed, BlendMode blendMode)
{
// Not a line
if (points.Length < 2)
@@ -116,7 +116,7 @@ namespace OpenRA.Graphics
// Single segment
if (points.Length == 2)
{
DrawLine(points[0], points[1], width, color);
DrawLine(points[0], points[1], width, color, blendMode);
return;
}
@@ -163,7 +163,7 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(cc + Offset, r, g, b, a, 0, 0);
vertices[4] = new Vertex(cd + Offset, r, g, b, a, 0, 0);
vertices[5] = new Vertex(ca + Offset, r, g, b, a, 0, 0);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
// Advance line segment
end = next;
@@ -175,32 +175,32 @@ namespace OpenRA.Graphics
}
}
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false)
public void DrawLine(IEnumerable<float3> points, float width, Color color, bool connectSegments = false, BlendMode blendMode = BlendMode.Alpha)
{
if (!connectSegments)
DrawDisconnectedLine(points, width, color);
DrawDisconnectedLine(points, width, color, blendMode);
else
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false);
DrawConnectedLine(points as float3[] ?? points.ToArray(), width, color, false, blendMode);
}
public void DrawPolygon(float3[] vertices, float width, Color color)
public void DrawPolygon(float3[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
{
DrawConnectedLine(vertices, width, color, true);
DrawConnectedLine(vertices, width, color, true, blendMode);
}
public void DrawPolygon(float2[] vertices, float width, Color color)
public void DrawPolygon(float2[] vertices, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
{
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true, blendMode);
}
public void DrawRect(in float3 tl, in float3 br, float width, Color color)
public void DrawRect(in float3 tl, in float3 br, float width, Color color, BlendMode blendMode = BlendMode.Alpha)
{
var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z);
DrawPolygon(new[] { tl, tr, br, bl }, width, color);
DrawPolygon(new[] { tl, tr, br, bl }, width, color, blendMode);
}
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color, BlendMode blendMode = BlendMode.Alpha)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -211,17 +211,17 @@ namespace OpenRA.Graphics
vertices[0] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
vertices[1] = new Vertex(b + Offset, cr, cg, cb, ca, 0, 0);
vertices[2] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
}
public void FillRect(in float3 tl, in float3 br, Color color)
public void FillRect(in float3 tl, in float3 br, Color color, BlendMode blendMode = BlendMode.Alpha)
{
var tr = new float3(br.X, tl.Y, tl.Z);
var bl = new float3(tl.X, br.Y, br.Z);
FillRect(tl, tr, br, bl, color);
FillRect(tl, tr, br, bl, color, blendMode);
}
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color, BlendMode blendMode = BlendMode.Alpha)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -235,10 +235,10 @@ namespace OpenRA.Graphics
vertices[3] = new Vertex(c + Offset, cr, cg, cb, ca, 0, 0);
vertices[4] = new Vertex(d + Offset, cr, cg, cb, ca, 0, 0);
vertices[5] = new Vertex(a + Offset, cr, cg, cb, ca, 0, 0);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
}
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor)
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color topLeftColor, Color topRightColor, Color bottomRightColor, Color bottomLeftColor, BlendMode blendMode = BlendMode.Alpha)
{
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor);
@@ -247,7 +247,7 @@ namespace OpenRA.Graphics
vertices[4] = VertexWithColor(d + Offset, bottomLeftColor);
vertices[5] = VertexWithColor(a + Offset, topLeftColor);
parent.DrawRGBAVertices(vertices);
parent.DrawRGBAVertices(vertices, blendMode);
}
static Vertex VertexWithColor(in float3 xyz, Color color)
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
}
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32, BlendMode blendMode = BlendMode.Alpha)
{
// TODO: Create an ellipse polygon instead
var a = (br.X - tl.X) / 2;
@@ -272,7 +272,7 @@ namespace OpenRA.Graphics
{
var z = float2.Lerp(tl.Z, br.Z, (y - tl.Y) / (br.Y - tl.Y));
var dx = a * (float)Math.Sqrt(1 - (y - yc) * (y - yc) / b / b);
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color);
DrawLine(new float3(xc - dx, y, z), new float3(xc + dx, y, z), 1, color, blendMode);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -22,44 +22,36 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawSprite(Sprite s, in float3 location, in float3 size)
public void DrawSprite(Sprite s, in float3 location, in float3 scale)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, location, 0, size);
parent.DrawSprite(s, 0, location, scale);
}
public void DrawSprite(Sprite s, in float3 location)
public void DrawSprite(Sprite s, in float3 location, float scale = 1f)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, location, 0, s.Size);
parent.DrawSprite(s, 0, location, scale);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
public void DrawSprite(Sprite s, in float3 location, float scale, in float3 tint, float alpha)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSprite(s, a, b, c, d);
parent.DrawSprite(s, 0, location, scale, tint, alpha);
}
public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
parent.DrawSpriteWithTint(s, a, b, c, d, tint);
parent.DrawSprite(s, 0, a, b, c, d, tint, alpha);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -33,10 +33,12 @@ namespace OpenRA.Graphics
int[] Frames { get; }
Rectangle Bounds { get; }
bool IgnoreWorldTint { get; }
float Scale { get; }
Sprite GetSprite(int frame);
Sprite GetSprite(int frame, WAngle facing);
Sprite GetShadow(int frame, WAngle facing);
float GetAlpha(int frame);
}
public interface ISpriteSequenceLoader
@@ -50,7 +52,7 @@ namespace OpenRA.Graphics
readonly string tileSet;
readonly Lazy<Sequences> sequences;
readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
public SpriteCache SpriteCache => spriteCache.Value;
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
@@ -70,15 +72,15 @@ namespace OpenRA.Graphics
public ISpriteSequence GetSequence(string unitName, string sequenceName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
if (!unitSeq.Value.TryGetValue(sequenceName, out var seq))
throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName));
throw new InvalidOperationException($"Unit `{unitName}` does not have a sequence named `{sequenceName}`");
return seq;
}
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
public IEnumerable<string> Images => sequences.Value.Keys;
public bool HasSequence(string unitName)
{
@@ -88,7 +90,7 @@ namespace OpenRA.Graphics
public bool HasSequence(string unitName, string sequenceName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
return unitSeq.Value.ContainsKey(sequenceName);
}
@@ -96,7 +98,7 @@ namespace OpenRA.Graphics
public IEnumerable<string> Sequences(string unitName)
{
if (!sequences.Value.TryGetValue(unitName, out var unitSeq))
throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName));
throw new InvalidOperationException($"Unit `{unitName}` does not have any sequences defined.");
return unitSeq.Value.Keys;
}
@@ -123,7 +125,7 @@ namespace OpenRA.Graphics
}
}
return new ReadOnlyDictionary<string, UnitSequences>(items);
return items;
}
public void Preload()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -32,7 +32,7 @@ namespace OpenRA.Graphics
return data;
}
public bool Buffered { get { return data != null || texture == null; } }
public bool Buffered => data != null || texture == null;
public Sheet(SheetType type, Size size)
{
@@ -79,21 +79,17 @@ namespace OpenRA.Graphics
public Png AsPng()
{
var data = GetData();
if (Type == SheetType.Indexed)
throw new InvalidOperationException("AsPng() cannot be called on Indexed sheets.");
// Convert BGRA to RGBA
for (var i = 0; i < Size.Width * Size.Height; i++)
{
var temp = data[i * 4];
data[i * 4] = data[i * 4 + 2];
data[i * 4 + 2] = temp;
}
return new Png(data, Size.Width, Size.Height);
return new Png(GetData(), SpriteFrameType.Bgra32, Size.Width, Size.Height);
}
public Png AsPng(TextureChannel channel, IPalette pal)
{
if (Type != SheetType.Indexed)
throw new InvalidOperationException("AsPng(TextureChannel, IPalette) can only be called on Indexed sheets.");
var d = GetData();
var plane = new byte[Size.Width * Size.Height];
var dataStride = 4 * Size.Width;
@@ -107,7 +103,7 @@ namespace OpenRA.Graphics
for (var i = 0; i < Palette.Size; i++)
palColors[i] = pal.GetColor(i);
return new Png(plane, Size.Width, Size.Height, palColors);
return new Png(plane, SpriteFrameType.Indexed8, Size.Width, Size.Height, palColors);
}
public void CreateBuffer()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -52,9 +52,16 @@ namespace OpenRA.Graphics
{
switch (t)
{
case SpriteFrameType.Indexed: return SheetType.Indexed;
case SpriteFrameType.BGRA: return SheetType.BGRA;
default: throw new NotImplementedException("Unknown SpriteFrameType {0}".F(t));
case SpriteFrameType.Indexed8:
return SheetType.Indexed;
// Util.FastCopyIntoChannel will automatically convert these to BGRA
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
return SheetType.BGRA;
default: throw new NotImplementedException($"Unknown SpriteFrameType {t}");
}
}
@@ -74,16 +81,16 @@ namespace OpenRA.Graphics
this.margin = margin;
}
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Size, 0, frame.Offset); }
public Sprite Add(byte[] src, Size size) { return Add(src, size, 0, float3.Zero); }
public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); }
public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset)
{
// Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0)
return new Sprite(current, Rectangle.Empty, 0, spriteOffset, channel, BlendMode.Alpha);
var rect = Allocate(size, zRamp, spriteOffset);
Util.FastCopyIntoChannel(rect, src);
Util.FastCopyIntoChannel(rect, src, type);
current.CommitBufferedData();
return rect;
}
@@ -96,15 +103,6 @@ namespace OpenRA.Graphics
return rect;
}
public Sprite Add(Size size, byte paletteIndex)
{
var data = new byte[size.Width * size.Height];
for (var i = 0; i < data.Length; i++)
data[i] = paletteIndex;
return Add(data, size);
}
TextureChannel? NextChannel(TextureChannel t)
{
var nextChannel = (int)t + (int)Type;
@@ -149,9 +147,9 @@ namespace OpenRA.Graphics
return rect;
}
public Sheet Current { get { return current; } }
public TextureChannel CurrentChannel { get { return channel; } }
public IEnumerable<Sheet> AllSheets { get { return sheets; } }
public Sheet Current => current;
public TextureChannel CurrentChannel => channel;
public IEnumerable<Sheet> AllSheets => sheets;
public void Dispose()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -23,7 +23,6 @@ namespace OpenRA.Graphics
public readonly float ZRamp;
public readonly float3 Size;
public readonly float3 Offset;
public readonly float3 FractionalOffset;
public readonly float Top, Left, Bottom, Right;
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
@@ -38,13 +37,16 @@ namespace OpenRA.Graphics
Channel = channel;
Size = scale * new float3(bounds.Size.Width, bounds.Size.Height, bounds.Size.Height * zRamp);
BlendMode = blendMode;
FractionalOffset = Size.Z != 0 ? offset / Size :
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
// Some GPUs suffer from precision issues when rendering into non 1:1 framebuffers that result
// in rendering a line of texels that sample outside the sprite rectangle.
// Insetting the texture coordinates by a small fraction of a pixel avoids this
// with negligible impact on the 1:1 rendering case.
var inset = 1 / 128f;
Left = (Math.Min(bounds.Left, bounds.Right) + inset) / sheet.Size.Width;
Top = (Math.Min(bounds.Top, bounds.Bottom) + inset) / sheet.Size.Height;
Right = (Math.Max(bounds.Left, bounds.Right) - inset) / sheet.Size.Width;
Bottom = (Math.Max(bounds.Top, bounds.Bottom) - inset) / sheet.Size.Height;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -10,7 +10,6 @@
#endregion
using System;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
@@ -21,7 +20,6 @@ namespace OpenRA.Graphics
public int TopOffset { get; private set; }
readonly int size;
readonly SheetBuilder builder;
readonly Func<string, float> lineWidth;
readonly IFont font;
readonly Cache<char, GlyphInfo> glyphs;
readonly Cache<(char C, int Radius), Sprite> contrastGlyphs;
@@ -32,7 +30,7 @@ namespace OpenRA.Graphics
public SpriteFont(string name, byte[] data, int size, int ascender, float scale, SheetBuilder builder)
{
if (builder.Type != SheetType.BGRA)
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
throw new ArgumentException("The sheet builder must create BGRA sheets.", nameof(builder));
deviceScale = scale;
this.size = size;
@@ -43,13 +41,9 @@ namespace OpenRA.Graphics
contrastGlyphs = new Cache<(char, int), Sprite>(CreateContrastGlyph);
dilationElements = new Cache<int, float[]>(CreateCircularWeightMap);
// PERF: Cache these delegates for Measure calls.
Func<char, float> characterWidth = character => glyphs[character].Advance;
lineWidth = line => line.Sum(characterWidth) / deviceScale;
// Pre-cache small font sizes so glyphs are immediately available when we need them
if (size <= 24)
using (new PerfTimer("Precache {0} {1}px".F(name, size)))
using (new PerfTimer($"Precache {name} {size}px"))
for (var n = (char)0x20; n < (char)0x7f; n++)
if (glyphs[n] == null)
throw new InvalidOperationException();
@@ -89,10 +83,10 @@ namespace OpenRA.Graphics
if (g.Sprite != null)
{
var contrastSprite = contrastGlyphs[(s, screenContrast)];
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(contrastSprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(contrastSprite,
(screen + g.Offset - contrastVector) / deviceScale,
contrastSprite.Size / deviceScale,
tint);
1f / deviceScale,
tint, 1f);
}
screen += new int2((int)(g.Advance + 0.5f), 0);
@@ -120,10 +114,10 @@ namespace OpenRA.Graphics
// Convert screen coordinates back to UI coordinates for drawing
if (g.Sprite != null)
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
(screen + g.Offset).ToFloat2() / deviceScale,
g.Sprite.Size / deviceScale,
tint);
1f / deviceScale,
tint, 1f);
screen += new int2((int)(g.Advance + 0.5f), 0);
}
@@ -172,12 +166,12 @@ namespace OpenRA.Graphics
// Offset rotated glyph to align the top-left corner with the screen pixel grid
var screenOffset = new float2((int)(ra.X * deviceScale + 0.5f), (int)(ra.Y * deviceScale + 0.5f)) / deviceScale - ra;
Game.Renderer.RgbaSpriteRenderer.DrawSpriteWithTint(g.Sprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
ra + screenOffset,
rb + screenOffset,
rc + screenOffset,
rd + screenOffset,
tint);
tint, 1f);
}
p += new float2(g.Advance / deviceScale, 0);
@@ -238,16 +232,26 @@ namespace OpenRA.Graphics
if (string.IsNullOrEmpty(text))
return new int2(0, size);
var lines = text.Split('\n');
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
var lines = text.SplitLines('\n');
var maxWidth = 0f;
var rows = 0;
foreach (var line in lines)
{
rows++;
maxWidth = Math.Max(maxWidth, LineWidth(line));
}
return new int2((int)Math.Ceiling(maxWidth), rows * size);
}
static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
float LineWidth(ReadOnlySpan<char> line)
{
var maxWidth = 0f;
foreach (var line in lines)
maxWidth = Math.Max(maxWidth, lineWidth(line));
return maxWidth;
var result = 0f;
foreach (var c in line)
result += glyphs[c].Advance;
return result / deviceScale;
}
GlyphInfo CreateGlyph(char c)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -18,11 +18,33 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public enum SpriteFrameType { Indexed, BGRA }
/// <summary>
/// Describes the format of the pixel data in a ISpriteFrame.
/// Note that the channel order is defined for little-endian bytes, so BGRA corresponds
/// to a 32bit ARGB value, such as that returned by Color.ToArgb()!
/// </summary>
public enum SpriteFrameType
{
// 8 bit index into an external palette
Indexed8,
// 32 bit color such as returned by Color.ToArgb() or the bmp file format
// (remember that little-endian systems place the little bits in the first byte!)
Bgra32,
// Like BGRA, but without an alpha channel
Bgr24,
// 32 bit color in big-endian format, like png
Rgba32,
// Like RGBA, but without an alpha channel
Rgb24
}
public interface ISpriteLoader
{
bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata);
bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata);
}
public interface ISpriteFrame
@@ -47,7 +69,7 @@ namespace OpenRA.Graphics
public class SpriteCache
{
public readonly Cache<SpriteFrameType, SheetBuilder> SheetBuilders;
public readonly Cache<SheetType, SheetBuilder> SheetBuilders;
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;
@@ -57,7 +79,7 @@ namespace OpenRA.Graphics
public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders)
{
SheetBuilders = new Cache<SpriteFrameType, SheetBuilder>(t => new SheetBuilder(SheetBuilder.FrameTypeToSheetType(t)));
SheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t));
this.fileSystem = fileSystem;
this.loaders = loaders;
@@ -103,7 +125,7 @@ namespace OpenRA.Graphics
{
if (unloaded[i] != null)
{
sprite[i] = SheetBuilders[unloaded[i].Type].Add(unloaded[i]);
sprite[i] = SheetBuilders[SheetBuilder.FrameTypeToSheetType(unloaded[i].Type)].Add(unloaded[i]);
unloaded[i] = null;
}
}
@@ -142,7 +164,7 @@ namespace OpenRA.Graphics
frames = new Cache<string, ISpriteFrame[]>(filename => FrameLoader.GetFrames(fileSystem, filename, loaders, out _));
}
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
public ISpriteFrame[] this[string filename] => frames[filename];
}
public static class FrameLoader
@@ -151,7 +173,7 @@ namespace OpenRA.Graphics
{
using (var stream = fileSystem.Open(filename))
{
var spriteFrames = GetFrames(stream, loaders, out metadata);
var spriteFrames = GetFrames(stream, loaders, filename, out metadata);
if (spriteFrames == null)
throw new InvalidDataException(filename + " is not a valid sprite file!");
@@ -159,12 +181,12 @@ namespace OpenRA.Graphics
}
}
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, out TypeDictionary metadata)
public static ISpriteFrame[] GetFrames(Stream stream, ISpriteLoader[] loaders, string filename, out TypeDictionary metadata)
{
metadata = null;
foreach (var loader in loaders)
if (loader.TryParseSprite(stream, out var frames, out metadata))
if (loader.TryParseSprite(stream, filename, out var frames, out metadata))
return frames;
return null;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public struct SpriteRenderable : IRenderable, ITintableRenderable, IFinalizedRenderable
public class SpriteRenderable : IPalettedRenderable, IModifyableRenderable, IFinalizedRenderable
{
public static readonly IEnumerable<IRenderable> None = new IRenderable[0];
@@ -25,16 +25,11 @@ namespace OpenRA.Graphics
readonly PaletteReference palette;
readonly float scale;
readonly float3 tint;
readonly TintModifiers tintModifiers;
readonly float alpha;
readonly bool isDecoration;
readonly bool ignoreWorldTint;
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, false) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, bool isDecoration, bool ignoreWorldTint)
: this(sprite, pos, offset, zOffset, palette, scale, float3.Ones, isDecoration, ignoreWorldTint) { }
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float3 tint, bool isDecoration, bool ignoreWorldTint)
public SpriteRenderable(Sprite sprite, WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale, float alpha, float3 tint, TintModifiers tintModifiers, bool isDecoration)
{
this.sprite = sprite;
this.pos = pos;
@@ -44,44 +39,61 @@ namespace OpenRA.Graphics
this.scale = scale;
this.tint = tint;
this.isDecoration = isDecoration;
this.ignoreWorldTint = ignoreWorldTint;
this.tintModifiers = tintModifiers;
this.alpha = alpha;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
this.palette = null;
}
public WPos Pos { get { return pos + offset; } }
public WVec Offset { get { return offset; } }
public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public bool IsDecoration { get { return isDecoration; } }
public WPos Pos => pos + offset;
public WVec Offset => offset;
public PaletteReference Palette => palette;
public int ZOffset => zOffset;
public bool IsDecoration => isDecoration;
public IRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public float Alpha => alpha;
public float3 Tint => tint;
public TintModifiers TintModifiers => tintModifiers;
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new SpriteRenderable(sprite, pos, offset, zOffset, newPalette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable WithZOffset(int newOffset) { return new SpriteRenderable(sprite, pos, offset, newOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable OffsetBy(in WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, alpha, tint, tintModifiers, isDecoration); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, tint, tintModifiers, true); }
public IModifyableRenderable WithAlpha(float newAlpha)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newAlpha, tint, tintModifiers, isDecoration);
}
public IModifyableRenderable WithTint(in float3 newTint, TintModifiers newTintModifiers)
{
return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, alpha, newTint, newTintModifiers, isDecoration);
}
float3 ScreenPosition(WorldRenderer wr)
{
var xy = wr.ScreenPxPosition(pos) + wr.ScreenPxOffset(offset) - (0.5f * scale * sprite.Size.XY).ToInt2();
// HACK: The z offset needs to be applied somewhere, but this probably is the wrong place.
return new float3(xy, sprite.Offset.Z + wr.ScreenZPosition(pos, 0) - 0.5f * scale * sprite.Size.Z);
var s = 0.5f * scale * sprite.Size;
return wr.Screen3DPxPosition(pos) + wr.ScreenPxOffset(offset) - new float3((int)s.X, (int)s.Y, s.Z);
}
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
var wsr = Game.Renderer.WorldSpriteRenderer;
if (ignoreWorldTint)
wsr.DrawSprite(sprite, ScreenPosition(wr), palette, scale * sprite.Size);
else
{
var t = tint;
if (wr.TerrainLighting != null)
t *= wr.TerrainLighting.TintAt(pos);
var t = alpha * tint;
if (wr.TerrainLighting != null && (tintModifiers & TintModifiers.IgnoreWorldTint) == 0)
t *= wr.TerrainLighting.TintAt(pos);
wsr.DrawSpriteWithTint(sprite, ScreenPosition(wr), palette, scale * sprite.Size, t);
}
// Shader interprets negative alpha as a flag to use the tint colour directly instead of multiplying the sprite colour
var a = alpha;
if ((tintModifiers & TintModifiers.ReplaceColor) != 0)
a *= -1;
wsr.DrawSprite(sprite, palette, ScreenPosition(wr), scale, t, a);
}
public void RenderDebugGeometry(WorldRenderer wr)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -10,6 +10,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Primitives;
@@ -17,8 +18,8 @@ namespace OpenRA.Graphics
{
public class SpriteRenderer : Renderer.IBatchRenderer
{
const int SheetCount = 7;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
public const int SheetCount = 8;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => $"Texture{i}");
readonly Renderer renderer;
readonly IShader shader;
@@ -81,119 +82,165 @@ namespace OpenRA.Graphics
for (; secondarySheetIndex < ns; secondarySheetIndex++)
if (sheets[secondarySheetIndex] == secondarySheet)
break;
// If neither sheet has been mapped both index values will be set to ns.
// This is fine if they both reference the same texture, but if they don't
// we must increment the secondary sheet index to the next free sampler.
if (secondarySheetIndex == sheetIndex && secondarySheet != sheet)
secondarySheetIndex++;
}
// Make sure that we have enough free samplers to map both if needed, otherwise flush
var needSamplers = (sheetIndex == ns ? 1 : 0) + (secondarySheetIndex == ns ? 1 : 0);
if (ns + needSamplers >= sheets.Length)
if (Math.Max(sheetIndex, secondarySheetIndex) >= sheets.Length)
{
Flush();
sheetIndex = 0;
if (ss != null)
secondarySheetIndex = 1;
secondarySheetIndex = ss != null && ss.SecondarySheet != sheet ? 1 : 0;
}
if (sheetIndex >= ns)
{
sheets[sheetIndex] = sheet;
ns += 1;
ns++;
}
if (secondarySheetIndex >= ns && ss != null)
{
sheets[secondarySheetIndex] = ss.SecondarySheet;
ns += 1;
ns++;
}
return new int2(sheetIndex, secondarySheetIndex);
}
internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
float ResolveTextureIndex(Sprite s, PaletteReference pal)
{
if (pal == null)
return 0;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (s.Channel == TextureChannel.RGBA && !pal.HasColorShift)
return 0;
return pal.TextureIndex;
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, in float3 scale)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f);
nv += 6;
}
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
{
DrawSprite(s, location, pal.TextureIndex, s.Size);
}
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
{
DrawSprite(s, location, pal.TextureIndex, size);
}
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, float3.Ones, 1f);
nv += 6;
}
internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale = 1f)
{
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale);
}
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 location, float scale, in float3 tint, float alpha)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
Util.FastCreateQuad(vertices, location + scale * s.Offset, s, samplers, paletteTextureIndex, nv, scale * s.Size, tint, alpha);
nv += 6;
}
public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
public void DrawSprite(Sprite s, PaletteReference pal, in float3 location, float scale, in float3 tint, float alpha)
{
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
DrawSprite(s, ResolveTextureIndex(s, pal), location, scale, tint, alpha);
}
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
internal void DrawSprite(Sprite s, float paletteTextureIndex, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint, float alpha)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, paletteTextureIndex, tint, alpha, nv);
nv += 6;
}
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, Sheet sheet, BlendMode blendMode)
public void DrawVertexBuffer(IVertexBuffer<Vertex> buffer, int start, int length, PrimitiveType type, IEnumerable<Sheet> sheets, BlendMode blendMode)
{
shader.SetTexture("Texture0", sheet.GetTexture());
var i = 0;
foreach (var s in sheets)
{
if (i >= SheetCount)
ThrowSheetOverflow(nameof(sheets));
if (s != null)
shader.SetTexture(SheetIndexToTextureName[i++], s.GetTexture());
}
renderer.Context.SetBlendMode(blendMode);
shader.PrepareRender();
renderer.DrawBatch(buffer, start, length, type);
renderer.Context.SetBlendMode(BlendMode.None);
}
// PERF: methods that throw won't be inlined by the JIT, so extract a static helper for use on hot paths
static void ThrowSheetOverflow(string paramName)
{
throw new ArgumentException($"SpriteRenderer only supports {SheetCount} simultaneous textures", paramName);
}
// For RGBAColorRenderer
internal void DrawRGBAVertices(Vertex[] v)
internal void DrawRGBAVertices(Vertex[] v, BlendMode blendMode)
{
renderer.CurrentBatchRenderer = this;
if (currentBlend != BlendMode.Alpha || nv + v.Length > renderer.TempBufferSize)
if (currentBlend != blendMode || nv + v.Length > renderer.TempBufferSize)
Flush();
currentBlend = BlendMode.Alpha;
currentBlend = blendMode;
Array.Copy(v, 0, vertices, nv, v.Length);
nv += v.Length;
}
public void SetPalette(ITexture palette)
public void SetPalette(ITexture palette, ITexture colorShifts)
{
shader.SetTexture("Palette", palette);
shader.SetTexture("ColorShifts", colorShifts);
}
public void SetViewportParams(Size screen, float depthScale, float depthOffset, int2 scroll)
public void SetViewportParams(Size sheetSize, int downscale, float depthMargin, int2 scroll)
{
shader.SetVec("Scroll", scroll.X, scroll.Y, scroll.Y);
shader.SetVec("r1",
2f / screen.Width,
2f / screen.Height,
-depthScale / screen.Height);
shader.SetVec("r2", -1, -1, 1 - depthOffset);
// Calculate the scale (r1) and offset (r2) that convert from OpenRA viewport pixels
// to OpenGL normalized device coordinates (NDC). OpenGL expects coordinates to vary from [-1, 1],
// so we rescale viewport pixels to the range [0, 2] using r1 then subtract 1 using r2.
var width = 2f / (downscale * sheetSize.Width);
var height = 2f / (downscale * sheetSize.Height);
// Texture index is sampled as a float, so convert to pixels then scale
shader.SetVec("DepthTextureScale", 128 * depthScale / screen.Height);
// Depth is more complicated:
// * The OpenGL z axis is inverted (negative is closer) relative to OpenRA (positive is closer).
// * We want to avoid clipping pixels that are behind the nominal z == y plane at the
// top of the map, or above the nominal z == y plane at the bottom of the map.
// We therefore expand the depth range by an extra margin that is calculated based on
// the maximum expected world height (see Renderer.InitializeDepthBuffer).
// * Sprites can specify an additional per-pixel depth offset map, which is applied in the
// fragment shader. The fragment shader operates in OpenGL window coordinates, not NDC,
// with a depth range [0, 1] corresponding to the NDC [-1, 1]. We must therefore multiply the
// sprite channel value [0, 1] by 255 to find the pixel depth offset, then by our depth scale
// to find the equivalent NDC offset, then divide by 2 to find the window coordinate offset.
// * If depthMargin == 0 (which indicates per-pixel depth testing is disabled) sprites that
// extend beyond the top of bottom edges of the screen may be pushed outside [-1, 1] and
// culled by the GPU. We avoid this by forcing everything into the z = 0 plane.
var depth = depthMargin != 0f ? 2f / (downscale * (sheetSize.Height + depthMargin)) : 0;
shader.SetVec("DepthTextureScale", 128 * depth);
shader.SetVec("Scroll", scroll.X, scroll.Y, depthMargin != 0f ? scroll.Y : 0);
shader.SetVec("r1", width, height, -depth);
shader.SetVec("r2", -1, -1, depthMargin != 0f ? 1 : 0);
}
public void SetDepthPreviewEnabled(bool enabled)
public void SetDepthPreview(bool enabled, float contrast, float offset)
{
shader.SetBool("EnableDepthPreview", enabled);
shader.SetVec("DepthPreviewParams", contrast, offset);
}
public void SetAntialiasingPixelsPerTexel(float pxPerTx)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -15,7 +15,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public struct TargetLineRenderable : IRenderable, IFinalizedRenderable
public class TargetLineRenderable : IRenderable, IFinalizedRenderable
{
readonly IEnumerable<WPos> waypoints;
readonly Color color;
@@ -30,14 +30,19 @@ namespace OpenRA.Graphics
this.markerSize = markerSize;
}
public WPos Pos { get { return waypoints.First(); } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return 0; } }
public bool IsDecoration { get { return true; } }
public WPos Pos => waypoints.First();
public int ZOffset => 0;
public bool IsDecoration => true;
public IRenderable WithPalette(PaletteReference newPalette) { return new TargetLineRenderable(waypoints, color); }
public IRenderable WithZOffset(int newOffset) { return new TargetLineRenderable(waypoints, color); }
public IRenderable OffsetBy(WVec vec) { return new TargetLineRenderable(waypoints.Select(w => w + vec), color); }
public IRenderable OffsetBy(in WVec vec)
{
// Lambdas can't use 'in' variables, so capture a copy for later
var offset = vec;
return new TargetLineRenderable(waypoints.Select(w => w + offset), color);
}
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -20,9 +20,9 @@ namespace OpenRA.Graphics
{
static readonly int[] CornerVertexMap = { 0, 1, 2, 2, 3, 0 };
public readonly Sheet Sheet;
public readonly BlendMode BlendMode;
readonly Sheet[] sheets;
readonly Sprite emptySprite;
readonly IVertexBuffer<Vertex> vertexBuffer;
@@ -35,22 +35,22 @@ namespace OpenRA.Graphics
readonly WorldRenderer worldRenderer;
readonly Map map;
readonly PaletteReference palette;
readonly PaletteReference[] palettes;
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds)
public TerrainSpriteLayer(World world, WorldRenderer wr, Sprite emptySprite, BlendMode blendMode, bool restrictToBounds)
{
worldRenderer = wr;
this.restrictToBounds = restrictToBounds;
Sheet = sheet;
this.emptySprite = emptySprite;
sheets = new Sheet[SpriteRenderer.SheetCount];
BlendMode = blendMode;
this.palette = palette;
map = world.Map;
rowStride = 6 * map.MapSize.X;
vertices = new Vertex[rowStride * map.MapSize.Y];
palettes = new PaletteReference[map.MapSize.X * map.MapSize.Y];
vertexBuffer = Game.Renderer.Context.CreateVertexBuffer(vertices.Length);
emptySprite = new Sprite(sheet, Rectangle.Empty, TextureChannel.Alpha);
wr.PaletteInvalidated += UpdatePaletteIndices;
@@ -63,12 +63,11 @@ namespace OpenRA.Graphics
void UpdatePaletteIndices()
{
// Everything in the layer uses the same palette,
// so we can fix the indices in one pass
for (var i = 0; i < vertices.Length; i++)
{
var v = vertices[i];
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, v.R, v.G, v.B);
var p = palettes[i / 6]?.TextureIndex ?? 0;
vertices[i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, p, v.C, v.R, v.G, v.B, v.A);
}
for (var row = 0; row < map.MapSize.Y; row++)
@@ -77,24 +76,24 @@ namespace OpenRA.Graphics
public void Clear(CPos cell)
{
Update(cell, null, true);
Update(cell, null, null, 1f, 1f, true);
}
public void Update(CPos cell, ISpriteSequence sequence, int frame)
public void Update(CPos cell, ISpriteSequence sequence, PaletteReference palette, int frame)
{
Update(cell, sequence.GetSprite(frame), sequence.IgnoreWorldTint);
Update(cell, sequence.GetSprite(frame), palette, sequence.Scale, sequence.GetAlpha(frame), sequence.IgnoreWorldTint);
}
public void Update(CPos cell, Sprite sprite, bool ignoreTint)
public void Update(CPos cell, Sprite sprite, PaletteReference palette, float scale = 1f, float alpha = 1f, bool ignoreTint = false)
{
var xyz = float3.Zero;
if (sprite != null)
{
var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset);
xyz = worldRenderer.Screen3DPosition(cellOrigin) + sprite.Offset - 0.5f * sprite.Size;
xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size);
}
Update(cell.ToMPos(map.Grid.Type), sprite, xyz, ignoreTint);
Update(cell.ToMPos(map.Grid.Type), sprite, palette, xyz, scale, alpha, ignoreTint);
}
void UpdateTint(MPos uv)
@@ -102,11 +101,10 @@ namespace OpenRA.Graphics
var offset = rowStride * uv.V + 6 * uv.U;
if (ignoreTint[offset])
{
var noTint = float3.Ones;
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, noTint);
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * float3.Ones, v.A);
}
return;
@@ -131,31 +129,61 @@ namespace OpenRA.Graphics
for (var i = 0; i < 6; i++)
{
var v = vertices[offset + i];
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, palette.TextureIndex, v.C, weights[CornerVertexMap[i]]);
vertices[offset + i] = new Vertex(v.X, v.Y, v.Z, v.S, v.T, v.U, v.V, v.P, v.C, v.A * weights[CornerVertexMap[i]], v.A);
}
dirtyRows.Add(uv.V);
}
public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
int GetOrAddSheetIndex(Sheet sheet)
{
if (sheet == null)
return 0;
for (var i = 0; i < sheets.Length; i++)
{
if (sheets[i] == sheet)
return i;
if (sheets[i] == null)
{
sheets[i] = sheet;
return i;
}
}
throw new InvalidDataException("Sheet overflow");
}
public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 pos, float scale, float alpha, bool ignoreTint)
{
int2 samplers;
if (sprite != null)
{
if (sprite.Sheet != Sheet)
throw new InvalidDataException("Attempted to add sprite from a different sheet");
if (sprite.BlendMode != BlendMode)
throw new InvalidDataException("Attempted to add sprite with a different blend mode");
samplers = new int2(GetOrAddSheetIndex(sprite.Sheet), GetOrAddSheetIndex((sprite as SpriteWithSecondaryData)?.SecondarySheet));
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the limitation that palettes are defined on traits rather than on sequences,
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
palette = null;
}
else
{
sprite = emptySprite;
samplers = int2.Zero;
}
// The vertex buffer does not have geometry for cells outside the map
if (!map.Tiles.Contains(uv))
return;
var offset = rowStride * uv.V + 6 * uv.U;
Util.FastCreateQuad(vertices, pos, sprite, int2.Zero, palette.TextureIndex, offset, sprite.Size, float3.Ones);
Util.FastCreateQuad(vertices, pos, sprite, samplers, palette?.TextureIndex ?? 0, offset, scale * sprite.Size, alpha * float3.Ones, alpha);
palettes[uv.V * map.MapSize.X + uv.U] = palette;
if (worldRenderer.TerrainLighting != null)
{
@@ -188,7 +216,7 @@ namespace OpenRA.Graphics
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(
vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow),
PrimitiveType.TriangleList, Sheet, BlendMode);
PrimitiveType.TriangleList, sheets, BlendMode);
Game.Renderer.Flush();
}

View File

@@ -1,197 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA.Graphics
{
class TheaterTemplate
{
public readonly Sprite[] Sprites;
public readonly int Stride;
public readonly int Variants;
public TheaterTemplate(Sprite[] sprites, int stride, int variants)
{
Sprites = sprites;
Stride = stride;
Variants = variants;
}
}
public sealed class Theater : IDisposable
{
readonly Dictionary<ushort, TheaterTemplate> templates = new Dictionary<ushort, TheaterTemplate>();
SheetBuilder sheetBuilder;
readonly Sprite missingTile;
readonly MersenneTwister random;
TileSet tileset;
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
{
this.tileset = tileset;
var allocated = false;
Func<Sheet> allocate = () =>
{
if (allocated)
throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter.");
allocated = true;
return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize));
};
random = new MersenneTwister();
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders);
foreach (var t in tileset.Templates)
{
var variants = new List<Sprite[]>();
foreach (var i in t.Value.Images)
{
ISpriteFrame[] allFrames;
if (onMissingImage != null)
{
try
{
allFrames = frameCache[i];
}
catch (FileNotFoundException)
{
onMissingImage(t.Key, i);
continue;
}
}
else
allFrames = frameCache[i];
var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length;
var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j);
var start = indices.Min();
var end = indices.Max();
if (start < 0 || end >= frameCount)
throw new YamlException("Template `{0}` uses frames [{1}..{2}] of {3}, but only [0..{4}] actually exist"
.F(t.Key, start, end, i, frameCount - 1));
variants.Add(indices.Select(j =>
{
var f = allFrames[j];
var tile = t.Value.Contains(j) ? t.Value[j] : null;
// The internal z axis is inverted from expectation (negative is closer)
var zOffset = tile != null ? -tile.ZOffset : 0;
var zRamp = tile != null ? tile.ZRamp : 1f;
var offset = new float3(f.Offset, zOffset);
var type = SheetBuilder.FrameTypeToSheetType(f.Type);
// Defer SheetBuilder creation until we know what type of frames we are loading!
// TODO: Support mixed indexed and BGRA frames
if (sheetBuilder == null)
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
else if (type != sheetBuilder.Type)
throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
var s = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(s, f.Data);
if (tileset.EnableDepth)
{
var ss = sheetBuilder.Allocate(f.Size, zRamp, offset);
Util.FastCopyIntoChannel(ss, allFrames[j + frameCount].Data);
// s and ss are guaranteed to use the same sheet
// because of the custom terrain sheet allocation
s = new SpriteWithSecondaryData(s, s.Sheet, ss.Bounds, ss.Channel);
}
return s;
}).ToArray());
}
var allSprites = variants.SelectMany(s => s);
// Ignore the offsets baked into R8 sprites
if (tileset.IgnoreTileSpriteOffsets)
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
if (onMissingImage != null && !variants.Any())
continue;
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length));
}
// 1x1px transparent tile
missingTile = sheetBuilder.Add(new byte[sheetBuilder.Type == SheetType.BGRA ? 4 : 1], new Size(1, 1));
Sheet.ReleaseBuffer();
}
public bool HasTileSprite(TerrainTile r, int? variant = null)
{
return TileSprite(r, variant) != missingTile;
}
public Sprite TileSprite(TerrainTile r, int? variant = null)
{
if (!templates.TryGetValue(r.Type, out var template))
return missingTile;
if (r.Index >= template.Stride)
return missingTile;
var start = template.Variants > 1 ? variant.HasValue ? variant.Value : random.Next(template.Variants) : 0;
return template.Sprites[start * template.Stride + r.Index];
}
public Rectangle TemplateBounds(TerrainTemplateInfo template, Size tileSize, MapGridType mapGrid)
{
Rectangle? templateRect = null;
var i = 0;
for (var y = 0; y < template.Size.Y; y++)
{
for (var x = 0; x < template.Size.X; x++)
{
var tile = new TerrainTile(template.Id, (byte)(i++));
var tileInfo = tileset.GetTileInfo(tile);
// Empty tile
if (tileInfo == null)
continue;
var sprite = TileSprite(tile);
var u = mapGrid == MapGridType.Rectangular ? x : (x - y) / 2f;
var v = mapGrid == MapGridType.Rectangular ? y : (x + y) / 2f;
var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size;
var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y);
templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect;
}
}
return templateRect.HasValue ? templateRect.Value : Rectangle.Empty;
}
public Sheet Sheet { get { return sheetBuilder.Current; } }
public void Dispose()
{
sheetBuilder.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -13,7 +13,7 @@ using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public struct UISpriteRenderable : IRenderable, IFinalizedRenderable
public class UISpriteRenderable : IRenderable, IPalettedRenderable, IFinalizedRenderable
{
readonly Sprite sprite;
readonly WPos effectiveWorldPos;
@@ -21,8 +21,9 @@ namespace OpenRA.Graphics
readonly int zOffset;
readonly PaletteReference palette;
readonly float scale;
readonly float alpha;
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale)
public UISpriteRenderable(Sprite sprite, WPos effectiveWorldPos, int2 screenPos, int zOffset, PaletteReference palette, float scale = 1f, float alpha = 1f)
{
this.sprite = sprite;
this.effectiveWorldPos = effectiveWorldPos;
@@ -30,25 +31,32 @@ namespace OpenRA.Graphics
this.zOffset = zOffset;
this.palette = palette;
this.scale = scale;
this.alpha = alpha;
// PERF: Remove useless palette assignments for RGBA sprites
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed
if (sprite.Channel == TextureChannel.RGBA && !(palette?.HasColorShift ?? false))
this.palette = null;
}
// Does not exist in the world, so a world positions don't make sense
public WPos Pos { get { return effectiveWorldPos; } }
public WVec Offset { get { return WVec.Zero; } }
public bool IsDecoration { get { return true; } }
public WPos Pos => effectiveWorldPos;
public WVec Offset => WVec.Zero;
public bool IsDecoration => true;
public PaletteReference Palette { get { return palette; } }
public int ZOffset { get { return zOffset; } }
public PaletteReference Palette => palette;
public int ZOffset => zOffset;
public IRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale); }
public IPalettedRenderable WithPalette(PaletteReference newPalette) { return new UISpriteRenderable(sprite, effectiveWorldPos, screenPos, zOffset, newPalette, scale, alpha); }
public IRenderable WithZOffset(int newOffset) { return this; }
public IRenderable OffsetBy(WVec vec) { return this; }
public IRenderable OffsetBy(in WVec vec) { return this; }
public IRenderable AsDecoration() { return this; }
public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; }
public void Render(WorldRenderer wr)
{
Game.Renderer.SpriteRenderer.DrawSprite(sprite, screenPos, palette, scale * sprite.Size);
Game.Renderer.SpriteRenderer.DrawSprite(sprite, palette, screenPos, scale, float3.Ones, alpha);
}
public void RenderDebugGeometry(WorldRenderer wr)

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -20,29 +20,28 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint)
public static void FastCreateQuad(Vertex[] vertices, in float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, in float3 size, in float3 tint, float alpha)
{
var b = new float3(o.X + size.X, o.Y, o.Z);
var c = new float3(o.X + size.X, o.Y + size.Y, o.Z + size.Z);
var d = new float3(o.X, o.Y + size.Y, o.Z + size.Z);
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, nv);
FastCreateQuad(vertices, o, b, c, d, r, samplers, paletteTextureIndex, tint, alpha, nv);
}
public static void FastCreateQuad(Vertex[] vertices,
in float3 a, in float3 b, in float3 c, in float3 d,
Sprite r, int2 samplers, float paletteTextureIndex,
in float3 tint, int nv)
in float3 tint, float alpha, int nv)
{
float sl = 0;
float st = 0;
float sr = 0;
float sb = 0;
// See shp.vert for documentation on the channel attribute format
// See combined.vert for documentation on the channel attribute format
var attribC = r.Channel == TextureChannel.RGBA ? 0x02 : ((byte)r.Channel) << 1 | 0x01;
attribC |= samplers.X << 6;
var ss = r as SpriteWithSecondaryData;
if (ss != null)
if (r is SpriteWithSecondaryData ss)
{
sl = ss.SecondaryLeft;
st = ss.SecondaryTop;
@@ -54,15 +53,15 @@ namespace OpenRA.Graphics
}
var fAttribC = (float)attribC;
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint);
vertices[nv] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 1] = new Vertex(b, r.Right, r.Top, sr, st, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 2] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 3] = new Vertex(c, r.Right, r.Bottom, sr, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 4] = new Vertex(d, r.Left, r.Bottom, sl, sb, paletteTextureIndex, fAttribC, tint, alpha);
vertices[nv + 5] = new Vertex(a, r.Left, r.Top, sl, st, paletteTextureIndex, fAttribC, tint, alpha);
}
public static void FastCopyIntoChannel(Sprite dest, byte[] src)
public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType)
{
var destData = dest.Sheet.GetData();
var width = dest.Bounds.Width;
@@ -85,12 +84,34 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < width; i++)
{
var r = src[k++];
var g = src[k++];
var b = src[k++];
var a = src[k++];
var cc = Color.FromArgb(a, r, g, b);
byte r, g, b, a;
switch (srcType)
{
case SpriteFrameType.Bgra32:
case SpriteFrameType.Bgr24:
{
b = src[k++];
g = src[k++];
r = src[k++];
a = srcType == SpriteFrameType.Bgra32 ? src[k++] : (byte)255;
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
r = src[k++];
g = src[k++];
b = src[k++];
a = srcType == SpriteFrameType.Rgba32 ? src[k++] : (byte)255;
break;
}
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {srcType}");
}
var cc = Color.FromArgb(a, r, g, b);
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
}
}
@@ -139,16 +160,29 @@ namespace OpenRA.Graphics
for (var i = 0; i < width; i++)
{
Color cc;
if (src.Palette == null)
switch (src.Type)
{
var r = src.Data[k++];
var g = src.Data[k++];
var b = src.Data[k++];
var a = src.Data[k++];
cc = Color.FromArgb(a, r, g, b);
case SpriteFrameType.Indexed8:
{
cc = src.Palette[src.Data[k++]];
break;
}
case SpriteFrameType.Rgba32:
case SpriteFrameType.Rgb24:
{
var r = src.Data[k++];
var g = src.Data[k++];
var b = src.Data[k++];
var a = src.Type == SpriteFrameType.Rgba32 ? src.Data[k++] : (byte)255;
cc = Color.FromArgb(a, r, g, b);
break;
}
// Pngs don't support BGR[A], so no need to include them here
default:
throw new InvalidOperationException($"Unknown SpriteFrameType {src.Type}");
}
else
cc = src.Palette[src.Data[k++]];
data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb();
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,7 +14,7 @@ using System.Runtime.InteropServices;
namespace OpenRA.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
public readonly struct Vertex
{
// 3d position
public readonly float X, Y, Z;
@@ -26,24 +26,24 @@ namespace OpenRA.Graphics
public readonly float P, C;
// Color tint
public readonly float R, G, B;
public readonly float R, G, B, A;
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones) { }
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, float3.Ones, 1f) { }
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(in float3 xyz, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
: this(xyz.X, xyz.Y, xyz.Z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, in float3 tint, float a)
: this(x, y, z, s, t, u, v, p, c, tint.X, tint.Y, tint.Z, a) { }
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b)
public Vertex(float x, float y, float z, float s, float t, float u, float v, float p, float c, float r, float g, float b, float a)
{
X = x; Y = y; Z = z;
S = s; T = t;
U = u; V = v;
P = p; C = c;
R = r; G = g; B = b;
R = r; G = g; B = b; A = a;
}
}
}

View File

@@ -0,0 +1,37 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 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.FileSystem;
using OpenRA.Primitives;
namespace OpenRA.Video
{
public interface IVideo
{
ushort Frames { get; }
byte Framerate { get; }
ushort Width { get; }
ushort Height { get; }
uint[,] FrameData { get; }
int CurrentFrame { get; }
void AdvanceFrame();
bool HasAudio { get; }
byte[] AudioData { get; }
int AudioChannels { get; }
int SampleBits { get; }
int SampleRate { get; }
void Reset();
}
}

View File

@@ -0,0 +1,32 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 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.IO;
namespace OpenRA.Video
{
public interface IVideoLoader
{
bool TryParseVideo(Stream s, out IVideo video);
}
public static class VideoLoader
{
public static IVideo GetVideo(Stream stream, IVideoLoader[] loaders)
{
foreach (var loader in loaders)
if (loader.TryParseVideo(stream, out var video))
return video;
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -51,11 +51,11 @@ namespace OpenRA.Graphics
// Viewport geometry (world-px)
public int2 CenterLocation { get; private set; }
public WPos CenterPosition { get { return worldRenderer.ProjectedPosition(CenterLocation); } }
public WPos CenterPosition => worldRenderer.ProjectedPosition(CenterLocation);
public Rectangle Rectangle { get { return new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y)); } }
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
public Rectangle Rectangle => new Rectangle(TopLeft, new Size(viewportSize.X, viewportSize.Y));
public int2 TopLeft => CenterLocation - viewportSize / 2;
public int2 BottomRight => CenterLocation + viewportSize / 2;
int2 viewportSize;
ProjectedCellRegion cells;
bool cellsDirty = true;
@@ -75,10 +75,7 @@ namespace OpenRA.Graphics
public float Zoom
{
get
{
return zoom;
}
get => zoom;
private set
{
@@ -89,7 +86,7 @@ namespace OpenRA.Graphics
}
}
public float MinZoom { get { return minZoom; } }
public float MinZoom => minZoom;
public void AdjustZoom(float dz)
{
@@ -253,6 +250,9 @@ namespace OpenRA.Graphics
else
Zoom = Zoom.Clamp(minZoom, maxZoom);
var maxSize = (1f / (unlockMinZoom ? unlockedMinZoom : minZoom) * new float2(Game.Renderer.NativeResolution));
Game.Renderer.SetMaximumViewportSize(new Size((int)maxSize.X, (int)maxSize.Y));
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -21,12 +21,11 @@ namespace OpenRA.Graphics
public sealed class WorldRenderer : IDisposable
{
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
r => ZPosition(r.Pos, r.ZOffset);
r => r.Pos.Y + r.Pos.Z + r.ZOffset;
public readonly Size TileSize;
public readonly int TileScale;
public readonly World World;
public readonly Theater Theater;
public Viewport Viewport { get; private set; }
public readonly ITerrainLighting TerrainLighting;
@@ -46,8 +45,6 @@ namespace OpenRA.Graphics
readonly List<IRenderable> renderablesBuffer = new List<IRenderable>();
bool lastDepthPreviewEnabled;
internal WorldRenderer(ModData modData, World world)
{
World = world;
@@ -68,7 +65,6 @@ namespace OpenRA.Graphics
palette.Initialize();
Theater = new Theater(world.Map.Rules.TileSet);
TerrainLighting = world.WorldActor.TraitOrDefault<ITerrainLighting>();
terrainRenderer = world.WorldActor.TraitOrDefault<IRenderTerrain>();
@@ -87,7 +83,13 @@ namespace OpenRA.Graphics
return new PaletteReference(name, palette.GetPaletteIndex(name), pal, palette);
}
public PaletteReference Palette(string name) { return palettes.GetOrAdd(name, createPaletteReference); }
public PaletteReference Palette(string name)
{
// HACK: This is working around the fact that palettes are defined on traits rather than sequences
// and can be removed once this has been fixed.
return name == null ? null : palettes.GetOrAdd(name, createPaletteReference);
}
public void AddPalette(string name, ImmutablePalette pal, bool allowModifiers = false, bool allowOverwrite = false)
{
if (allowOverwrite && palette.Contains(name))
@@ -111,6 +113,11 @@ namespace OpenRA.Graphics
palettes[name].Palette = pal;
}
public void SetPaletteColorShift(string name, float hueOffset, float satOffset, float minHue, float maxHue)
{
palette.SetColorShift(name, hueOffset, satOffset, minHue, maxHue);
}
// PERF: Avoid LINQ.
void GenerateRenderables()
{
@@ -247,11 +254,7 @@ namespace OpenRA.Graphics
if (World.WorldActor.Disposed)
return;
if (debugVis.Value != null && lastDepthPreviewEnabled != debugVis.Value.DepthBuffer)
{
lastDepthPreviewEnabled = debugVis.Value.DepthBuffer;
Game.Renderer.WorldSpriteRenderer.SetDepthPreviewEnabled(lastDepthPreviewEnabled);
}
debugVis.Value?.UpdateDepthBuffer();
var bounds = Viewport.GetScissorBounds(World.Type != WorldType.Editor);
Game.Renderer.EnableScissor(bounds);
@@ -356,7 +359,13 @@ namespace OpenRA.Graphics
public float3 Screen3DPosition(WPos pos)
{
var z = ZPosition(pos, 0) * (float)TileSize.Height / TileScale;
// The projection from world coordinates to screen coordinates has
// a non-obvious relationship between the y and z coordinates:
// * A flat surface with constant y (e.g. a vertical wall) in world coordinates
// transforms into a flat surface with constant z (depth) in screen coordinates.
// * Increasing the world y coordinate increases screen y and z coordinates equally.
// * Increases the world z coordinate decreases screen y but doesn't change screen z.
var z = pos.Y * (float)TileSize.Height / TileScale;
return new float3((float)TileSize.Width * pos.X / TileScale, (float)TileSize.Height * (pos.Y - pos.Z) / TileScale, z);
}
@@ -375,7 +384,7 @@ namespace OpenRA.Graphics
}
// For scaling vectors to pixel sizes in the model renderer
public float3 ScreenVectorComponents(WVec vec)
public float3 ScreenVectorComponents(in WVec vec)
{
return new float3(
(float)TileSize.Width * vec.X / TileScale,
@@ -384,29 +393,19 @@ namespace OpenRA.Graphics
}
// For scaling vectors to pixel sizes in the model renderer
public float[] ScreenVector(WVec vec)
public float[] ScreenVector(in WVec vec)
{
var xyz = ScreenVectorComponents(vec);
return new[] { xyz.X, xyz.Y, xyz.Z, 1f };
}
public int2 ScreenPxOffset(WVec vec)
public int2 ScreenPxOffset(in WVec vec)
{
// Round to nearest pixel
var xyz = ScreenVectorComponents(vec);
return new int2((int)Math.Round(xyz.X), (int)Math.Round(xyz.Y));
}
public float ScreenZPosition(WPos pos, int offset)
{
return ZPosition(pos, offset) * (float)TileSize.Height / TileScale;
}
static int ZPosition(WPos pos, int offset)
{
return pos.Y + pos.Z + offset;
}
/// <summary>
/// Returns a position in the world that is projected to the given screen position.
/// There are many possible world positions, and the returned value chooses the value with no elevation.
@@ -425,7 +424,6 @@ namespace OpenRA.Graphics
World.Dispose();
palette.Dispose();
Theater.Dispose();
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -96,14 +96,8 @@ namespace OpenRA
return null;
}
public HotkeyReference this[string name]
{
get
{
return new HotkeyReference(GetHotkeyReference(name));
}
}
public HotkeyReference this[string name] => new HotkeyReference(GetHotkeyReference(name));
public IEnumerable<HotkeyDefinition> Definitions { get { return definitions.Values; } }
public IEnumerable<HotkeyDefinition> Definitions => definitions.Values;
}
}

View File

@@ -0,0 +1,63 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 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.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace OpenRA
{
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
public static class HttpExtension
{
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
{
var total = response.Content.Headers.ContentLength ?? -1;
var canReportProgress = total > 0;
#if !MONO
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
#else
using (var contentStream = await response.Content.ReadAsStreamAsync())
#endif
{
var totalRead = 0L;
var buffer = new byte[8192];
var hasMoreToRead = true;
do
{
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
if (read == 0)
hasMoreToRead = false;
else
{
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
totalRead += read;
if (canReportProgress)
{
var progressPercentage = (int)((double)totalRead / total * 100);
onProgress?.Invoke(total, totalRead, progressPercentage);
}
}
}
while (hasMoreToRead && !token.IsCancellationRequested);
onProgress?.Invoke(total, totalRead, 100);
}
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -13,7 +13,7 @@ using System;
namespace OpenRA
{
public struct Hotkey : IEquatable<Hotkey>
public readonly struct Hotkey : IEquatable<Hotkey>
{
public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None);
public bool IsValid()
@@ -81,11 +81,10 @@ namespace OpenRA
public override bool Equals(object obj)
{
var o = obj as Hotkey?;
return o != null && o == this;
return obj is Hotkey o && (Hotkey?)o == this;
}
public override string ToString() { return "{0} {1}".F(Key, Modifiers.ToString("F")); }
public override string ToString() { return $"{Key} {Modifiers.ToString("F")}"; }
public string DisplayString()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -37,36 +37,24 @@ namespace OpenRA
public void OnKeyInput(KeyInput input)
{
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleKeyPress(input));
Sync.RunUnsynced(world, () => Ui.HandleKeyPress(input));
}
public void OnTextInput(string text)
{
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleTextInput(text));
Sync.RunUnsynced(world, () => Ui.HandleTextInput(text));
}
public void OnMouseInput(MouseInput input)
{
Sync.RunUnsynced(Game.Settings.Debug.SyncCheckUnsyncedCode, world, () => Ui.HandleInput(input));
Sync.RunUnsynced(world, () => Ui.HandleInput(input));
}
}
public class MouseButtonPreference
{
public MouseButton Action
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
}
}
public MouseButton Action => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Left : MouseButton.Right;
public MouseButton Cancel
{
get
{
return Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
}
}
public MouseButton Cancel => Game.Settings.Game.UseClassicMouseStyle ? MouseButton.Right : MouseButton.Left;
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -16,7 +16,6 @@ using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA
{
@@ -75,7 +74,7 @@ namespace OpenRA
}
catch (Exception e)
{
Log.Write("debug", "Load mod '{0}': {1}".F(path, e));
Log.Write("debug", $"Load mod '{path}': {e}");
}
package?.Dispose();
@@ -99,10 +98,10 @@ namespace OpenRA
return ret;
}
public Manifest this[string key] { get { return mods[key]; } }
public int Count { get { return mods.Count; } }
public ICollection<string> Keys { get { return mods.Keys; } }
public ICollection<Manifest> Values { get { return mods.Values; } }
public Manifest this[string key] => mods[key];
public IEnumerable<string> Keys => mods.Keys;
public IEnumerable<Manifest> Values => mods.Values;
public int Count => mods.Count;
public bool ContainsKey(string key) { return mods.ContainsKey(key); }
public IEnumerator<KeyValuePair<string, Manifest>> GetEnumerator() { return mods.GetEnumerator(); }
public bool TryGetValue(string key, out Manifest value) { return mods.TryGetValue(key, out value); }

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -12,10 +12,10 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using OpenRA.Support;
namespace OpenRA
{
@@ -24,11 +24,11 @@ namespace OpenRA
const int AuthKeySize = 2048;
public enum LinkState { Uninitialized, GeneratingKeys, Unlinked, CheckingLink, ConnectionFailed, Linked }
public LinkState State { get { return innerState; } }
public string Fingerprint { get { return innerFingerprint; } }
public string PublicKey { get { return innerPublicKey; } }
public LinkState State => innerState;
public string Fingerprint => innerFingerprint;
public string PublicKey => innerPublicKey;
public PlayerProfile ProfileData { get { return innerData; } }
public PlayerProfile ProfileData => innerData;
volatile LinkState innerState;
volatile PlayerProfile innerData;
@@ -76,17 +76,16 @@ namespace OpenRA
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
return;
Action<DownloadDataCompletedEventArgs> onQueryComplete = i =>
Task.Run(async () =>
{
try
{
if (i.Error != null)
{
innerState = LinkState.ConnectionFailed;
return;
}
var client = HttpClientFactory.Create();
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First();
var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var yaml = MiniYaml.FromString(result).First();
if (yaml.Key == "Player")
{
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
@@ -110,10 +109,9 @@ namespace OpenRA
{
onComplete?.Invoke();
}
};
});
innerState = LinkState.CheckingLink;
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
}
public void GenerateKeypair()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,7 +14,7 @@ using OpenRA.Primitives;
namespace OpenRA
{
public struct MPos : IEquatable<MPos>
public readonly struct MPos : IEquatable<MPos>
{
public readonly int U, V;
@@ -66,7 +66,7 @@ namespace OpenRA
/// <summary>
/// Projected map position
/// </summary>
public struct PPos : IEquatable<PPos>
public readonly struct PPos : IEquatable<PPos>
{
public readonly int U, V;

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
@@ -20,6 +21,17 @@ namespace OpenRA
{
public interface IGlobalModData { }
public sealed class TerrainFormat : IGlobalModData
{
public readonly string Type;
public readonly IReadOnlyDictionary<string, MiniYaml> Metadata;
public TerrainFormat(MiniYaml yaml)
{
Type = yaml.Value;
Metadata = new ReadOnlyDictionary<string, MiniYaml>(yaml.ToDictionary());
}
}
public sealed class SpriteSequenceFormat : IGlobalModData
{
public readonly string Type;
@@ -48,6 +60,7 @@ namespace OpenRA
public string Version;
public string Website;
public string WebIcon32;
public string WindowTitle;
public bool Hidden;
}
@@ -70,13 +83,14 @@ namespace OpenRA
public readonly string[] SoundFormats = { };
public readonly string[] SpriteFormats = { };
public readonly string[] PackageFormats = { };
public readonly string[] VideoFormats = { };
readonly string[] reservedModuleNames =
{
"Include", "Metadata", "Folders", "MapFolders", "Packages", "Rules",
"Sequences", "ModelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions", "Hotkeys",
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
"ServerTraits", "LoadScreen", "SupportsMapsFrom", "SoundFormats", "SpriteFormats", "VideoFormats",
"RequiresMods", "PackageFormats"
};
@@ -100,7 +114,7 @@ namespace OpenRA
var filename = nodes[i].Value.Value;
var contents = package.GetStream(filename);
if (contents == null)
throw new YamlException("{0}: File `{1}` not found.".F(nodes[i].Location, filename));
throw new YamlException($"{nodes[i].Location}: File `{filename}` not found.");
nodes.RemoveAt(i);
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
@@ -115,7 +129,7 @@ namespace OpenRA
MapFolders = YamlDictionary(yaml, "MapFolders");
if (yaml.TryGetValue("Packages", out var packages))
Packages = packages.ToDictionary(x => x.Value).AsReadOnly();
Packages = packages.ToDictionary(x => x.Value);
Rules = YamlList(yaml, "Rules");
Sequences = YamlList(yaml, "Sequences");
@@ -155,6 +169,9 @@ namespace OpenRA
if (yaml.ContainsKey("SpriteFormats"))
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
if (yaml.ContainsKey("VideoFormats"))
VideoFormats = FieldLoader.GetValue<string[]>("VideoFormats", yaml["VideoFormats"].Value);
}
public void LoadCustomData(ObjectCreator oc)
@@ -166,7 +183,7 @@ namespace OpenRA
var t = oc.FindType(kv.Key);
if (t == null || !typeof(IGlobalModData).IsAssignableFrom(t))
throw new InvalidDataException("`{0}` is not a valid mod manifest entry.".F(kv.Key));
throw new InvalidDataException($"`{kv.Key}` is not a valid mod manifest entry.");
IGlobalModData module;
var ctor = t.GetConstructor(new[] { typeof(MiniYaml) });
@@ -199,10 +216,9 @@ namespace OpenRA
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
{
if (!yaml.ContainsKey(key))
return new ReadOnlyDictionary<string, string>();
return new Dictionary<string, string>();
var inner = yaml[key].ToDictionary(my => my.Value);
return new ReadOnlyDictionary<string, string>(inner);
return yaml[key].ToDictionary(my => my.Value);
}
public bool Contains<T>() where T : IGlobalModData

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -37,7 +37,7 @@ namespace OpenRA
public class ActorInitializer : IActorInitializer
{
public readonly Actor Self;
public World World { get { return Self.World; } }
public World World => Self.World;
internal TypeDictionary Dict;
@@ -66,7 +66,7 @@ namespace OpenRA
{
var init = GetOrDefault<T>(info);
if (init == null)
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`");
return init;
}
@@ -150,16 +150,16 @@ namespace OpenRA
protected ValueActorInit(T value) { this.value = value; }
public virtual T Value { get { return value; } }
public virtual T Value => value;
public virtual void Initialize(MiniYaml yaml)
{
Initialize((T)FieldLoader.GetValue("value", typeof(T), yaml.Value));
Initialize((T)FieldLoader.GetValue(nameof(value), typeof(T), yaml.Value));
}
public virtual void Initialize(T value)
{
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
var field = GetType().GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, value);
}
@@ -246,14 +246,14 @@ namespace OpenRA
public void Initialize(MiniYaml yaml)
{
var field = GetType().GetField("InternalName", BindingFlags.Public | BindingFlags.Instance);
var field = GetType().GetField(nameof(InternalName), BindingFlags.Public | BindingFlags.Instance);
if (field != null)
field.SetValue(this, yaml.Value);
}
public void Initialize(Player player)
{
var field = GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
var field = GetType().GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
field.SetValue(this, player);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -27,7 +27,7 @@ namespace OpenRA
public string Type;
Lazy<TypeDictionary> initDict;
internal TypeDictionary InitDict { get { return initDict.Value; } }
internal TypeDictionary InitDict => initDict.Value;
public ActorReference(string type)
: this(type, new Dictionary<string, MiniYaml>()) { }
@@ -42,7 +42,7 @@ namespace OpenRA
{
var init = LoadInit(i.Key, i.Value);
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'");
dict.Add(init);
}
@@ -68,7 +68,7 @@ namespace OpenRA
var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator);
var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init");
if (type == null)
throw new InvalidDataException("Unknown initializer type '{0}Init'".F(initInstance[0]));
throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'");
var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
if (initInstance.Length > 1)
@@ -76,7 +76,7 @@ namespace OpenRA
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
if (loader == null)
throw new InvalidDataException("{0}Init does not define a yaml-assignable type.".F(initInstance[0]));
throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type.");
loader.Invoke(init, new[] { initYaml });
return init;
@@ -119,7 +119,7 @@ namespace OpenRA
public void Add(ActorInit init)
{
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'");
InitDict.Add(init);
}
@@ -162,7 +162,7 @@ namespace OpenRA
{
var init = GetOrDefault<T>(info);
if (init == null)
throw new InvalidOperationException("TypeDictionary does not contain instance of type `{0}`".F(typeof(T)));
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`");
return init;
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -52,7 +52,15 @@ namespace OpenRA
// Resolve an array index from cell coordinates
int Index(CPos cell)
{
return Index(cell.ToMPos(GridType));
// PERF: Inline CPos.ToMPos to avoid MPos allocation
var x = cell.X;
var y = cell.Y;
if (GridType == MapGridType.Rectangular)
return y * Size.Width + x;
var u = (x - y) / 2;
var v = x + y;
return v * Size.Width + u;
}
// Resolve an array index from map coordinates
@@ -64,10 +72,7 @@ namespace OpenRA
/// <summary>Gets or sets the <see cref="CellLayer"/> using cell coordinates</summary>
public T this[CPos cell]
{
get
{
return entries[Index(cell)];
}
get => entries[Index(cell)];
set
{
@@ -80,10 +85,7 @@ namespace OpenRA
/// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!)</summary>
public T this[MPos uv]
{
get
{
return entries[Index(uv)];
}
get => entries[Index(uv)];
set
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -38,7 +38,7 @@ namespace OpenRA
public virtual void CopyValuesFrom(CellLayerBase<T> anotherLayer)
{
if (Size != anotherLayer.Size || GridType != anotherLayer.GridType)
throw new ArgumentException("Layers must have a matching size and shape (grid type).", "anotherLayer");
throw new ArgumentException("Layers must have a matching size and shape (grid type).", nameof(anotherLayer));
Array.Copy(anotherLayer.entries, entries, entries.Length);
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -62,7 +62,7 @@ namespace OpenRA
public static CellRegion BoundingRegion(MapGridType shape, IEnumerable<CPos> cells)
{
if (cells == null || !cells.Any())
throw new ArgumentException("cells must not be null or empty.", "cells");
throw new ArgumentException("cells must not be null or empty.", nameof(cells));
var minU = int.MaxValue;
var minV = int.MaxValue;
@@ -97,10 +97,7 @@ namespace OpenRA
return uv.U >= mapTopLeft.U && uv.U <= mapBottomRight.U && uv.V >= mapTopLeft.V && uv.V <= mapBottomRight.V;
}
public MapCoordsRegion MapCoords
{
get { return new MapCoordsRegion(mapTopLeft, mapBottomRight); }
}
public MapCoordsRegion MapCoords => new MapCoordsRegion(mapTopLeft, mapBottomRight);
public CellRegionEnumerator GetEnumerator()
{
@@ -161,8 +158,8 @@ namespace OpenRA
v = r.mapTopLeft.V;
}
public CPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public CPos Current => current;
object IEnumerator.Current => Current;
public void Dispose() { }
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -17,13 +17,15 @@ using System.Reflection;
using System.Text;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA
{
struct BinaryDataHeader
readonly struct BinaryDataHeader
{
public readonly byte Format;
public readonly uint TilesOffset;
@@ -51,7 +53,7 @@ namespace OpenRA
ResourcesOffset = s.ReadUInt32();
}
else
throw new InvalidDataException("Unknown binary map format '{0}'".F(Format));
throw new InvalidDataException($"Unknown binary map format '{Format}'");
}
}
@@ -98,7 +100,7 @@ namespace OpenRA
if (node == null)
{
if (required)
throw new YamlException("Required field `{0}` not found in map.yaml".F(key));
throw new YamlException($"Required field `{key}` not found in map.yaml");
return;
}
@@ -163,6 +165,7 @@ namespace OpenRA
new MapField("Bounds"),
new MapField("Visibility"),
new MapField("Categories"),
new MapField("Translations", required: false, ignoreIfValue: ""),
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
new MapField("Players", "PlayerDefinitions"),
new MapField("Actors", "ActorDefinitions"),
@@ -173,7 +176,6 @@ namespace OpenRA
new MapField("Voices", "VoiceDefinitions", required: false),
new MapField("Music", "MusicDefinitions", required: false),
new MapField("Notifications", "NotificationDefinitions", required: false),
new MapField("Translations", "TranslationDefinitions", required: false)
};
// Format versions
@@ -189,6 +191,7 @@ namespace OpenRA
public Rectangle Bounds;
public MapVisibility Visibility = MapVisibility.Lobby;
public string[] Categories = { "Conquest" };
public string[] Translations;
public int2 MapSize { get; private set; }
@@ -204,7 +207,8 @@ namespace OpenRA
public readonly MiniYaml VoiceDefinitions;
public readonly MiniYaml MusicDefinitions;
public readonly MiniYaml NotificationDefinitions;
public readonly MiniYaml TranslationDefinitions;
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new Dictionary<CPos, TerrainTile>();
// Generated data
public readonly MapGrid Grid;
@@ -245,6 +249,8 @@ namespace OpenRA
CellLayer<List<MPos>> inverseCellProjection;
CellLayer<byte> projectedHeight;
internal Translation Translation;
public static string ComputeUID(IReadOnlyPackage package)
{
// UID is calculated by taking an SHA1 of the yaml and binary data
@@ -252,7 +258,7 @@ namespace OpenRA
var contents = package.Contents.ToList();
foreach (var required in requiredFiles)
if (!contents.Contains(required))
throw new FileNotFoundException("Required file {0} not present in this map".F(required));
throw new FileNotFoundException($"Required file {required} not present in this map");
var streams = new List<Stream>();
try
@@ -282,18 +288,17 @@ namespace OpenRA
/// Initializes a new map created by the editor or importer.
/// The map will not receive a valid UID until after it has been saved and reloaded.
/// </summary>
public Map(ModData modData, TileSet tileset, int width, int height)
public Map(ModData modData, ITerrainInfo terrainInfo, int width, int height)
{
this.modData = modData;
var size = new Size(width, height);
Grid = modData.Manifest.Get<MapGrid>();
var tileRef = new TerrainTile(tileset.Templates.First().Key, 0);
Title = "Name your map here";
Author = "Your name here";
MapSize = new int2(size);
Tileset = tileset.Id;
Tileset = terrainInfo.Id;
// Empty rules that can be added to by the importers.
// Will be dropped on save if nothing is added to it
@@ -310,7 +315,7 @@ namespace OpenRA
Tiles.CellEntryChanged += UpdateRamp;
}
Tiles.Clear(tileRef);
Tiles.Clear(terrainInfo.DefaultTerrainTile);
PostInit();
}
@@ -321,19 +326,16 @@ namespace OpenRA
Package = package;
if (!Package.Contains("map.yaml") || !Package.Contains("map.bin"))
throw new InvalidDataException("Not a valid map\n File: {0}".F(package.Name));
throw new InvalidDataException($"Not a valid map\n File: {package.Name}");
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name));
foreach (var field in YamlFields)
field.Deserialize(this, yaml.Nodes);
if (MapFormat != SupportedMapFormat)
throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, package.Name));
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
if (PlayerDefinitions.Count > 64)
throw new InvalidDataException("Maps must not define more than 64 players.\n File: {0}".F(package.Name));
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
Grid = modData.Manifest.Get<MapGrid>();
@@ -418,6 +420,8 @@ namespace OpenRA
Rules.Sequences.Preload();
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
var tl = new MPos(0, 0).ToCPos(this);
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
AllCells = new CellRegion(Grid.Type, tl, br);
@@ -430,12 +434,18 @@ namespace OpenRA
foreach (var uv in AllCells.MapCoords)
CustomTerrain[uv] = byte.MaxValue;
// Cache initial ramp state
var tileset = Rules.TileSet;
foreach (var uv in AllCells)
// Replace invalid tiles and cache ramp state
var terrainInfo = Rules.TerrainInfo;
foreach (var uv in AllCells.MapCoords)
{
var tile = tileset.GetTileInfo(Tiles[uv]);
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
if (!terrainInfo.TryGetTerrainInfo(Tiles[uv], out var info))
{
ReplacedInvalidTerrainTiles[uv.ToCPos(this)] = Tiles[uv];
Tiles[uv] = terrainInfo.DefaultTerrainTile;
info = terrainInfo.GetTerrainInfo(terrainInfo.DefaultTerrainTile);
}
Ramp[uv] = info.RampType;
}
AllEdgeCells = UpdateEdgeCells();
@@ -443,8 +453,7 @@ namespace OpenRA
void UpdateRamp(CPos cell)
{
var tile = Rules.TileSet.GetTileInfo(Tiles[cell]);
Ramp[cell] = tile != null ? tile.RampType : (byte)0;
Ramp[cell] = Rules.TerrainInfo.GetTerrainInfo(Tiles[cell]).RampType;
}
void InitializeCellProjection()
@@ -531,8 +540,7 @@ namespace OpenRA
// The original games treat the top of cliffs the same way as the bottom
// This information isn't stored in the map data, so query the offset from the tileset
var temp = inverse.MaxBy(uv => uv.V);
var terrain = Tiles[temp];
return (byte)(Height[temp] - Rules.TileSet.Templates[terrain.Type][terrain.Index].Height);
return (byte)(Height[temp] - Rules.TerrainInfo.GetTerrainInfo(Tiles[temp]).Height);
}
// Try the next cell down if this is a cliff face
@@ -667,35 +675,23 @@ namespace OpenRA
public (Color Left, Color Right) GetTerrainColorPair(MPos uv)
{
Color left, right;
var tileset = Rules.TileSet;
var type = tileset.GetTileInfo(Tiles[uv]);
if (type != null)
{
if (type.MinColor != type.MaxColor)
{
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
}
else
left = right = type.MinColor;
var terrainInfo = Rules.TerrainInfo;
var type = terrainInfo.GetTerrainInfo(Tiles[uv]);
var left = type.GetColor(Game.CosmeticRandom);
var right = type.GetColor(Game.CosmeticRandom);
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
{
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
}
if (terrainInfo.MinHeightColorBrightness != 1.0f || terrainInfo.MaxHeightColorBrightness != 1.0f)
{
var scale = float2.Lerp(terrainInfo.MinHeightColorBrightness, terrainInfo.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
}
else
left = right = Color.Black;
return (left, right);
}
public byte[] SavePreview()
{
var tileset = Rules.TileSet;
var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo<IMapPreviewSignatureInfo>());
var actors = ActorDefinitions.Where(a => actorTypes.Where(ai => ai.Name == a.Value.Value).Any());
var positions = new List<(MPos Position, Color Color)>();
@@ -710,95 +706,120 @@ namespace OpenRA
}
// ResourceLayer is on world actor, which isn't caught above, so an extra check for it.
var worldActorInfo = Rules.Actors["world"];
var worldActorInfo = Rules.Actors[SystemActors.World];
var worldimpsis = worldActorInfo.TraitInfos<IMapPreviewSignatureInfo>();
foreach (var worldimpsi in worldimpsis)
worldimpsi.PopulateMapPreviewSignatureCells(this, worldActorInfo, null, positions);
using (var stream = new MemoryStream())
var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric;
var top = int.MaxValue;
var bottom = int.MinValue;
if (Grid.MaximumTerrainHeight > 0)
{
var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric;
// Fudge the heightmap offset by adding as much extra as we need / can.
// This tries to correct for our incorrect assumption that MPos == PPos
var heightOffset = Math.Min(Grid.MaximumTerrainHeight, MapSize.Y - Bounds.Bottom);
var width = Bounds.Width;
var height = Bounds.Height + heightOffset;
var bitmapWidth = width;
if (isRectangularIsometric)
bitmapWidth = 2 * bitmapWidth - 1;
var stride = bitmapWidth * 4;
var pxStride = 4;
var minimapData = new byte[stride * height];
(Color Left, Color Right) terrainColor = default((Color, Color));
for (var y = 0; y < height; y++)
// The minimap is drawn in cell space, so we need to
// unproject the PPos bounds to find the MPos boundaries.
// This matches the calculation in RadarWidget that is used ingame
for (var x = Bounds.Left; x < Bounds.Right; x++)
{
for (var x = 0; x < width; x++)
var allTop = Unproject(new PPos(x, Bounds.Top));
var allBottom = Unproject(new PPos(x, Bounds.Bottom));
if (allTop.Any())
top = Math.Min(top, allTop.MinBy(uv => uv.V).V);
if (allBottom.Any())
bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V);
}
}
else
{
// If the mod uses flat maps, MPos == PPos and we can take the bounds rect directly
top = Bounds.Top;
bottom = Bounds.Bottom;
}
var width = Bounds.Width;
var height = bottom - top;
var bitmapWidth = width;
if (isRectangularIsometric)
bitmapWidth = 2 * bitmapWidth - 1;
var stride = bitmapWidth * 4;
var pxStride = 4;
var minimapData = new byte[stride * height];
(Color Left, Color Right) terrainColor = default;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var uv = new MPos(x + Bounds.Left, y + top);
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
if (actorColor.A == 0)
terrainColor = GetTerrainColorPair(uv);
if (isRectangularIsometric)
{
var uv = new MPos(x + Bounds.Left, y + Bounds.Top);
// FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty
var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color;
if (actorColor.A == 0)
terrainColor = GetTerrainColorPair(uv);
if (isRectangularIsometric)
// Odd rows are shifted right by 1px
var dx = uv.V & 1;
var xOffset = pxStride * (2 * x + dx);
if (x + dx > 0)
{
// Odd rows are shifted right by 1px
var dx = uv.V & 1;
var xOffset = pxStride * (2 * x + dx);
if (x + dx > 0)
{
var z = y * stride + xOffset - pxStride;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
if (xOffset < stride)
{
var z = y * stride + xOffset;
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
}
else
{
var z = y * stride + pxStride * x;
var z = y * stride + xOffset - pxStride;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
if (xOffset < stride)
{
var z = y * stride + xOffset;
var c = actorColor.A == 0 ? terrainColor.Right : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
}
else
{
var z = y * stride + pxStride * x;
var c = actorColor.A == 0 ? terrainColor.Left : actorColor;
minimapData[z++] = c.R;
minimapData[z++] = c.G;
minimapData[z++] = c.B;
minimapData[z] = c.A;
}
}
var png = new Png(minimapData, bitmapWidth, height);
return png.Save();
}
var png = new Png(minimapData, SpriteFrameType.Rgba32, bitmapWidth, height);
return png.Save();
}
public bool Contains(CPos cell)
{
// .ToMPos() returns the same result if the X and Y coordinates
// are switched. X < Y is invalid in the RectangularIsometric coordinate system,
// so we pre-filter these to avoid returning the wrong result
if (Grid.Type == MapGridType.RectangularIsometric && cell.X < cell.Y)
return false;
// If the mod uses flat & rectangular maps, ToMPos and Contains(MPos) create unnecessary cost.
// Just check if CPos is within map bounds.
if (Grid.MaximumTerrainHeight == 0 && Grid.Type == MapGridType.Rectangular)
return Bounds.Contains(cell.X, cell.Y);
if (Grid.Type == MapGridType.RectangularIsometric)
{
// .ToMPos() returns the same result if the X and Y coordinates
// are switched. X < Y is invalid in the RectangularIsometric coordinate system,
// so we pre-filter these to avoid returning the wrong result
if (cell.X < cell.Y)
return false;
}
else
{
// If the mod uses flat & rectangular maps, ToMPos and Contains(MPos) create unnecessary cost.
// Just check if CPos is within map bounds.
if (Grid.MaximumTerrainHeight == 0)
return Bounds.Contains(cell.X, cell.Y);
}
return Contains(cell.ToMPos(this));
}
@@ -896,6 +917,14 @@ namespace OpenRA
return new WDist(offset.Z);
}
public WRot TerrainOrientation(CPos cell)
{
if (!Ramp.Contains(cell))
return WRot.None;
return Grid.Ramps[Ramp[cell]].Orientation;
}
public WVec Offset(CVec delta, int dz)
{
if (Grid.Type == MapGridType.Rectangular)
@@ -907,15 +936,9 @@ namespace OpenRA
/// <summary>
/// The size of the map Height step in world units
/// </summary>
public WDist CellHeightStep
{
get
{
// RectangularIsometric defines 1024 units along the diagonal axis,
// giving a half-tile height step of sqrt(2) * 512
return new WDist(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512);
}
}
/// RectangularIsometric defines 1024 units along the diagonal axis,
/// giving a half-tile height step of sqrt(2) * 512
public WDist CellHeightStep => new WDist(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512);
public CPos CellContaining(WPos pos)
{
@@ -1025,33 +1048,6 @@ namespace OpenRA
ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray();
}
public void FixOpenAreas()
{
var r = new Random();
var tileset = Rules.TileSet;
for (var j = Bounds.Top; j < Bounds.Bottom; j++)
{
for (var i = Bounds.Left; i < Bounds.Right; i++)
{
var type = Tiles[new MPos(i, j)].Type;
var index = Tiles[new MPos(i, j)].Index;
if (!tileset.Templates.ContainsKey(type))
{
Console.WriteLine("Unknown Tile ID {0}".F(type));
continue;
}
var template = tileset.Templates[type];
if (!template.PickAny)
continue;
index = (byte)r.Next(0, template.TilesCount);
Tiles[new MPos(i, j)] = new TerrainTile(type, index);
}
}
}
public byte GetTerrainIndex(CPos cell)
{
const short InvalidCachedTerrainIndex = -1;
@@ -1075,8 +1071,7 @@ namespace OpenRA
if (terrainIndex == InvalidCachedTerrainIndex)
{
var custom = CustomTerrain[uv];
terrainIndex = cachedTerrainIndexes[uv] =
custom != byte.MaxValue ? custom : Rules.TileSet.GetTerrainIndex(Tiles[uv]);
terrainIndex = cachedTerrainIndexes[uv] = custom != byte.MaxValue ? custom : Rules.TerrainInfo.GetTerrainInfo(Tiles[uv]).TerrainType;
}
return (byte)terrainIndex;
@@ -1084,7 +1079,7 @@ namespace OpenRA
public TerrainTypeInfo GetTerrainInfo(CPos cell)
{
return Rules.TileSet[GetTerrainIndex(cell)];
return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(cell)];
}
public CPos Clamp(CPos cell)
@@ -1264,7 +1259,7 @@ namespace OpenRA
return AllEdgeCells.Random(rand);
}
public WDist DistanceToEdge(WPos pos, WVec dir)
public WDist DistanceToEdge(WPos pos, in WVec dir)
{
var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? ProjectedTopLeft.X : ProjectedBottomRight.X) - projectedPos.X) / dir.X;
@@ -1279,11 +1274,11 @@ namespace OpenRA
public IEnumerable<CPos> FindTilesInAnnulus(CPos center, int minRange, int maxRange, bool allowOutsideBounds = false)
{
if (maxRange < minRange)
throw new ArgumentOutOfRangeException("maxRange", "Maximum range is less than the minimum range.");
throw new ArgumentOutOfRangeException(nameof(maxRange), "Maximum range is less than the minimum range.");
if (maxRange >= Grid.TilesByDistance.Length)
throw new ArgumentOutOfRangeException("maxRange",
"The requested range ({0}) cannot exceed the value of MaximumTileSearchRange ({1})".F(maxRange, Grid.MaximumTileSearchRange));
throw new ArgumentOutOfRangeException(nameof(maxRange),
$"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})");
for (var i = minRange; i <= maxRange; i++)
{
@@ -1346,5 +1341,13 @@ namespace OpenRA
return false;
}
public string Translate(string key, IDictionary<string, object> args = null, string attribute = null)
{
if (Translation.GetFormattedMessage(key, args, attribute) == key)
return Ui.Translate(key, args, attribute);
return Translation.GetFormattedMessage(key, args, attribute);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,9 +14,8 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
@@ -27,7 +26,7 @@ namespace OpenRA
public sealed class MapCache : IEnumerable<MapPreview>, IDisposable
{
public static readonly MapPreview UnknownMap = new MapPreview(null, null, MapGridType.Rectangular, null);
public readonly IReadOnlyDictionary<IReadOnlyPackage, MapClassification> MapLocations;
public IReadOnlyDictionary<IReadOnlyPackage, MapClassification> MapLocations => mapLocations;
readonly Dictionary<IReadOnlyPackage, MapClassification> mapLocations = new Dictionary<IReadOnlyPackage, MapClassification>();
readonly Cache<string, MapPreview> previews;
@@ -47,8 +46,6 @@ namespace OpenRA
var gridType = Exts.Lazy(() => modData.Manifest.Get<MapGrid>().Type);
previews = new Cache<string, MapPreview>(uid => new MapPreview(modData, uid, gridType.Value, this));
sheetBuilder = new SheetBuilder(SheetType.BGRA);
MapLocations = new ReadOnlyDictionary<IReadOnlyPackage, MapClassification>(mapLocations);
}
public void LoadMaps()
@@ -149,8 +146,7 @@ namespace OpenRA
{
foreach (var map in package.Contents)
{
var mapPackage = package.OpenPackage(map, modData.ModFiles) as IReadWritePackage;
if (mapPackage != null)
if (package.OpenPackage(map, modData.ModFiles) is IReadWritePackage mapPackage)
yield return mapPackage;
}
}
@@ -163,55 +159,57 @@ namespace OpenRA
yield return new Map(modData, mapPackage);
}
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
{
var maps = uids.Distinct()
var queryUids = uids.Distinct()
.Where(uid => uid != null)
.Select(uid => previews[uid])
.Where(p => p.Status == MapStatus.Unavailable)
.ToDictionary(p => p.Uid, p => p);
.Select(p => p.Uid)
.ToList();
if (!maps.Any())
return;
foreach (var uid in queryUids)
previews[uid].UpdateRemoteSearch(MapStatus.Searching, null);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Searching, null);
var url = repositoryUrl + "hash/" + string.Join(",", maps.Keys) + "/yaml";
Action<DownloadDataCompletedEventArgs> onInfoComplete = i =>
Task.Run(async () =>
{
if (i.Error != null)
var client = HttpClientFactory.Create();
// Limit each query to 50 maps at a time to avoid request size limits
for (var i = 0; i < queryUids.Count; i += 50)
{
Log.Write("debug", "Remote map query failed with error: {0}", Download.FormatErrorMessage(i.Error));
Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
var batchUids = queryUids.Skip(i).Take(50).ToList();
var url = repositoryUrl + "hash/" + string.Join(",", batchUids) + "/yaml";
try
{
var httpResponseMessage = await client.GetAsync(url);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
queryFailed?.Invoke();
var yaml = MiniYaml.FromString(result);
foreach (var kv in yaml)
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
return;
foreach (var uid in batchUids)
{
var p = previews[uid];
if (p.Status != MapStatus.DownloadAvailable)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
}
}
catch (Exception e)
{
Log.Write("debug", "Remote map query failed with error: {0}", e);
Log.Write("debug", "URL was: {0}", url);
foreach (var uid in batchUids)
{
var p = previews[uid];
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
mapQueryFailed?.Invoke(p);
}
}
}
var data = Encoding.UTF8.GetString(i.Result);
try
{
var yaml = MiniYaml.FromString(data);
foreach (var kv in yaml)
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
foreach (var map in maps)
if (map.Value.Status != MapStatus.DownloadAvailable)
map.Value.UpdateRemoteSearch(MapStatus.Unavailable, null);
}
catch (Exception e)
{
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
Log.Write("debug", "Exception: {0}", e);
queryFailed?.Invoke();
}
};
new Download(url, _ => { }, onInfoComplete);
});
}
void LoadAsyncInternal()
@@ -334,10 +332,7 @@ namespace OpenRA
return initialUid;
}
public MapPreview this[string key]
{
get { return previews[key]; }
}
public MapPreview this[string key] => previews[key];
public IEnumerator<MapPreview> GetEnumerator()
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2021 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
@@ -14,7 +14,7 @@ using System.Collections.Generic;
namespace OpenRA
{
public struct MapCoordsRegion : IEnumerable<MPos>
public readonly struct MapCoordsRegion : IEnumerable<MPos>
{
public struct MapCoordsEnumerator : IEnumerator<MPos>
{
@@ -53,8 +53,8 @@ namespace OpenRA
current = new MPos(r.topLeft.U - 1, r.topLeft.V);
}
public MPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } }
public MPos Current => current;
object IEnumerator.Current => Current;
public void Dispose() { }
}
@@ -82,7 +82,7 @@ namespace OpenRA
return GetEnumerator();
}
public MPos TopLeft { get { return topLeft; } }
public MPos BottomRight { get { return bottomRight; } }
public MPos TopLeft => topLeft;
public MPos BottomRight => bottomRight;
}
}

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