Compare commits

...

330 Commits

Author SHA1 Message Date
Paul Chote
1a8e84e4cc Use ULFO format for non-compat disk images (requires macOS 10.11+). 2020-12-23 23:40:52 +00:00
Paul Chote
668ab78b1b Trim unused assemblies to reduce packaged size further. 2020-12-23 23:00:17 +00:00
Paul Chote
c754d6a892 Replace duplicate runtime files with hardlinks to reduce dmg size. 2020-12-23 20:05:48 +00:00
Paul Chote
e9108479b5 Switch macOS packages to .NET 5. 2020-12-23 20:05:48 +00:00
teinarss
857da21b0f Add support for dotnet core for Windows 2020-12-23 18:41:27 +01: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
Matthias Mailänder
09db4a0e25 Enable debug mode by default. 2020-12-15 23:06:05 +01:00
Trevor Nichols
a85da9d86c Implement loader for MegV3 file format 2020-12-15 11:11:05 +01:00
Paul Chote
920d00bbae Use nearest-neighbour scaling in --debug-chrome-regions utility command. 2020-12-14 22:38:25 +01:00
Ivaylo Draganov
62475279ee Remove unneeded chrome yaml declarations 2020-12-14 20:52:38 +01:00
Matthias Mailänder
d8e979d283 Remove dead code. 2020-12-14 20:38:50 +01:00
Paul Chote
299b8880dd Fix api output directory. 2020-12-14 18:33:35 +01:00
Paul Chote
61027e4067 Fix docs.openra.net repository reference. 2020-12-14 18:33:35 +01:00
Paul Chote
a7249c10dc Fix docs.openra.net documentation workflow. 2020-12-13 15:35:31 +01:00
Paul Chote
611d12ac78 Migrate CI and packaging from Travis CI to GitHub Actions. 2020-12-12 20:17:29 +00:00
abcdefg30
ef9f26a60d Fix MissionObjectives not properly ending the game 2020-12-12 14:59:49 +01:00
abcdefg30
aeaffc0a8e Properly restrict the spectator view in mission maps 2020-12-12 14:59:49 +01:00
Paul Chote
e3084e230e Switch mirror for nsis3 package. 2020-12-12 14:54:50 +01:00
Paul Chote
4c01c772f8 Fix incorrect animation playing when moving infantry stop to attack. 2020-12-12 14:40:13 +01:00
Paul Chote
ed94f7680a Revert "Fix WithInfantryBody wrongly overwriting attack animations"
This reverts commit 1a63cc4a41.
2020-12-12 14:40:13 +01:00
yuantse
408d66cdaf Add Allies 09a 2020-12-12 13:18:08 +01:00
Smittytron
c4a0f2f169 Add Counterstrike mission Sarin Gas 3: Controlled Burn 2020-12-12 13:15:18 +01:00
Paul Chote
26b28d26da Prevent Civilians from wandering onto Tiberium. 2020-12-12 12:54:46 +01:00
Paul Chote
8ded6dafd4 Add AvoidTerrainTypes to ScaredyCat. 2020-12-12 12:54:46 +01:00
Paul Chote
57a94ad667 Add AvoidTerrainTypes to Wanders. 2020-12-12 12:54:46 +01:00
Paul Chote
53933a4d8f Fix restart black screen race condition. 2020-12-11 22:57:57 +01:00
Paul Chote
6606d7dd93 Add DisplayFaction details to the replay metadata. 2020-12-11 18:05:44 +01:00
Paul Chote
7a256dcafa Fix rally point target line exit display. 2020-12-11 17:25:30 +01:00
abcdefg30
7899c52b6d Add an update rule 2020-12-11 17:13:02 +01:00
abcdefg30
919c670502 Update the rules of the default mods 2020-12-11 17:13:02 +01:00
abcdefg30
aac3174efc Rename Stances to Relationships in the yaml api 2020-12-11 17:13:02 +01:00
Paul Chote
a8d3d5c79a Fix Neutral crushing checks. 2020-12-08 20:17:11 +01:00
Paul Chote
7c852d90fb Ignore aircraft when searching for enemy targets. 2020-12-07 23:39:22 +01:00
Paul Chote
269ce9c406 Exclude carryalls from AI squads. 2020-12-07 23:39:22 +01:00
Paul Chote
53d98ec255 Abort squad states that are not able to move. 2020-12-07 23:39:22 +01:00
Paul Chote
7a7cd21578 Fix TD SAM Site facings being reset when damaged while closed. 2020-12-07 01:44:04 +01:00
abcdefg30
2b363e4e1c Fix WithInfantryBody wrongly overwriting attack animations 2020-12-07 00:29:16 +01:00
Paul Chote
b1560ae69c Overhaul packaging scripts and helpers:
- Shared helpers extracted to functions.sh for use by upstream packaging,
  Mod SDK, and downstream packaging (via the Makefile targets).
- Assembly management separated from data and combined between engine
  and mods to prepare for pending .NET core requirements.
- Streamline Makefile targets.
- Clean up a lot of old technical debt.
2020-12-06 17:16:47 +01:00
Paul Chote
f3ebe07540 Fix WithDeliveryAnimation overriding sell animation. 2020-12-06 09:49:40 +01:00
Orb
6cac587753 ST Death Explosion Change Commit 2020-12-06 09:47:22 +01:00
Paul Chote
09e6cc4add Enable dark mode support on macOS. 2020-12-05 17:09:32 +00:00
Paul Chote
5a2f91be1c Fix building bibs not blocking placement. 2020-12-05 17:06:04 +00:00
Paul Chote
25071b813e Prevent concrete from spawning underneath already placed building bibs.
This is the equivalent to reverting 83b61ab.
2020-12-05 17:06:04 +00:00
Paul Chote
d61bd675c4 Revert "Remove BuildingInfluence from ResourceLayer."
This reverts commit 1634f3b70e.
2020-12-05 17:06:04 +00:00
Paul Chote
582e2774ac Revert "Remove BuildingInfluence from building adjacency check."
This reverts commit 33a1ca5965.
2020-12-05 17:06:04 +00:00
Paul Chote
5a52ce5330 Revert "Remove BuildingInfluence trait."
This reverts commit 34bf143328.
2020-12-05 17:06:04 +00:00
abcdefg30
31c9b4fc80 Revert renderable buffer sorting improvements 2020-12-05 17:03:03 +00:00
abcdefg30
4953ce314b Make Attack turn during its own tick 2020-12-05 16:41:30 +00:00
Paul Chote
86519bfda5 Expose Player.HomeLocation to the Lua API. 2020-12-05 14:14:56 +01:00
teinarss
2c9a36b9a3 Update float3 to readonly and use in modifier for parameters. 2020-12-02 20:37:50 +01:00
Paul Chote
d3847d49ed Fix windows package filenames. 2020-12-02 19:41:24 +01:00
Paul Chote
57f40a0b20 Remove obsolete variable definition. 2020-12-02 19:41:24 +01:00
Paul Chote
fa05f4e4b0 Generate Unicode-aware Windows installers. 2020-12-02 19:41:24 +01:00
Paul Chote
754f41ecd1 Remove redundant python check from Linux package creation. 2020-12-02 19:41:24 +01:00
Paul Chote
4b6e1c2198 Add a utility wrapper script for non-Windows. 2020-12-02 16:57:51 +01:00
teinarss
e6db2c98d0 Make WRot readonly and use in parameter modifier 2020-12-01 22:54:39 +00:00
Paul Chote
942f1e2d9e Fix infantry ignoring the default subcell when produced.
They were being created with the correct subcell, but was rejecting it
as invalid because it was already occupied (by the unit itself).
2020-12-01 22:39:11 +01:00
Paul Chote
da5c94858f Evaluate exit distance from the exit cell instead of the spawn position. 2020-12-01 22:35:43 +01:00
Paul Chote
77ffc20a5f Convert Attack*.FacingTolerance to WAngle. 2020-11-30 16:39:48 +01:00
abcdefg30
d75fed3a00 Replace chan with c10 in actors spawned from selling RA tech buildings 2020-11-30 01:07:36 +01:00
Matthias Mailänder
2aba054fe8 Move it to the right folder. 2020-11-29 18:27:59 +01:00
Paul Chote
3b1f4ba07f Fix production exit desync. 2020-11-28 17:49:36 +01:00
Paul Chote
d35768e0f9 Rework Windows launchers:
- Use SDL2 message boxes instead of Winforms.
- Use a proper project instead of compiling a single file.
- Use assembly attributes instead of modifying strings in the source code.
- Remove generic OpenRA.exe launcher.
- Replace MakeLAA.exe with a python script.
2020-11-27 20:32:02 +01:00
Paul Chote
07a10069db Introduce new OpenRA.exe entrypoint and change OpenRA.Game to a library. 2020-11-27 20:32:02 +01:00
Paul Chote
20fe59e844 Output compiled binaries to ./bin. 2020-11-24 17:53:48 +01:00
Paul Chote
6ad5b9ebc4 Allow the game root directory to be moved away from the binaries. 2020-11-24 17:53:48 +01:00
Paul Chote
dd0b08d54a Replace ^ and . in platform paths with ^SupportDir and ^EngineDir. 2020-11-24 17:53:48 +01:00
Paul Chote
1cc1f93fb0 Rename Platform.GameDir to Platform.EngineDir. 2020-11-24 17:53:48 +01:00
Paul Chote
72f1f06ebc Remove Platform.UnresolvePath. 2020-11-22 16:42:46 +01:00
Paul Chote
151cea96e9 Remove Platform.IsPathRelativeToSupportDirectory. 2020-11-22 16:42:46 +01:00
Paul Chote
888915b53b Use Platform.GameDir explicitly in hardcoded paths. 2020-11-22 16:42:46 +01:00
Paul Chote
de7a84e8ed Don't use Platform.ResolvePath when constructing hardcoded paths. 2020-11-22 16:42:46 +01:00
Paul Chote
1dd5b113c7 Fix asset browser and map editor paths for SDK-based mods. 2020-11-22 16:42:46 +01:00
Paul Chote
e9ad38667e Expose TransformsInto* targetline colors to yaml. 2020-11-21 14:52:54 +01:00
abcdefg30
05e8026713 Remove or use unused variables in TransformsIntoAircraft 2020-11-20 23:12:44 +01:00
abcdefg30
5b75649888 Fix TransformsIntoAircraft transforming on all orders 2020-11-20 23:12:44 +01:00
Matthias Mailänder
c22392eb1e Enable itch app auto updates on Windows. 2020-11-17 19:46:28 +01:00
dnqbob
7c2a51df38 Make OrderGenerator flexible on mouse input for modder 2020-11-17 19:18:04 +01:00
reaperrr
05cb9b1fbf Fix dock sequence dock/undock notifications
- move Refinery dock/undock notifications to OnStateDock/-Undock
and only call Undock if Dock was also called.
- Add INotifyHarvesterAction support to VoxelHarvesterDockSequence.
2020-11-15 16:56:28 +01:00
reaperrr
6bba35c330 Fix TS harvesters getting stuck in unload graphics
when refinery is destroyed while unloading.
2020-11-15 16:56:28 +01:00
reaperrr
90d9ee1f5c Fix harv dock sequence issuing drag despite cancellation 2020-11-15 16:56:28 +01:00
reaperrr
385f01247b Fix harvs dragging to refinery even if it died while turning 2020-11-15 16:56:28 +01:00
reaperrr
996029ee38 Fix undock anim playing even if docking was cancelled
...before dock anim could run.

The undock animation would play even if the dock anim
hadn't run (meaning the sequence cancelled before the docking
completed, for example due to refinery death).
2020-11-15 16:56:28 +01:00
reaperrr
87929b3d91 Fix dock animation continuing after refinery died
OnStateDock would always be triggered, even if the
refinery was killed before the harvester would finish
docking.
2020-11-15 16:56:28 +01:00
abcdefg30
e7e50cc101 Remove WorldUtils.AreMutualAllies 2020-11-15 16:37:51 +01:00
abcdefg30
d6c0926856 Remove LaysTerrain 2020-11-15 16:33:32 +01:00
abcdefg30
99ec119ffd Move LaysTerrain functionality to D2kBuilding 2020-11-15 16:33:32 +01:00
abcdefg30
6b2920cc91 Introduce D2kBuilding 2020-11-15 16:33:32 +01:00
abcdefg30
05f933f007 Add an update rule for the DamagedByTerrain refactor 2020-11-15 16:33:32 +01:00
abcdefg30
80aeb5ada6 Refactor DamagedByTerrain 2020-11-15 16:33:32 +01:00
abcdefg30
83ea65d4ff Require IOccupySpace for DamagedByTerrain 2020-11-15 16:33:32 +01:00
Paul Chote
46b3b01b89 Fix WithVoxelBarrel orientation calculations. 2020-11-15 13:51:12 +00:00
Orb
973f679939 TD Veterancy Adjustment Commit 2020-11-15 09:46:54 +01:00
abcdefg30
6de1b7b915 Always mark defeated/victorious players as spectating 2020-11-15 09:45:16 +01:00
Orb
e7ce739fec TD Fix Repair Times Commit 2020-11-15 09:42:21 +01:00
abcdefg30
bf14a4ce80 Remove an unused variable from LoadComplete 2020-11-15 09:38:02 +01:00
abcdefg30
8840a690c6 Fix maps without spawn points no longer working 2020-11-15 09:38:02 +01:00
abcdefg30
77e85e7c58 Change DisabledSpawnPoints from List to HashSet 2020-11-15 09:38:02 +01:00
Pavol Molnar
6943cf5ad5 fixed tooltip, issue 18301 2020-11-15 09:36:56 +01:00
Vapre
17996dfdfc Shroud, access ProjectedCellLayer by array index. Shroud.touchedCount to avoid Tick updates.
Shroud, access ProjectedCellLayer by array index over PPos index.

Performance improvement. Avoid the multiple PPos to array index
conversions in the same method call by calculating the cell
layer index once.

Background:

`Shroud.Tick` and `ProjectedCellLayer.Index(PPos puv)` shows up
in profile reports as one of the most expensive methods
(9% of CPU time).

In `Shroud.Tick` calls `ProjectedCellLayer.Index(PPos puv)` multiple
times for the same or different cell layers of the same dimension.

Improvement:

Benchmark results show an 0.5ms mean improvement in tick
time and 0.3 improvement in render time -
on a replay map of 1.12 min of play at max speed.

Render time:
       render222052(bleed)  render221934(this commit)
count   8144.000000   8144.000000
mean      11.410075     11.470100
std        5.004876      4.731463
min        3.450700      3.638400
25%        7.409100      7.015900
50%       12.410600     12.435900
75%       13.998100     14.242900
max      149.036200    149.656500

Tick time:
       tick_time222043(bleed)  tick_time221923(this commit)
count      2366.000000      2366.000000
mean          4.762923         4.275833
std           3.240976         3.206362
min           0.263900         1.653600
25%           4.145375         3.668600
50%           4.779350         4.240050
75%           5.232575         4.611775
max          85.751800        87.387100

Shroud.touchedCount to avoid Tick updates if no cells touched.

Avoids iterating over all map cells of the `touched` cell layer.

Tick time improvement of 40%+ - during at least the first two
minutes of gameplay.

During the first minutes of a game - out of every 1000 ticks
only 10-100 result in the Shroud - of any player - to be touched.

For certains player types (Neutral, Creep) less Shroud updates
are expected throughout a complete game.

Throughout a complete game human/AI players can also have no
Shroud touches during certain Ticks.
2020-11-14 18:33:49 +00:00
reaperrr
7f32776701 Fix some RA effect warheads ignoring water actors 2020-11-14 16:06:21 +01:00
reaperrr
fce109a46d Fix piff anims of Sniper and SilencedPPK weapons 2020-11-14 16:06:21 +01:00
reaperrr
748292324a Update RA/TD light guns cosmetic effects
Use single piff instead of piffs per impact on
M60s and M1, but in return add visual inaccuracy.

Gave minigunner MG two additional delayed effect warheads
to match sound and fire animation.
2020-11-14 16:06:21 +01:00
reaperrr
efe0de2ecb Move around TD Pistol yaml
Feels more organized if located below the inherited
^LightMG, like all other inheritors.
2020-11-14 16:06:21 +01:00
reaperrr
cac9940736 Support 'inaccuracy' on effect warhead
Purely cosmetic, allows to 'fake' slight inaccuracy without
affecting game balance on weapons like light machine guns.
2020-11-14 16:06:21 +01:00
abcdefg30
559b143265 Use FlyForward for strafing 2020-11-14 12:50:02 +00:00
abcdefg30
a66305e282 Fix the constructor order of FlyForward 2020-11-14 12:50:02 +00:00
abcdefg30
b3aa61ee8e Make fields readonly where possible 2020-11-14 12:50:02 +00:00
abcdefg30
80436a3195 Add a distance based constructor to FlyForward 2020-11-14 12:50:02 +00:00
abcdefg30
5127a6813d Rename FlyTimed to FlyForward 2020-11-14 12:50:02 +00:00
tovl
84eb3c54ef Expose targetline colors to yaml.
All targetlines can now be set to a custom color in yaml or set to be invisible.
All automated behaviours including scripted activities now have no visible target lines.
2020-11-14 12:04:19 +00:00
abcdefg30
ea3c7a3c34 Remove the Stances dictionary 2020-11-14 11:04:41 +00:00
abcdefg30
718cf37146 Misc code improvements 2020-11-14 11:04:41 +00:00
abcdefg30
10f645bf77 Replace usage of the Stances dict by a method call 2020-11-14 11:04:41 +00:00
abcdefg30
eda9966d27 Rename Stance to PlayerRelationship 2020-11-14 11:04:41 +00:00
abc013
672172d1f1 Add missing PlayNotification calls to SelectTarget. 2020-11-12 19:52:06 +00:00
abcdefg30
a366e37014 Fix angles in the Descriptions of TDGunboat 2020-11-10 20:32:08 +01:00
Trevor Nichols
d66e0bb22e Right click lobby spawns to disable or remove players. 2020-11-07 19:33:28 +01:00
teinarss
13581c030d Use in parameter for Target 2020-11-06 22:02:24 +01:00
teinarss
13a8b6bda2 Make Target readonly 2020-11-06 22:02:24 +01:00
Paul Chote
3aaaa95618 Apply range modifiers to Missile RangeLimit. 2020-11-05 19:15:13 +01:00
abcdefg30
e3929d7ded Fix reservable crashing when unreserving and attack moving 2020-11-05 13:22:22 +01:00
teinarss
2c62f747d9 Fix style in issue template config 2020-11-04 23:26:04 +01:00
teinarss
e08818cca1 Update issue template with links to FAQ 2020-11-04 23:26:04 +01:00
Matthias Mailänder
b4670345dd Don't crash when the bullet bounces outside of the map. 2020-11-04 10:59:55 +01:00
abcdefg30
0320dcdef9 Revise the RA map pool 2020-11-02 21:32:25 +00:00
Mustafa Alperen Seki
8b12cce250 Disable neutral Oil Derricks in TD and RA. 2020-11-02 21:27:37 +00:00
Mustafa Alperen Seki
6bb7ab0f97 Add GrantConditionOnCombatantOwner. 2020-11-02 21:27:37 +00:00
Matthias Mailänder
b32346d65f Add a range marker to the nuke targeter. 2020-11-02 10:13:29 +00:00
darkademic
8f9c212921 Make AI randomly select from map squares with above average attractiveness when using support powers. 2020-11-01 21:20:00 +00:00
Orb
df39d4fcc4 TD Map Pool 2020 Commit 2020-11-01 19:21:23 +00:00
RoosterDragon
4daa5193b6 Use tie breaks for sort order in WorldRenderer.GenerateRenderables 2020-11-01 19:00:20 +00:00
abc013
13596c1474 Add offset to Explodes. 2020-10-31 17:48:21 +01:00
abc013
bb71b59e18 Improve explosion determination. 2020-10-31 14:55:13 +01:00
abc013
f2797c711c Add InvalidBounceTerrain parameter to Projectile. 2020-10-31 14:55:13 +01:00
Paul Chote
6b6b1e56e6 Resolve random players and spawn points in server replays. 2020-10-31 14:31:44 +01:00
abcdefg30
7b75a78e38 Fix free-of-charge repairing still costing credits 2020-10-30 15:12:47 +01:00
Taryn Hill
2cea4b26e8 vscode debugging: build before launching 2020-10-30 13:58:05 +01:00
dnqbob
48f4a98c6a Fix AI air squad misbehaving in TS maps
Change Cpos to Mpos by Pchote
2020-10-30 09:20:01 +01:00
Paul Chote
9ddc9073c2 Revert "Determine stance for spectators based on shroud selection"
This reverts commit e459fde031.
2020-10-30 08:36:23 +01:00
abcdefg30
774f2e0852 Fix Production.cs not properly using INotifyCreated 2020-10-29 21:42:03 +00:00
abcdefg30
f5a963ac47 Production now uses nearest exit to rally point. 2020-10-29 21:42:03 +00:00
Vapre
38f1f1e5c2 Replace TraitContainer.AllEnumerator with ApplyToAll.
As proposed in the past in #13577.

Replace TraitContainer.All() that uses the custom AllEnumerator with
TraitContainer.ApplyToAllX() that takes an action as argument.

The AllEnumerator.Current function show up in profiling reports since it is
used each tick multiple times for multiple traits. The function is 'heavy'
because it creates TraitPair<T>'s temporary objects for each actor
trait combination.

In the past about 20k ITick trait pairs were present during an average
multi player game.

Using an Apply function that takes an action avoid the need to create
these temporary objects.

To be able to still use 'DoTimed' somewhat efficiently the measurement
was moved to inside the trait container method.

Results in a 25% performance improvement in accessing all traits of
a certain type.

Apply function could be used for other TraitContainer functions as well
for further improvements.

Test result for calling on a dummy trait on 20k actors a 1000 times:
  1315 ms traitcontainer.AllEnumerator (current)
   989 ms traitcontainer.Apply (this commit)
2020-10-29 21:21:12 +00:00
Mustafa Alperen Seki
4cdbf74256 Add Align: to SupportPowerTimerWidget. 2020-10-29 19:46:51 +01:00
Andrew Odintsov
bd0738c5c4 Determine stance for spectators based on shroud selection 2020-10-26 23:39:42 +00:00
Paul Chote
72c82cb080 Remove BuildingInfluence trait. 2020-10-26 16:51:12 +01:00
Paul Chote
b9dd59cd63 Remove BuildingInfluence from building adjacency check. 2020-10-26 16:51:12 +01:00
Paul Chote
79019b06ca Remove BuildingInfluence from Plug placement. 2020-10-26 16:51:12 +01:00
Paul Chote
90b25be1b6 Remove BuildingInfluence from ResourceLayer. 2020-10-26 16:51:12 +01:00
Paul Chote
e4faa6b0f0 Remove BuildingInfluence from Aircraft. 2020-10-26 16:51:12 +01:00
Paul Chote
1a3dfdc67f Remove BuildingInfluence from LaysTerrain. 2020-10-26 16:51:12 +01:00
Paul Chote
8d2156fb30 Remove BuildingInfluence from BuildableTerrainLayer. 2020-10-26 16:51:12 +01:00
Paul Chote
5e032edd28 Remove redundant BuildingInfluence checks.
Buildings are already excluded by the ActorMap checks.
2020-10-26 16:51:12 +01:00
Vapre
63d597e4ad ShroudRenderer UpdateShroud only when at least one cell is dirty.
UpdateShroud shows up in profile reports as one of the most
active methods (2.3% CPU time, main mono thread).

This commit introduces `anyCellDirty` to indicate that at lease one
of the cells was marked as dirty.

Avoiding the need to traverse all projected cells of the map
to find any dirty cell.

This reduces the number of shroud updates by at least 50% during a
test game. I assume this is related to renders occurring more
often than logic ticks(?).
2020-10-25 23:01:19 +01:00
Pavel Penev
4135045ca4 Added valid TerrainTypes to the Minelayer trait
Also added a configurable deploy cursor and fixed Minelayer target cell validation checks, which should make for a much better experiencing when dragging over an area with blocking terrain, shroud, fog, etc.
2020-10-24 12:20:15 +02:00
abcdefg30
538623c835 Add a lint check for player counts 2020-10-24 10:09:22 +02:00
abcdefg30
3674583053 Throw an exception at load when a map has more than 64 players 2020-10-24 10:09:22 +02:00
Paul Chote
daa8c74c37 Improve Replaceable logic:
- Remove BuildingInfluence checks
- Support multiple Replaceable/Replacement traits on the same actors
- Fix description typos
2020-10-22 22:15:43 +02:00
Mustafa Alperen Seki
8aeec24c9b Add DamageTypes to Demolition 2020-10-21 18:41:16 +02:00
RoosterDragon
54c4a05062 Classes derived from Stream override ReadByte.
The Stream.ReadByte method is implemented by allocating a 1 byte buffer and calling into Read(byte[], int, int). Override ReadByte in derived classes to avoid needing to allocate this small temp buffer.

Also, fix some bugs in the stream implementations. Remove Write capability from MergedStream that didn't make sense. Add guards into SegmentStream to ensure reads and writes belonged to the segment - otherwise a reader or writer could access regions of the base stream that were outside the intended segment.
2020-10-20 22:53:32 +02:00
RoosterDragon
466de89e17 PackageEntry.HashFilename avoids stream reads .
Operate directly on the array to avoid overhead of stream reads.
2020-10-20 22:53:32 +02:00
RoosterDragon
6eaf51d450 CursorManager avoids use of BitConverter.
Avoid allocating a small temp array via BitConverter.GetBytes, and instead use bitwise ops to isolate the components of the color.
2020-10-20 22:53:32 +02:00
RoosterDragon
7c8dc5d5f4 XccGlobalDatabase allocation improvements
- Use the count to size the capacity of the list.
- Use a char array as a buffer, so will can build each string directly rather than needing a ToArray call first.
2020-10-20 22:53:32 +02:00
RoosterDragon
4f34d3edb3 Improvements in MiniYaml allocation
- Clone method will use the node count to create the correct capacity.
- ResolveInherits will use the node count as the suggested initial capacity.
- FromStream will now stream lines, rather than reading the whole file into memory and then splitting into lines.
2020-10-20 22:53:32 +02:00
RoosterDragon
0efdbc762d Avoid ReadBytes calls in some StreamExts methods.
Read individual bytes to avoid allocating small temp arrays.
2020-10-20 22:53:32 +02:00
Matthias Mailänder
5e42c03afc Replace more \n characters with write lines. 2020-10-19 22:37:01 +01:00
Matthias Mailänder
58726160a9 Reduce the indention level to show the table of contents. 2020-10-19 22:37:01 +01:00
Matthias Mailänder
8cd9215756 Add an additional new line before the unordered list. 2020-10-19 22:37:01 +01:00
Orb
0e33640b14 Code Polish TD Commit 2020-10-19 13:33:25 +02:00
reaperrr
a1c8cbab0b Enable recoil anim on RA/TD Gun Turrets 2020-10-18 20:39:22 +02:00
reaperrr
50b484df56 Fix RA/TD defense turrets facing issues
By using WithSpriteTurret instead of
WithEmbeddedTurretSpriteBody.
2020-10-18 20:39:22 +02:00
reaperrr
99facd2797 Fix default facing of RA/TD Gun Turrets
Compare with make anim and original, 224 was
one facing too far.
2020-10-18 20:39:22 +02:00
Paul Chote
f79e1cacf0 Fix and document FMV scanline rendering. 2020-10-18 20:15:11 +02:00
Paul Chote
82069db724 Fix FMV aspect ratio. 2020-10-18 20:15:11 +02:00
Paul Chote
5a7dc385a3 Remove obsolete LocomotorInfo caching. 2020-10-18 18:19:56 +02:00
darkademic
49e7a33db0 Fixed WithRangeCircle when Visibility is set to Always. 2020-10-18 18:06:22 +02:00
Paul Chote
90b26681eb Remove trait queries from Actor ctor. 2020-10-18 18:00:17 +02:00
Matthias Mailänder
14fc0254c6 Make all range circles fully configurable. 2020-10-18 15:08:17 +01:00
Matthias Mailänder
214aa64ce3 Fix Analyzer warning: V3128 field is used before initialized 2020-10-18 14:53:35 +01:00
abcdefg30
a7bb217887 Make the calculation of DesiredLocalFacing more readable 2020-10-18 14:48:05 +01:00
abcdefg30
10cb8090ec Remove the second arrow on the preview of gdi08a 2020-10-18 14:33:52 +01:00
abcdefg30
1fdecb4e9d Fix briefing text and video of nod04a 2020-10-18 14:33:52 +01:00
abcdefg30
575596c9ad Fix map previews and order in TD 2020-10-18 14:33:52 +01:00
RoosterDragon
e11c8436bd Misc changes to reduce allocation:
- Avoid creating new strings in SpriteRenderer.Flush.
- ProductionQueue.CancelUnbuildableItems can exit early if the queue is empty. It can also use a set of names for quicker lookups.
- OpenGL.CheckGLError avoids a Enum.HasFlag call.
2020-10-17 23:48:48 +02:00
RoosterDragon
c23efea402 Comments to justify GC.Collect calls, compact LOH during load. 2020-10-17 23:48:48 +02:00
RoosterDragon
8d3cec5bea When a render method has nothing to render, eagerly return.
By eagerly returning an empty enumerable in these cases, this avoids allocating an enumerable for the whole render method if nothing will be drawn.
2020-10-17 23:48:48 +02:00
RoosterDragon
87389d3051 Reuse a list in FrozenActor.RefreshState 2020-10-17 23:48:48 +02:00
RoosterDragon
71e3ca4493 Sort renderables in-place in WorldRenderer.GenerateRenderables 2020-10-17 23:48:48 +02:00
RoosterDragon
2adee1e374 Use HasMovementType to avoid Enum.HasFlag allocations. 2020-10-17 23:48:48 +02:00
RoosterDragon
094ccf76b0 Prefer Min/MaxBy overloads to OrderBy().First() patterns 2020-10-17 23:48:48 +02:00
RoosterDragon
bb116034c7 Avoid or reduce LINQ allocations required in various areas. 2020-10-17 23:48:48 +02:00
Paul Chote
da53d5b776 Fix a divide by zero crash in Move. 2020-10-17 22:31:35 +02:00
abcdefg30
75fe0e524f Fix units not attack moving to waypoints after resupply/takeoff 2020-10-17 19:48:54 +01:00
Matthias Mailänder
0ded0355c1 Allow mod code to set rally point indicators. 2020-10-16 18:14:33 +01:00
Matthias Mailänder
4da14cee0a Disable UPnP on local games. 2020-10-16 18:14:33 +01:00
Orb
90c8e112e2 Stealth C17 Checkbox 2020-10-16 18:09:43 +01:00
Orb
d49bf81d95 TD Balance 2020 3 Commit 2020-10-16 17:52:41 +01:00
abcdefg30
abbe16b6ec Remove default parameters 2020-10-15 10:53:06 +02:00
abcdefg30
274bf06eae Fix evacuated civilians not staying at the GDI base 2020-10-15 10:53:06 +02:00
abcdefg30
dcf7b6b3f6 Add an alternative escape route to the south 2020-10-15 10:53:06 +02:00
abcdefg30
4173e40a9d Use actor names instead of searching for actors with a type 2020-10-15 10:53:06 +02:00
abcdefg30
1abd272862 Fix actor naming since those are not Nod soldiers 2020-10-15 10:53:06 +02:00
abcdefg30
946bf7b9fa Let civilians flee away from - not towards - the player in Nod04b 2020-10-15 10:53:06 +02:00
Matthias Mailänder
0d64fa549b Fix the command bar selecting dead units
and crashing while doing a trait lookup on them.
2020-10-14 19:58:24 +02:00
abcdefg30
8a7020b4ef Ignore the length requirement of sync orders we want to drop 2020-10-14 19:53:49 +02:00
abcdefg30
1bc19e788c Drop and log sync orders with mismatching length 2020-10-14 19:53:49 +02:00
abcdefg30
c17110dac5 Make OrderManager fields readonly where possible 2020-10-14 19:53:49 +02:00
abcdefg30
d07b34e67e Style fixups in ReplayConnection.cs 2020-10-14 19:53:49 +02:00
abcdefg30
accecee018 Fix savegames not saving sync packets correctly 2020-10-14 19:53:49 +02:00
abcdefg30
1861174d38 Add a Order.SyncHashOrderLength const 2020-10-14 19:53:49 +02:00
Curtis Shmyr
8d5ed65feb Remove effective owner check when choosing armament 2020-10-14 19:52:34 +02:00
Paul Chote
a375f0e58a Rewrite spawn point assignment logic. 2020-10-13 20:41:39 +02:00
RoosterDragon
b2b639434c ThreadedGraphicsContext improvements.
- VertexBuffer interface redefined to remove an IntPtr overload for SetData. This removes some unsafe code in TerrainSpriteLayer. This also allows the ThreadedVertexBuffer to use a buffer and post these calls, meaning the SetData call can now be non-blocking.
- ThreadedTexture SetData now checks the incoming array size. As the arrays sent here are usually large (megabytes) this allows us to avoid creating temp arrays in the LOH and skip Array.Copy calls on large arrays. This means the call is now blocking more often, but significantly reduces memory churn and GC Gen2 collections.
2020-10-13 15:54:53 +02:00
Curtis Shmyr
5eadd26f66 Set lobby bool display values to start uppercase 2020-10-12 22:05:35 +02:00
Curtis Shmyr
c0cbca26ea Use lobby option display values when clients join 2020-10-12 22:05:35 +02:00
RoosterDragon
8fb65fd9bf Use string pooling in MiniYaml to de-duplicate strings in memory.
These config files often contain many repeated strings which result in different string references in memory. By using a pool, we can detect when the strings are equal and reuse an existing reference as strings are immutable.

The FromLines will now use a pool to de-duplicate strings for a single call. By allowing a pool to be provided as a parameter, we can reuse even more strings. The MapCache defines such a pool so that strings are reused across all maps in the cache for even more savings.
2020-10-12 21:57:08 +02:00
RoosterDragon
9072d645fd Use TrimExcess to shrink lists used by MiniYaml after loading 2020-10-12 21:57:08 +02:00
Paul Chote
597b8b1caa Hide legacy GL support behind a feature flag. 2020-10-12 12:24:22 +02:00
Paul Chote
2c0d512727 Package and default to GLES via ANGLE on windows. 2020-10-12 12:24:22 +02:00
Paul Chote
87c5cc96ad Add an "Automatic" GL profile. 2020-10-12 12:24:22 +02:00
Paul Chote
fc844cfa6d Print the GL_RENDERER string to stdout on engine start. 2020-10-12 12:24:22 +02:00
Paul Chote
1ab1c30e39 Remove glGetTexImage/glBindFragDataLocation on GLES.
These functions are not available in GLES3.
2020-10-12 12:24:22 +02:00
reaperrr
f2a1a497c7 Fix Pillbox damage
These were overlooked during the RA target type
refactor.
2020-10-12 11:56:55 +02:00
Paul Chote
1ddbe50b3f Add inline node inclusion support to mod.yaml. 2020-10-11 01:42:59 +02:00
Paul Chote
dd7b8b24af Use tileset ID in sequences instead of the TileSet object. 2020-10-11 01:23:15 +02:00
abcdefg30
6dcb701d1d Fix a crash in AIUtils 2020-10-10 17:54:55 +02:00
reaperrr
f67b7ad837 Cache IResolveOrder traits on Actor
Avoids looking up all of them each time
an actor is given an order.
2020-10-10 13:59:40 +01:00
reaperrr
a4a409f39b Pass world directly to UnitOrders.ResolveOrder
Avoids order.Subject.World look-ups.

Also removes validOrders parameter,
we can get that directly from World now.
2020-10-10 13:59:40 +01:00
reaperrr
904a5f60d1 Cache IOrderValidator traits on World 2020-10-10 13:59:40 +01:00
dnqbob
ca8341d432 Avoid WaterCheck crash in base builder 2020-10-10 11:44:43 +02:00
Clément Bœsch
815bbc6ee8 Save disconnect frame in the GameInformation
This information is useful to infer a winner in case the winstate is
unknown.
2020-10-10 01:17:39 +02:00
Clément Bœsch
ca8870a5cf Server: handle wins/losses using Sync hash
Signed-off-by: Paul Chote <pchote@users.noreply.github.com>
2020-10-10 01:17:39 +02:00
Clément Bœsch
e5da58e2b4 Server: add basic replay recording
Signed-off-by: Paul Chote <pchote@users.noreply.github.com>
2020-10-10 01:17:39 +02:00
Paul Chote
dd18829def Traits: add ICreatePlayersInfo
Signed-off-by: Clément Bœsch <u@pkh.me>
2020-10-10 01:17:39 +02:00
Paul Chote
41814a881d Player: move player name resolve in a dedicated function
Signed-off-by: Paul Chote <pchote@users.noreply.github.com>
2020-10-10 01:17:39 +02:00
Clément Bœsch
d708f46d50 Orders: make SyncHash packet size check more accurate 2020-10-10 01:17:39 +02:00
Clément Bœsch
11f57b2b26 Protocol: add defeat state bitfields to Sync packets
Signed-off-by: Paul Chote <pchote@users.noreply.github.com>
2020-10-10 01:17:39 +02:00
Paul Chote
60df247416 Streamline SpawnOccupant management. 2020-10-09 12:19:19 +02:00
Trevor Nichols
6f32196f89 Support List<> for FieldSaver/FieldLoader 2020-10-07 22:04:10 +02:00
abcdefg30
d647aab7fe Fix d2k conyards granting an unconsumed "auto-concrete" condition 2020-10-06 23:25:12 +01:00
Mustafa Alperen Seki
be88c33399 Add a lobby option to disable Concrete in D2k. 2020-10-06 13:29:33 +02:00
Mustafa Alperen Seki
7372da150a Make LaysTerrain Conditional. 2020-10-06 13:29:33 +02:00
Mustafa Alperen Seki
3ec3eac160 Don't StartOnThreshold if the trait is disabled. 2020-10-06 13:29:33 +02:00
reaperrr
0990caefd7 Fix turreted defenses always realigning
This looks weird, doesn't match the originals
and makes map-defined custom facings useless.
2020-10-06 12:53:11 +02:00
Paul Chote
fc1786e243 Fix turrets immediately realigning on actor creation. 2020-10-05 15:13:48 +02:00
Matthias Mailänder
7e61199458 Fix a link in the Lua documentation. 2020-10-05 10:11:30 +02:00
Paul Chote
0672553a07 Lock Server.LobbyInfo to prevent races with callback threads. 2020-10-04 20:31:07 +02:00
reaperrr
8d1f72c104 Fix for SquadManagerBotModule
Aircraft and ships are immediately assigned a squad,
so adding them to `unitsHangingAroundTheBase` is bogus.
2020-10-04 00:31:35 +01:00
reaperrr
6337067032 Clean up property names + defaults of smudge smoke 2020-10-02 23:46:48 +02:00
reaperrr
7395a3ed2d Use cached world instead of query in SmudgeLayer 2020-10-02 23:46:48 +02:00
abcdefg30
e4e1878a4b Remove an used TerrainTemplateInfo constructor 2020-10-02 12:09:42 +02:00
abcdefg30
cc8908d7eb Make ProductionParadrop and ProductionAirdrop refund undelivered actors 2020-10-02 11:51:48 +02:00
abcdefg30
0dac4520ad Add a 'refundableValue' parameter to 'Produce' 2020-10-02 11:51:48 +02:00
Paul Chote
cc2e369475 Fix corrupted final frame in VQA playback. 2020-10-02 11:37:00 +02:00
tovl
92189e4b50 Let docking angle be determined by the host building. 2020-10-02 11:28:14 +02:00
abcdefg30
2e8c85ff0b Add support for querying the Passengers of an actor via Lua 2020-10-02 11:24:52 +02:00
abcdefg30
798aff1140 Fix ValidStances of RevealsMap not working 2020-10-02 11:17:03 +02:00
Niklas Holma
8596ce00cc Add a text field next to the map editor actor initializer sliders. 2020-09-28 15:46:19 +02:00
Matthias Mailänder
b5613acad8 Don't put absolute paths of developer machines in there. 2020-09-28 12:47:27 +02:00
Matthias Mailänder
7e992d44c8 Fix a crash when refineries share a cell. 2020-09-28 12:45:20 +02:00
Matthias Mailänder
7a7393b9f0 cachedWidth is never assigned. 2020-09-27 22:56:41 +01:00
Matthias Mailänder
f13e6fb76d Fix Analyzer warning V3008 variable is assigned values twice. 2020-09-27 22:56:41 +01:00
Mustafa Alperen Seki
3e83346915 Add DamageTypes to SoundOnDamageTransition. 2020-09-27 21:22:52 +01:00
Mustafa Alperen Seki
761d3583c4 Add DeathTypes to ShakeOnDeath. 2020-09-27 21:22:52 +01:00
abcdefg30
1a4b773fda Replace 'is' by 'as' and a null check in ScriptMemberWrapper 2020-09-27 22:17:20 +02:00
Matthias Mailänder
930f8ab207 Reset lobby ready state when options change server side. 2020-09-27 15:06:48 +02:00
Matthias Mailänder
2e438f1da9 Remove superfluous null checks. 2020-09-27 00:11:25 +01:00
abcdefg30
4627387ae3 Document the 'CancelActivity' field 2020-09-26 23:20:00 +01:00
abcdefg30
fd69bce609 Use CancelActivity instead of hackily using ResolveOrder 2020-09-26 23:20:00 +01:00
abcdefg30
391d9030cb Move update rules into their correct subfolder 2020-09-26 23:20:00 +01:00
abcdefg30
1ad9a4b65d Move shared color definitions to common metrics 2020-09-26 23:05:28 +01:00
abcdefg30
c6cc2405d3 Move shared sound definitions to common metrics 2020-09-26 23:05:28 +01:00
Matthias Mailänder
0e39cc7829 Tooltip is a conditional trait.
Fix Analyzer warning: V3146 Possible null dereference.
2020-09-26 22:59:39 +01:00
Matthias Mailänder
8d7e5f4663 Fix Analyzer warning: V3022 Expression is always true. 2020-09-26 22:59:39 +01:00
Paul Chote
e320bbfc87 Add disclaimer in readme. 2020-09-26 13:40:20 +02:00
Paul Chote
2e104046d4 Remove music downloads. 2020-09-26 13:40:20 +02:00
reaperrr
9cf38c1784 Increase Pillbox muzzle anim length
Length of muzzle anim now roughly matches the length
of the sound and impact animations.
2020-09-20 20:34:44 +02:00
Paul Chote
1aca6da1ea Hide "Furthest" battlefield camera option if the mod disables it. 2020-09-20 20:28:28 +02:00
abcdefg30
ce4d263b52 Fix AttackMove getting unarmed units stuck 2020-09-20 12:02:20 +02:00
abcdefg30
53c02eb2b9 Fix a crash in the Move activity 2020-09-18 20:12:42 +02:00
abcdefg30
82a2148300 Fix grouped orders not being validated correctly 2020-09-15 10:16:50 +02:00
Paul Chote
b72a58b917 Add missing ActorReferences to yaml dictionaries. 2020-09-15 09:27:50 +02:00
Paul Chote
7f7bce50dc Add dictionary support for ActorReference. 2020-09-15 09:27:50 +02:00
Paul Chote
290e214638 Remove unexpected RenderSprite.FactionImage fallback. 2020-09-12 17:52:46 +02:00
Paul Chote
ad4d6eaec9 Improve --check-missing-sprites error handling. 2020-09-12 17:52:46 +02:00
Paul Chote
cd9bf53e1a Use nameof() to reference image fields. 2020-09-12 17:52:46 +02:00
Paul Chote
ac8b312140 Generalize --check-sequence-sprites to --check-missing-sprites. 2020-09-12 17:52:46 +02:00
Paul Chote
3e849568ff Improve exception message for bogus tileset definitions. 2020-09-12 17:52:46 +02:00
Paul Chote
5d6961619d Fix WithIdleOverlayInfo Image override. 2020-09-12 17:52:46 +02:00
Paul Chote
94180f6a0a Add SequenceReference support for dictionaries. 2020-09-12 17:52:46 +02:00
Paul Chote
7803686aec Rewrite sequence linting / missing file handling.
- Distinguish between missing sequences and missing sprites
- Lint default sequences as well as maps
- Improved performance
- Correctly handle null images
2020-09-12 17:52:46 +02:00
Paul Chote
b8e60ca8ec Fix SequenceReference definitions. 2020-09-12 17:52:46 +02:00
Paul Chote
b985edbc29 Add ModData to ILintRulesPass.Run. 2020-09-12 17:52:46 +02:00
Paul Chote
ffdb3f86d7 Move mod-specific lint attributes to Mods.Common. 2020-09-12 17:52:46 +02:00
Paul Chote
c9b2a34561 Save SubCellInit as an integer. 2020-09-12 17:17:26 +02:00
Paul Chote
e5dc0309f1 Support OPENRA_DISPLAY_SCALE environment variable on Windows. 2020-09-12 00:37:20 +02:00
abcdefg30
57a3ad8ae2 Replace 'inner' with 'move' inside AttackMoveActivity 2020-09-12 00:30:33 +02:00
abcdefg30
ad3722e19f Add support for circumventing the target scan limit in AttackMove 2020-09-12 00:30:33 +02:00
abcdefg30
a12d127fd6 Rework the internals of AttackMoveActivity
Avoids creating and throwing away inner activities unnecessarily
2020-09-12 00:30:33 +02:00
abcdefg30
e80ebfae35 Use readonly where possible in AttackMoveActivity 2020-09-12 00:30:33 +02:00
abcdefg30
4669d6b378 Work around a sound issue when loading saves 2020-09-12 00:18:25 +02:00
abcdefg30
9f093da61e Unhardcode the leeway range of AttackFollow 2020-09-11 23:55:57 +02:00
abcdefg30
c0d31688c4 Exclude walls from the assets value 2020-09-11 23:44:30 +02:00
abcdefg30
86698e7822 Exclude C17, A10 and carryall.reinforce from the assets value 2020-09-11 23:44:30 +02:00
abcdefg30
3fc5859f08 Add 'AddToAssetsValue' to 'UpdatesPlayerStatistics' 2020-09-11 23:44:30 +02:00
tovl
f9bb663f6b Allow thieves to capture landed aircraft in RA mod. 2020-09-10 18:18:09 +02:00
tovl
5e8121bd0b Properly cancel EnterActor when target capturable trait is disabled. 2020-09-10 18:18:09 +02:00
Paul Chote
9cd6df2929 Replace DecorationPosition with mod-defined string ids. 2020-09-09 23:16:05 +02:00
Matthias Mailänder
b16cbeacb6 Sort contents before merging to unify UIDs across filesystems. 2020-09-07 17:25:24 +02:00
Matthias Mailänder
4d46464bc6 Fix an invalid target crash
target.CenterPosition is referenced in warhead orientation later
2020-09-06 12:21:15 +01:00
Matthias Mailänder
7746dc55f0 Remove the table of content
which is auto-generated by mkdocs.
2020-09-06 11:44:23 +01:00
Matthias Mailänder
5d9e8b56c5 Use fewer hard-coded HTML tags to improve mkdocs rendering. 2020-09-06 11:44:23 +01:00
Matthias Mailänder
d1c06365a8 Move automatic API documentation to docs.openra.net 2020-09-06 11:44:23 +01:00
Matthias Mailänder
b56ddc1a08 Check global scripts for Lua errors. 2020-09-05 21:13:47 +01:00
Orb
ae8c8e781f TD Balance Summer 2020 2 2020-09-05 21:02:59 +01:00
reaperrr
1344b1f2e3 Remove system order checks from PlayerStatistics
ResolveOrder for actors isn't even used
for these.
2020-09-05 17:22:04 +01:00
reaperrr
36df25dcb4 GivesExperience performance optimization
Move some look-ups to creation to reduce contribution
to actor death cost.
2020-09-05 17:16:00 +01:00
reaperrr
84246d287d Merge two ifs into one in SpawnActorOnDeath 2020-09-05 17:16:00 +01:00
reaperrr
6d0fbfa21f Explodes performance optimization
Cache armaments on creation, avoid LINQ.

Also merge and put first the DamageThreshold == 0
check in Damaged, because the common default IS 0,
so most of the time the IsTraitDisabled and
IsInWorld checks are redundant.
2020-09-05 17:16:00 +01:00
abcdefg30
034c6824de Fix MiG missiles damaging airborne actors 2020-09-05 18:06:30 +02:00
reaperrr
c8afa4a2a8 Remove defenderStats look-up from UpdatePlayerStatistics
playerStats is updated on owner change,
so it should always be identical to the removed defenderStats.
2020-09-05 11:12:38 +02:00
abcdefg30
1a77f7320b Fix a crash when updating the DiscordPresence party size 2020-09-05 10:36:35 +02:00
abcdefg30
183ece1360 Remove the unused SetPlayers method 2020-09-05 10:36:35 +02:00
abcdefg30
035cc3da99 Turn DiscordService's 'GetService' into property 2020-09-05 10:36:35 +02:00
801 changed files with 25419 additions and 20341 deletions

View File

@@ -1,11 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Frequently Asked Questions
url: https://github.com/OpenRA/OpenRA/wiki/FAQ#frequently-asked-questions
about: Explanations for common problems and questions.
- name: OpenRA Forum
url: https://forum.openra.net/
about: "Please ask questions about modding here."
about: Please ask questions about modding here.
- name: OpenRA Discord server
url: https://discord.openra.net/
about: "Join the community Discord server for community discussion and support."
about: Join the community Discord server for community discussion and support.
- name: OpenRA IRC Channel
url: https://webchat.freenode.net/#openra
about: "Join our development IRC channel on freenode for discussion of development topics."
about: Join our development IRC channel on freenode for discussion of development topics.

View File

@@ -1,6 +1,6 @@
---
name: Crash report
about: Report a game crash.
about: Report a game crash. Check the FAQ first https://github.com/OpenRA/OpenRA/wiki/FAQ#common-issues
title: My game crashed
labels: Crash
assignees: ''

55
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Continuous Integration
on:
push:
pull_request:
branches: [ bleed ]
jobs:
linux-mono:
name: Linux (mono)
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
- name: Check Code
run: |
mono --version
make check
- name: Check Mods
run: |
sudo apt-get install lua5.1
make check-scripts
make test
windows:
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 locals all --clear
.\make.ps1 check
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: |
chocolatey install lua --version 5.1.5.52
$ENV:Path = $ENV:Path + ";C:\Program Files (x86)\Lua\5.1\"
.\make.ps1 check-scripts
.\make.ps1 test

107
.github/workflows/documentation.yml vendored Normal file
View File

@@ -0,0 +1,107 @@
name: Deploy Documentation
on:
workflow_dispatch:
inputs:
tag:
description: 'Git Tag'
required: true
default: 'release-xxxxxxxx'
jobs:
wiki:
name: Update Wiki
if: github.repository == 'openra/openra'
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.tag }}
- name: Prepare Environment
run: |
make all
- name: Clone Wiki
uses: actions/checkout@v2
with:
repository: openra/openra.wiki
token: ${{ secrets.DOCS_TOKEN }}
path: wiki
- name: Update Wiki (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings (playtest).md"
- name: Update Wiki (Release)
if: startsWith(github.event.inputs.tag, 'release-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --settings-docs "${GIT_TAG}" > "wiki/Settings.md"
- name: Push Wiki
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
cd wiki
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
git add --all
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
git push origin master
docs:
name: Update docs.openra.net
if: github.repository == 'openra/openra'
runs-on: ubuntu-20.04
steps:
- name: Clone Repository
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.tag }}
- name: Prepare Environment
run: |
make all
- name: Clone docs.openra.net
uses: actions/checkout@v2
with:
repository: openra/docs
token: ${{ secrets.DOCS_TOKEN }}
path: docs
- name: Update docs.openra.net (Playtest)
if: startsWith(github.event.inputs.tag, 'playtest-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --docs "${GIT_TAG}" > "docs/api/playtest/traits.md"
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/playtest/weapons.md"
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/playtest/lua.md"
- name: Update docs.openra.net (Release)
if: startsWith(github.event.inputs.tag, 'release-')
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
./utility.sh all --docs "${GIT_TAG}" > "docs/api/release/traits.md"
./utility.sh all --weapon-docs "${GIT_TAG}" > "docs/api/release/weapons.md"
./utility.sh all --lua-docs "${GIT_TAG}" > "docs/api/release/lua.md"
- name: Push docs.openra.net
env:
GIT_TAG: ${{ github.event.inputs.tag }}
run: |
cd docs
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
git add --all
git commit -m "Update auto-generated documentation for ${GIT_TAG}"
git push origin master

88
.github/workflows/itch.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Deploy itch.io Packages
on:
workflow_dispatch:
inputs:
tag:
description: 'Git Tag'
required: true
default: 'release-xxxxxxxx'
jobs:
itch:
name: Deploy to itch.io
runs-on: ubuntu-20.04
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
- name: Publish Windows Installer
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: win
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}-x64.exe"
- name: Publish Windows Itch Bundle
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: itch
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}-x64-win-itch.zip
- name: Publish macOS Package
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: macos
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-${{ github.event.inputs.tag }}}.dmg"
- name: Publish RA AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-ra
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Red-Alert-x86_64.AppImage
- name: Publish TD AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-cnc
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Tiberian-Dawn-x86_64.AppImage
- name: Publish D2k AppImage
uses: josephbmanley/butler-publish-itchio-action@master
env:
BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }}
CHANNEL: linux-d2k
ITCH_GAME: openra
ITCH_USER: openra-developers
VERSION: ${{ github.event.inputs.tag }}
PACKAGE: OpenRA-Dune-2000-x86_64.AppImage

99
.github/workflows/packaging.yml vendored Normal file
View File

@@ -0,0 +1,99 @@
name: Release Packaging
on:
push:
tags:
- 'release-*'
- 'playtest-*'
- 'devtest-*'
jobs:
linux:
name: Linux AppImages
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 AppImages
run: |
mkdir -p build/linux
./packaging/linux/buildpackage.sh "${GIT_TAG}" "${PWD}/build/linux"
- 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/linux/*
macos:
name: macOS Disk Images
runs-on: macos-10.15
steps:
- 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}
- name: Package Disk Images
env:
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
MACOS_DEVELOPER_CERTIFICATE_BASE64: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_BASE64 }}
MACOS_DEVELOPER_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_DEVELOPER_CERTIFICATE_PASSWORD }}
MACOS_DEVELOPER_USERNAME: ${{ secrets.MACOS_DEVELOPER_USERNAME }}
MACOS_DEVELOPER_PASSWORD: ${{ secrets.MACOS_DEVELOPER_PASSWORD }}
run: |
mkdir -p build/macos
./packaging/macos/buildpackage.sh "${GIT_TAG}" "${PWD}/build/macos"
- 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/macos/*
windows:
name: Windows Installers
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: Prepare Environment
run: |
echo "GIT_TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_ENV}
sudo apt install nsis wine64
- name: Package Installers
run: |
mkdir -p build/windows
./packaging/windows/buildpackage.sh "${GIT_TAG}" "${PWD}/build/windows"
- 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/windows/*

View File

@@ -1,92 +0,0 @@
# Travis-CI Build for OpenRA
# see travis-ci.org for details
language: csharp
mono: 6.4.0
os: linux
dist: xenial
jobs:
include:
- os: linux
dist: xenial
- os: osx
if: tag IS present
osx_image: xcode10
addons:
apt:
packages:
- lua5.1
- dpkg
- zsync
- imagemagick
# Environment variables
env:
secure: "C0+Hlfa0YGErxUuWV00Tj6p45otC/D3YwYFuLpi2mj1rDFn/4dgh5WRngjvdDBVbXJ3duaZ78jPHWm1jr7vn2jqj9yETsCIK9psWd38ep/FEBM0SDr6MUD89OuXk/YyvxJAE+UXF6bXg7giey09g/CwBigjMW7ynET3wNAWPHPs="
# Fetch dependencies
# Run the build script
# Check source code with StyleCop
# call OpenRA to check for YAML errors
# Run the NUnit tests
script:
- make all
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
make check || travis_terminate 1;
make check-scripts || travis_terminate 1;
make test || travis_terminate 1;
mono ~/.nuget/packages/nunit.consolerunner/3.11.1/tools/nunit3-console.exe --noresult OpenRA.Test.dll || travis_terminate 1;
fi
# Only watch the development branch and tagged release.
branches:
only:
- /^release-.*$/
- /^playtest-.*$/
- /^devtest-.*$/
- /^prep-.*$/
- bleed
# Notify developers when build passed/failed.
notifications:
irc:
if: repo = OpenRA/OpenRA
template:
- "%{repository}#%{build_number} %{commit} %{author}: %{message} %{build_url}"
channels:
- "irc.freenode.net#openra"
use_notice: true
skip_join: true
before_deploy:
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis-common_3.04-1_all.deb;
wget https://mirrors.edge.kernel.org/ubuntu/pool/universe/n/nsis/nsis_3.04-1_amd64.deb;
sudo dpkg -i nsis-common_3.04-1_all.deb;
sudo dpkg -i nsis_3.04-1_amd64.deb;
echo ${TRAVIS_REPO_SLUG};
if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
cd packaging && ./update-wiki.sh ${TRAVIS_TAG} && cd ..;
fi;
fi
- export PATH=${PATH}:${HOME}/usr/bin
- DOTVERSION=`echo ${TRAVIS_TAG} | sed "s/-/\\./g"`
- cd packaging
- mkdir build
- ./package-all.sh ${TRAVIS_TAG} ${PWD}/build/
- if [[ "${TRAVIS_REPO_SLUG}" == "OpenRA/OpenRA" ]]; then
./upload-itch.sh ${TRAVIS_TAG} ${PWD}/build/;
fi
deploy:
provider: releases
token: ${GH_DEPLOY_API_KEY}
file_glob: true
file: build/*
skip_cleanup: true
on:
all_branches: true
tags: true

12
.vscode/launch.json vendored
View File

@@ -13,7 +13,8 @@
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=cnc"]
"args": ["Game.Mod=cnc"],
"preLaunchTask": "build",
},
{
"name": "Launch (RA)",
@@ -27,7 +28,8 @@
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ra"]
"args": ["Game.Mod=ra"],
"preLaunchTask": "build",
},
{
"name": "Launch (D2k)",
@@ -41,7 +43,8 @@
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=d2k"]
"args": ["Game.Mod=d2k"],
"preLaunchTask": "build",
},
{
"name": "Launch (TS)",
@@ -55,7 +58,8 @@
"request": "launch",
"program": "${workspaceRoot}/OpenRA.Game.exe",
"cwd": "${workspaceRoot}",
"args": ["Game.Mod=ts"]
"args": ["Game.Mod=ts"],
"preLaunchTask": "build",
},
]
}

View File

@@ -145,6 +145,7 @@ Also thanks to:
* Tirili
* Tomas Einarsson (Mesacer)
* Tom van Leth (tovl)
* Trevor Nichols (ocdi)
* Tristan Keating (Kilkakon)
* Tristan Mühlbacher (MicroBit)
* UnknownProgrammer
@@ -188,6 +189,8 @@ distributed under MIT License.
Using Json.NET developed by James Newton-King
distributed under MIT License.
Using ANGLE distributed under the BS3 3-Clause license.
This site or product includes IP2Location LITE data
available from http://www.ip2location.com.

View File

@@ -8,18 +8,16 @@ 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.
Mono, version 6.4 or later, is required to compile OpenRA. You can add the [upstream mono repository](https://www.mono-project.com/download/stable/#download-lin) for your distro to obtain the latest version if your system packages are not sufficient.
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.
@@ -99,7 +97,7 @@ macOS
=====
Before compiling OpenRA you must install the following dependencies:
* [Mono >= 5.18](https://www.mono-project.com/download/stable/#download-mac)
* [Mono >= 6.4](https://www.mono-project.com/download/stable/#download-mac)
To compile OpenRA, run `make` from the command line. Run with `./launch-game.sh`.

344
Makefile
View File

@@ -12,45 +12,34 @@
# to check the engine and official mod dlls for code style violations, run:
# make check
#
# to install, run:
# to compile and install Red Alert, Tiberian Dawn, and Dune 2000, run:
# make [prefix=/foo] [bindir=/bar/bin] install
#
# to install Linux startup scripts, desktop files and icons:
# make install-linux-shortcuts [DEBUG=false]
# to install Linux startup scripts, desktop files, icons, and MIME metadata
# make install-linux-shortcuts
#
# to install the engine and common mod files (omitting the default mods):
# make install-engine
# make install-dependencies
# make install-common-mod-files
#
# to uninstall, run:
# make uninstall
# to install Linux AppStream metadata
# make install-linux-appdata
#
# for help, run:
# make help
#
# to start the game, run:
# openra
############################## TOOLCHAIN ###############################
#
# List of .NET assemblies that we can guarantee exist
# OpenRA.Game.dll is a harmless false positive that we can ignore
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.dll OpenRA.Utility.dll OpenRA.Server.dll 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
NUNIT_LIBS_PATH :=
NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll
WHITELISTED_CORE_ASSEMBLIES = mscorlib.dll System.dll System.Configuration.dll System.Core.dll System.Numerics.dll System.Security.dll System.Xml.dll Mono.Security.dll netstandard.dll Microsoft.Win32.Registry.dll System.Security.AccessControl.dll System.Security.Principal.Windows.dll System.Xml.Linq.dll System.Runtime.Serialization.dll
######################### UTILITIES/SETTINGS ###########################
#
# install locations
# Install locations for local installs and downstream packaging
prefix ?= /usr/local
datarootdir ?= $(prefix)/share
datadir ?= $(datarootdir)
@@ -60,27 +49,21 @@ libdir ?= $(prefix)/lib
gameinstalldir ?= $(libdir)/openra
BIN_INSTALL_DIR = $(DESTDIR)$(bindir)
DATA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
DATA_INSTALL_DIR = $(DESTDIR)$(datadir)
OPENRA_INSTALL_DIR = $(DESTDIR)$(gameinstalldir)
# install tools
# Toolchain
CWD = $(shell pwd)
MSBUILD = msbuild -verbosity:m -nologo
MONO = mono
RM = rm
RM_R = $(RM) -r
RM_F = $(RM) -f
RM_RF = $(RM) -rf
CP = cp
CP_R = $(CP) -r
INSTALL = install
INSTALL_DIR = $(INSTALL) -d
INSTALL_PROGRAM = $(INSTALL) -m755
INSTALL_DATA = $(INSTALL) -m644
# Toolchain
MSBUILD = msbuild -verbosity:m -nologo
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
# Enable 32 bit builds while generating the windows installer
WIN32 = false
# dependencies
# Detect target platform for dependencies if not given by the user
ifndef TARGETPLATFORM
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
@@ -95,242 +78,73 @@ endif
endif
endif
# program targets
VERSION = $(shell git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || echo git-`git rev-parse --short HEAD`)
OPENRA_UTILITY = ENGINE_DIR=".." $(MONO) --debug bin/OpenRA.Utility.dll
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -p:Configuration=Debug
@echo
@echo "Checking runtime assemblies..."
@mono --debug OpenRA.Utility.exe all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
@echo
@echo "Checking for explicit interface violations..."
@mono --debug OpenRA.Utility.exe all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@mono --debug OpenRA.Utility.exe all --check-conditional-trait-interface-overrides
test: core
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@mono --debug OpenRA.Utility.exe ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@mono --debug OpenRA.Utility.exe d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@mono --debug OpenRA.Utility.exe cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@mono --debug OpenRA.Utility.exe ra --check-yaml
########################## MAKE/INSTALL RULES ##########################
##################### DEVELOPMENT BUILDS AND TESTS #####################
#
all: core
core:
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)
@$(MSBUILD) -t:Build -restore -p:Configuration=Release -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
ifeq ($(TARGETPLATFORM), unix-generic)
@./configure-system-libraries.sh
endif
@./fetch-geoip.sh
clean:
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.exe *.dll *.dll.config *.so *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_RF) ./*/bin ./*/obj
@ $(MSBUILD) -t:clean
@-$(RM_RF) ./bin ./*/bin ./*/obj
@$(MSBUILD) -t:Clean -p:Mono=true
@-$(RM_F) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
check:
@echo
@echo "Compiling in debug mode..."
@$(MSBUILD) -t:build -restore -p:Configuration=Debug -p:TargetPlatform=$(TARGETPLATFORM) -p:Mono=true -p:DefineConstants="MONO"
@echo
@echo "Checking runtime assemblies..."
@$(OPENRA_UTILITY) all --check-runtime-assemblies $(WHITELISTED_OPENRA_ASSEMBLIES) $(WHITELISTED_THIRDPARTY_ASSEMBLIES) $(WHITELISTED_CORE_ASSEMBLIES)
@echo
@echo "Checking for explicit interface violations..."
@$(OPENRA_UTILITY) all --check-explicit-interfaces
@echo
@echo "Checking for incorrect conditional trait interface overrides..."
@$(OPENRA_UTILITY) all --check-conditional-trait-interface-overrides
check-scripts:
@echo
@echo "Checking for Lua syntax errors..."
@luac -p $(shell find mods/*/maps/* -iname '*.lua')
@luac -p $(shell find lua/* -iname '*.lua')
@luac -p $(shell find mods/*/bits/scripts/* -iname '*.lua')
test: all
@echo
@echo "Testing Tiberian Sun mod MiniYAML..."
@$(OPENRA_UTILITY) ts --check-yaml
@echo
@echo "Testing Dune 2000 mod MiniYAML..."
@$(OPENRA_UTILITY) d2k --check-yaml
@echo
@echo "Testing Tiberian Dawn mod MiniYAML..."
@$(OPENRA_UTILITY) cnc --check-yaml
@echo
@echo "Testing Red Alert mod MiniYAML..."
@$(OPENRA_UTILITY) 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
@echo "$(VERSION)" > VERSION
@for i in $? ; do \
awk '{sub("Version:.*$$","Version: $(VERSION)"); print $0}' $${i} > $${i}.tmp && \
awk '{sub("/[^/]*: User$$", "/$(VERSION): User"); print $0}' $${i}.tmp > $${i} && \
rm $${i}.tmp; \
done
@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: core install-engine install-common-mod-files install-default-mods
@$(CP) *.sh "$(DATA_INSTALL_DIR)"
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'
install-linux-shortcuts: install-linux-scripts install-linux-icons install-linux-desktop
install-dependencies:
ifeq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dll "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), linux-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.so "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.so "$(DATA_INSTALL_DIR)"
endif
ifeq ($(TARGETPLATFORM), osx-x64)
@-echo "Installing OpenRA dependencies to $(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) soft_oal.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) SDL2.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) freetype6.dylib "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) lua51.dylib "$(DATA_INSTALL_DIR)"
endif
install-engine:
@-echo "Installing OpenRA engine to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Game.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Server.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Utility.exe "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) OpenRA.Platforms.Default.dll "$(DATA_INSTALL_DIR)"
ifneq ($(TARGETPLATFORM), $(filter $(TARGETPLATFORM),win-x86 win-x64))
@$(INSTALL_DATA) OpenRA.Platforms.Default.dll.config "$(DATA_INSTALL_DIR)"
endif
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
@$(INSTALL_DATA) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP "$(DATA_INSTALL_DIR)/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
@$(CP) SDL2-CS* "$(DATA_INSTALL_DIR)"
@$(CP) OpenAL-CS* "$(DATA_INSTALL_DIR)"
@$(CP) Eluant* "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) BeaconLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) DiscordRPC.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Newtonsoft.Json.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:
@-echo "Installing OpenRA common mod files to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/common "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Common.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_PROGRAM) mods/common/OpenRA.Mods.Cnc.dll "$(DATA_INSTALL_DIR)/mods/common"
@$(INSTALL_DATA) "global mix database.dat" "$(DATA_INSTALL_DIR)/global mix database.dat"
install-default-mods:
@-echo "Installing OpenRA default mods to $(DATA_INSTALL_DIR)"
@$(INSTALL_DIR) "$(DATA_INSTALL_DIR)/mods"
@$(CP_R) mods/cnc "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/ra "$(DATA_INSTALL_DIR)/mods/"
@$(CP_R) mods/d2k "$(DATA_INSTALL_DIR)/mods/"
@$(INSTALL_PROGRAM) mods/d2k/OpenRA.Mods.D2k.dll "$(DATA_INSTALL_DIR)/mods/d2k"
@$(CP_R) mods/modcontent "$(DATA_INSTALL_DIR)/mods/"
install-linux-icons:
for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps"; \
$(INSTALL_DATA) packaging/artwork/ra_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(INSTALL_DATA) packaging/artwork/cnc_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(INSTALL_DATA) packaging/artwork/d2k_$$SIZE.png "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
$(INSTALL_DIR) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps"
$(INSTALL_DATA) packaging/artwork/ra_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
$(INSTALL_DATA) packaging/artwork/cnc_scalable.svg "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
install-linux-desktop:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/ra/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Red Alert/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-ra.desktop
@$(INSTALL_DATA) packaging/linux/openra-ra.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Tiberian Dawn/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-cnc.desktop
@$(INSTALL_DATA) packaging/linux/openra-cnc.desktop "$(DESTDIR)$(datadir)/applications"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.desktop.in | sed 's/{MODNAME}/Dune 2000/g' | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-d2k.desktop
@$(INSTALL_DATA) packaging/linux/openra-d2k.desktop "$(DESTDIR)$(datadir)/applications"
@-$(RM) packaging/linux/openra-ra.desktop packaging/linux/openra-cnc.desktop packaging/linux/openra-d2k.desktop
install-linux-mime:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/mime/packages/"
@sed 's/{MODID}/ra/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@sed 's/{MODID}/cnc/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@sed 's/{MODID}/d2k/g' packaging/linux/openra-mimeinfo.xml.in | sed 's/{TAG}/$(VERSION)/g' > packaging/linux/openra-mimeinfo.xml
@$(INSTALL_DATA) packaging/linux/openra-mimeinfo.xml "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
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'
install-linux-appdata:
@$(INSTALL_DIR) "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/ra/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Red Alert/g' | sed 's/{SCREENSHOT_RA}/ type="default"/g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-ra.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-ra.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/cnc/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Tiberian Dawn/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}/ type="default"/g' | sed 's/{SCREENSHOT_D2K}//g'> packaging/linux/openra-cnc.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-cnc.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@sed 's/{MODID}/d2k/g' packaging/linux/openra.appdata.xml.in | sed 's/{MOD_NAME}/Dune 2000/g' | sed 's/{SCREENSHOT_RA}//g' | sed 's/{SCREENSHOT_CNC}//g' | sed 's/{SCREENSHOT_D2K}/ type="default"/g'> packaging/linux/openra-d2k.appdata.xml
@$(INSTALL_DATA) packaging/linux/openra-d2k.appdata.xml "$(DESTDIR)$(datadir)/appdata/"
@-$(RM) packaging/linux/openra-ra.appdata.xml packaging/linux/openra-cnc.appdata.xml packaging/linux/openra-d2k.appdata.xml
install-man-page:
@$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man6/"
@mono --debug OpenRA.Utility.exe all --man-page > openra.6
@$(INSTALL_DATA) openra.6 "$(DESTDIR)$(mandir)/man6/"
@-$(RM) openra.6
install-linux-scripts:
ifeq ($(DEBUG), $(filter $(DEBUG),false no n off 0))
@sed 's/{DEBUG}//' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}//' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
else
@sed 's/{DEBUG}/--debug/' packaging/linux/openra.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra.debug.in
@sed 's/{DEBUG}/--debug/' packaging/linux/openra-server.in | sed 's|{GAME_INSTALL_DIR}|$(gameinstalldir)|' | sed 's|{BIN_DIR}|$(bindir)|' > packaging/linux/openra-server.debug.in
endif
@sed 's/{MODID}/ra/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra
@sed 's/{MODID}/cnc/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc
@sed 's/{MODID}/d2k/g' packaging/linux/openra.debug.in | sed 's/{TAG}/$(VERSION)/g' | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra packaging/linux/openra-cnc packaging/linux/openra-d2k packaging/linux/openra.debug.in
@sed 's/{MODID}/ra/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Red Alert/g' > packaging/linux/openra-ra-server
@sed 's/{MODID}/cnc/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Tiberian Dawn/g' > packaging/linux/openra-cnc-server
@sed 's/{MODID}/d2k/g' packaging/linux/openra-server.debug.in | sed 's/{MODNAME}/Dune 2000/g' > packaging/linux/openra-d2k-server
@$(INSTALL_DIR) "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-ra-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-cnc-server "$(BIN_INSTALL_DIR)"
@$(INSTALL_PROGRAM) -m +rx packaging/linux/openra-d2k-server "$(BIN_INSTALL_DIR)"
@-$(RM) packaging/linux/openra-ra-server packaging/linux/openra-cnc-server packaging/linux/openra-d2k-server packaging/linux/openra-server.debug.in
uninstall:
@-$(RM_R) "$(DATA_INSTALL_DIR)"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-ra-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-cnc-server"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k"
@-$(RM_F) "$(BIN_INSTALL_DIR)/openra-d2k-server"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-ra.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-cnc.desktop"
@-$(RM_F) "$(DESTDIR)$(datadir)/applications/openra-d2k.desktop"
@-for SIZE in 16x16 32x32 48x48 64x64 128x128; do \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-ra.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-cnc.png"; \
$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/$$SIZE/apps/openra-d2k.png"; \
done
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-ra.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/icons/hicolor/scalable/apps/openra-cnc.svg"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-ra.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-cnc.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/mime/packages/openra-d2k.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-ra.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-cnc.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(datadir)/appdata/openra-d2k.appdata.xml"
@-$(RM_F) "$(DESTDIR)$(mandir)/man6/openra.6"
@sh -c '. ./packaging/functions.sh; install_linux_appdata $(CWD) $(DATA_INSTALL_DIR) cnc d2k ra'
help:
@echo 'to compile, run:'
@@ -345,22 +159,14 @@ help:
@echo 'to check the engine and official mod dlls for code style violations, run:'
@echo ' make test'
@echo
@echo 'to install, run:'
@echo ' make [prefix=/foo] [bindir=/bar/bin] install'
@echo 'to compile and install Red Alert, Tiberian Dawn, and Dune 2000 run:'
@echo ' make [prefix=/foo] install'
@echo
@echo 'to install Linux startup scripts, desktop files and icons'
@echo ' make install-linux-shortcuts [DEBUG=false]'
@echo 'to install Linux startup scripts, desktop files, icons, and MIME metadata'
@echo ' make install-linux-shortcuts'
@echo
@echo ' to install the engine and common mod files (omitting the default mods):'
@echo ' make install-engine'
@echo ' make install-dependencies'
@echo ' make install-common-mod-files'
@echo
@echo 'to uninstall, run:'
@echo ' make uninstall'
@echo
@echo 'to start the game, run:'
@echo ' openra'
@echo 'to install Linux AppStream metadata'
@echo ' make install-linux-appdata'
########################### MAKEFILE SETTINGS ##########################
#
@@ -368,4 +174,4 @@ help:
.SUFFIXES:
.PHONY: check-scripts check test all core clean version install install-linux-shortcuts install-dependencies install-engine install-common-mod-files install-default-mods install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
.PHONY: all clean check check-scripts test version install install-linux-shortcuts install-linux-appdata help

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Activities
public readonly Color Color;
public readonly Sprite Tile;
public TargetLineNode(Target target, Color color, Sprite tile = null)
public TargetLineNode(in Target target, Color color, Sprite tile = null)
{
// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
// if "yield break" in TargetLineNode(Actor self) is not feasible.

View File

@@ -102,6 +102,7 @@ namespace OpenRA
readonly IFacing facing;
readonly IHealth health;
readonly IResolveOrder[] resolveOrders;
readonly IRenderModifier[] renderModifiers;
readonly IRender[] renders;
readonly IMouseBounds[] mouseBounds;
@@ -109,9 +110,10 @@ namespace OpenRA
readonly IDefaultVisibility defaultVisibility;
readonly INotifyBecomingIdle[] becomingIdles;
readonly INotifyIdle[] tickIdles;
readonly ITargetablePositions[] targetablePositions;
readonly IEnumerable<ITargetablePositions> enabledTargetablePositions;
WPos[] staticTargetablePositions;
bool created;
bool setStaticTargetablePositions;
internal Actor(World world, string name, TypeDictionary initDict)
{
@@ -139,42 +141,61 @@ namespace OpenRA
throw new NotImplementedException("No rules definition for unit " + name);
Info = world.Map.Rules.Actors[name];
foreach (var trait in Info.TraitsInConstructOrder())
IPositionable positionable = null;
var resolveOrdersList = new List<IResolveOrder>();
var renderModifiersList = new List<IRenderModifier>();
var rendersList = new List<IRender>();
var mouseBoundsList = new List<IMouseBounds>();
var visibilityModifiersList = new List<IVisibilityModifier>();
var becomingIdlesList = new List<INotifyBecomingIdle>();
var tickIdlesList = new List<INotifyIdle>();
var targetablesList = new List<ITargetable>();
var targetablePositionsList = new List<ITargetablePositions>();
var syncHashesList = new List<SyncHash>();
foreach (var traitInfo in Info.TraitsInConstructOrder())
{
AddTrait(trait.Create(init));
var trait = traitInfo.Create(init);
AddTrait(trait);
// Some traits rely on properties provided by IOccupySpace in their initialization,
// so we must ready it now, we cannot wait until all traits have finished construction.
if (trait is IOccupySpaceInfo)
OccupiesSpace = Trait<IOccupySpace>();
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
// Note: The blocks are required to limit the scope of the t's, so we make an exception to our normal style
// rules for spacing in order to keep these assignments compact and readable.
{ if (trait is IPositionable t) positionable = t; }
{ if (trait is IOccupySpace t) OccupiesSpace = t; }
{ if (trait is IEffectiveOwner t) EffectiveOwner = t; }
{ if (trait is IFacing t) facing = t; }
{ if (trait is IHealth t) health = t; }
{ if (trait is IResolveOrder t) resolveOrdersList.Add(t); }
{ if (trait is IRenderModifier t) renderModifiersList.Add(t); }
{ if (trait is IRender t) rendersList.Add(t); }
{ if (trait is IMouseBounds t) mouseBoundsList.Add(t); }
{ if (trait is IVisibilityModifier t) visibilityModifiersList.Add(t); }
{ if (trait is IDefaultVisibility t) defaultVisibility = t; }
{ if (trait is INotifyBecomingIdle t) becomingIdlesList.Add(t); }
{ if (trait is INotifyIdle t) tickIdlesList.Add(t); }
{ if (trait is ITargetable t) targetablesList.Add(t); }
{ if (trait is ITargetablePositions t) targetablePositionsList.Add(t); }
{ if (trait is ISync t) syncHashesList.Add(new SyncHash(t)); }
}
resolveOrders = resolveOrdersList.ToArray();
renderModifiers = renderModifiersList.ToArray();
renders = rendersList.ToArray();
mouseBounds = mouseBoundsList.ToArray();
visibilityModifiers = visibilityModifiersList.ToArray();
becomingIdles = becomingIdlesList.ToArray();
tickIdles = tickIdlesList.ToArray();
Targetables = targetablesList.ToArray();
var targetablePositions = targetablePositionsList.ToArray();
enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled);
SyncHashes = syncHashesList.ToArray();
setStaticTargetablePositions = positionable == null && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled);
}
// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
facing = TraitOrDefault<IFacing>();
health = TraitOrDefault<IHealth>();
renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
renders = TraitsImplementing<IRender>().ToArray();
mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
defaultVisibility = Trait<IDefaultVisibility>();
becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
Targetables = TraitsImplementing<ITargetable>().ToArray();
targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
world.AddFrameEndTask(w =>
{
// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
});
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
}
internal void Initialize(bool addToWorld = true)
@@ -208,7 +229,12 @@ namespace OpenRA
foreach (var notify in allObserverNotifiers)
notify(this, readOnlyConditionCache);
// TODO: Some traits may need initialization after being notified of initial condition state.
// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
if (setStaticTargetablePositions)
staticTargetablePositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
// TODO: Other traits may need initialization after being notified of initial condition state.
// TODO: A post condition initialization notification phase may allow queueing activities instead.
// The initial activity should run before any activities queued by INotifyCreated.Created
@@ -404,6 +430,12 @@ namespace OpenRA
});
}
public void ResolveOrder(Order order)
{
foreach (var r in resolveOrders)
r.ResolveOrder(this, order);
}
// TODO: move elsewhere.
public void ChangeOwner(Player newOwner)
{
@@ -507,9 +539,8 @@ namespace OpenRA
if (staticTargetablePositions != null)
return staticTargetablePositions;
var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
if (enabledTargetablePositionTraits.Any())
return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
if (enabledTargetablePositions.Any())
return enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this));
return new[] { CenterPosition };
}

View File

@@ -121,7 +121,7 @@ namespace OpenRA
mods[key] = mod;
}
internal void Register(Manifest mod, string launchPath, ModRegistration registration)
internal void Register(Manifest mod, string launchPath, IEnumerable<string> launchArgs, ModRegistration registration)
{
if (mod.Metadata.Hidden)
return;
@@ -133,7 +133,7 @@ namespace OpenRA
new MiniYamlNode("Version", mod.Metadata.Version),
new MiniYamlNode("Title", mod.Metadata.Title),
new MiniYamlNode("LaunchPath", launchPath),
new MiniYamlNode("LaunchArgs", "Game.Mod=" + mod.Id)
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
}));
using (var stream = mod.Package.GetStream("icon.png"))

View File

@@ -120,7 +120,14 @@ namespace OpenRA
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k)
where V : new()
{
return d.GetOrAdd(k, _ => new V());
return d.GetOrAdd(k, new V());
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, V v)
{
if (!d.TryGetValue(k, out var ret))
d.Add(k, ret = v);
return ret;
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> d, K k, Func<K, V> createFn)

View File

@@ -435,7 +435,7 @@ namespace OpenRA
ret.SetValue(GetValue(fieldName, fieldType.GetElementType(), parts[i].Trim(), field), i);
return ret;
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(HashSet<>))
else if (fieldType.IsGenericType && (fieldType.GetGenericTypeDefinition() == typeof(HashSet<>) || fieldType.GetGenericTypeDefinition() == typeof(List<>)))
{
var set = Activator.CreateInstance(fieldType);
if (value == null)

View File

@@ -94,7 +94,7 @@ namespace OpenRA
return ((Array)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
if (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(HashSet<>) || t.GetGenericTypeDefinition() == typeof(List<>)))
{
return ((System.Collections.IEnumerable)v).Cast<object>().Select(FormatValue).JoinWith(", ");
}

View File

@@ -63,7 +63,7 @@ namespace OpenRA.FileSystem
{
// Raw directories are the easiest and one of the most common cases, so try these first
var resolvedPath = Platform.ResolvePath(filename);
if (!filename.Contains("|") && Directory.Exists(resolvedPath))
if (!resolvedPath.Contains("|") && Directory.Exists(resolvedPath))
return new Folder(resolvedPath);
// Children of another package require special handling
@@ -295,7 +295,7 @@ namespace OpenRA.FileSystem
public static string ResolveAssemblyPath(string path, Manifest manifest, InstalledMods installedMods)
{
var explicitSplit = path.IndexOf('|');
if (explicitSplit > 0)
if (explicitSplit > 0 && !path.StartsWith("^"))
{
var parent = path.Substring(0, explicitSplit);
var filename = path.Substring(explicitSplit + 1);

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRA.FileSystem
{
@@ -32,10 +33,11 @@ namespace OpenRA.FileSystem
{
get
{
foreach (var filename in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
yield return Path.GetFileName(filename);
foreach (var filename in Directory.GetDirectories(path))
yield return Path.GetFileName(filename);
// Order may vary on different file systems and it matters for hashing.
return Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)
.Concat(Directory.GetDirectories(path))
.Select(Path.GetFileName)
.OrderBy(f => f);
}
}

View File

@@ -17,6 +17,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.Graphics;
@@ -72,10 +73,10 @@ namespace OpenRA
return om;
}
static string TimestampedFilename(bool includemilliseconds = false)
public static string TimestampedFilename(bool includemilliseconds = false, string extra = "")
{
var format = includemilliseconds ? "yyyy-MM-ddTHHmmssfffZ" : "yyyy-MM-ddTHHmmssZ";
return ModData.Manifest.Id + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
return ModData.Manifest.Id + extra + "-" + DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
}
static void JoinInner(OrderManager om)
@@ -170,11 +171,13 @@ namespace OpenRA
worldRenderer = new WorldRenderer(ModData, OrderManager.World);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
using (new PerfTimer("LoadComplete"))
OrderManager.World.LoadComplete(worldRenderer);
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
if (OrderManager.GameStarted)
@@ -189,6 +192,14 @@ namespace OpenRA
worldRenderer.RefreshPalette();
Cursor.SetCursor("default");
// 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.
// - Live objects are likely to live for the length of the game or longer,
// thus promoting them into a higher generation is not an issue.
// - We can remove any fragmentation in the LOH caused by temporary loading garbage.
// - A loading screen is visible, so a delay won't matter to the user.
// Much better to clean up now then to drop frames during gameplay for GC pauses.
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
}
@@ -251,18 +262,24 @@ namespace OpenRA
public static void InitializeSettings(Arguments args)
{
Settings = new Settings(Platform.ResolvePath(Path.Combine(Platform.SupportDirPrefix, "settings.yaml")), args);
Settings = new Settings(Path.Combine(Platform.SupportDir, "settings.yaml"), args);
}
public static RunStatus InitializeAndRun(string[] args)
{
Initialize(new Arguments(args));
// Proactively collect memory during loading to reduce peak memory.
GC.Collect();
return Run();
}
static void Initialize(Arguments args)
{
var engineDirArg = args.GetValue("Engine.EngineDir", null);
if (!string.IsNullOrEmpty(engineDirArg))
Platform.OverrideEngineDir(engineDirArg);
var supportDirArg = args.GetValue("Engine.SupportDir", null);
if (!string.IsNullOrEmpty(supportDirArg))
Platform.OverrideSupportDir(supportDirArg);
@@ -272,7 +289,7 @@ namespace OpenRA
// Load the engine version as early as possible so it can be written to exception logs
try
{
EngineVersion = File.ReadAllText(Platform.ResolvePath(Path.Combine(".", "VERSION"))).Trim();
EngineVersion = File.ReadAllText(Path.Combine(Platform.EngineDir, "VERSION")).Trim();
}
catch { }
@@ -280,6 +297,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
@@ -311,10 +329,17 @@ namespace OpenRA
Settings.Game.Platform = p;
try
{
var rendererPath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + p + ".dll"));
var assembly = Assembly.LoadFile(rendererPath);
var rendererPath = Path.Combine(Platform.BinDir, "OpenRA.Platforms." + p + ".dll");
#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.");
@@ -341,7 +366,7 @@ namespace OpenRA
var modSearchArg = args.GetValue("Engine.ModSearchPaths", null);
var modSearchPaths = modSearchArg != null ?
FieldLoader.GetValue<string[]>("Engine.ModsPath", modSearchArg) :
new[] { Path.Combine(".", "mods") };
new[] { Path.Combine(Platform.EngineDir, "mods") };
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
@@ -354,14 +379,24 @@ namespace OpenRA
if (modID != null && Mods.TryGetValue(modID, out _))
{
var launchPath = args.GetValue("Engine.LaunchPath", Assembly.GetEntryAssembly().Location);
var launchPath = args.GetValue("Engine.LaunchPath", null);
var launchArgs = new List<string>();
// Sanitize input from platform-specific launchers
// Process.Start requires paths to not be quoted, even if they contain spaces
if (launchPath.First() == '"' && launchPath.Last() == '"')
if (launchPath != null && launchPath.First() == '"' && launchPath.Last() == '"')
launchPath = launchPath.Substring(1, launchPath.Length - 2);
ExternalMods.Register(Mods[modID], launchPath, ModRegistration.User);
if (launchPath == null)
{
// When launching the assembly directly we must propagate the Engine.EngineDir argument if defined
// Platform-specific launchers are expected to manage this internally.
launchPath = Assembly.GetEntryAssembly().Location;
if (!string.IsNullOrEmpty(engineDirArg))
launchArgs.Add("Engine.EngineDir=\"" + engineDirArg + "\"");
}
ExternalMods.Register(Mods[modID], launchPath, launchArgs, ModRegistration.User);
if (ExternalMods.TryGetValue(ExternalMod.MakeKey(Mods[modID]), out var activeMod))
ExternalMods.ClearInvalidRegistrations(activeMod, ModRegistration.User);
@@ -410,7 +445,7 @@ namespace OpenRA
ModData = new ModData(Mods[mod], Mods, true);
LocalPlayerProfile = new LocalPlayerProfile(Platform.ResolvePath(Path.Combine("^", Settings.Game.AuthProfile)), ModData.Manifest.Get<PlayerDatabase>());
LocalPlayerProfile = new LocalPlayerProfile(Path.Combine(Platform.SupportDir, Settings.Game.AuthProfile), ModData.Manifest.Get<PlayerDatabase>());
if (!ModData.LoadScreen.BeforeLoad())
return;
@@ -526,7 +561,7 @@ namespace OpenRA
using (new PerfTimer("Renderer.SaveScreenshot"))
{
var mod = ModData.Manifest.Metadata;
var directory = Platform.ResolvePath(Platform.SupportDirPrefix, "Screenshots", ModData.Manifest.Id, mod.Version);
var directory = Path.Combine(Platform.SupportDir, "Screenshots", ModData.Manifest.Id, mod.Version);
Directory.CreateDirectory(directory);
var filename = TimestampedFilename(true);
@@ -907,9 +942,11 @@ namespace OpenRA
AdvertiseOnline = false
};
// Always connect to local games using the same loopback connection
// Exposing multiple endpoints introduces a race condition on the client's PlayerIndex (sometimes 0, sometimes 1)
// This would break the Restart button, which relies on the PlayerIndex always being the same for local servers
var endpoints = new List<IPEndPoint>
{
new IPEndPoint(IPAddress.IPv6Loopback, 0),
new IPEndPoint(IPAddress.Loopback, 0)
};
server = new Server.Server(endpoints, settings, ModData, ServerType.Local);

View File

@@ -35,6 +35,7 @@ namespace OpenRA
/// <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 IList<Player> Players { get; private set; }
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } }
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
@@ -118,11 +119,13 @@ namespace OpenRA
IsBot = runtimePlayer.IsBot,
FactionName = runtimePlayer.Faction.Name,
FactionId = runtimePlayer.Faction.InternalName,
DisplayFactionName = runtimePlayer.DisplayFaction.Name,
DisplayFactionId = runtimePlayer.DisplayFaction.InternalName,
Color = runtimePlayer.Color,
Team = client.Team,
SpawnPoint = runtimePlayer.SpawnPoint,
IsRandomFaction = runtimePlayer.Faction.InternalName != client.Faction,
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint,
IsRandomSpawnPoint = runtimePlayer.DisplaySpawnPoint == 0,
Fingerprint = client.Fingerprint
};
@@ -156,6 +159,10 @@ namespace OpenRA
public string FactionId;
public Color Color;
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
public string DisplayFactionName;
public string DisplayFactionId;
/// <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;
@@ -179,6 +186,9 @@ namespace OpenRA
/// <summary>The time when this player won or lost the game.</summary>
public DateTime OutcomeTimestampUtc;
/// <summary>The frame at which this player disconnected.</summary>
public int DisconnectFrame;
#endregion
}
}

View File

@@ -208,7 +208,7 @@ namespace OpenRA
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] :
new SequenceProvider(fileSystem, modData, ts, mapSequences);
new SequenceProvider(fileSystem, modData, tileSet, mapSequences);
var modelSequences = dr.ModelSequences;
if (mapModelSequences != null)

View File

@@ -163,7 +163,7 @@ namespace OpenRA.GameRules
}
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
public bool IsValidAgainst(Target target, World world, Actor firedBy)
public bool IsValidAgainst(in Target target, World world, Actor firedBy)
{
if (target.Type == TargetType.Actor)
return IsValidAgainst(target.Actor, firedBy);
@@ -220,20 +220,24 @@ namespace OpenRA.GameRules
}
/// <summary>Applies all the weapon's warheads to the target.</summary>
public void Impact(Target target, WarheadArgs args)
public void Impact(in Target target, WarheadArgs args)
{
var world = args.SourceActor.World;
foreach (var warhead in Warheads)
{
if (warhead.Delay > 0)
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
{
// Lambdas can't use 'in' variables, so capture a copy for later
var delayedTarget = target;
world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, delayedTarget, args)));
}
else
warhead.DoImpact(target, args);
}
}
/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
public void Impact(Target target, Actor firedBy)
public void Impact(in Target target, Actor firedBy)
{
// The impact will happen immediately at target.CenterPosition.
var args = new WarheadArgs

View File

@@ -236,15 +236,14 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < width; i++)
{
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
var c = palette[frame.Data[j * width + i]];
var rgba = palette[frame.Data[j * width + i]];
var k = 4 * (j * width + i);
// Convert RGBA to BGRA
data[k] = bytes[2];
data[k + 1] = bytes[1];
data[k + 2] = bytes[0];
data[k + 3] = bytes[3];
data[k] = (byte)(rgba >> 16);
data[k + 1] = (byte)(rgba >> 8);
data[k + 2] = (byte)(rgba >> 0);
data[k + 3] = (byte)(rgba >> 24);
}
}

View File

@@ -31,8 +31,10 @@ namespace OpenRA.Graphics
Palette = palette;
Name = name;
Frames = cache[cursorSrc].Skip(Start).ToArray();
if ((d.ContainsKey("Length") && d["Length"].Value == "*") || (d.ContainsKey("End") && d["End"].Value == "*"))
Length = Frames.Length - Start;
Length = Frames.Length;
else if (d.ContainsKey("Length"))
Length = Exts.ParseIntegerInvariant(d["Length"].Value);
else if (d.ContainsKey("End"))
@@ -40,10 +42,7 @@ namespace OpenRA.Graphics
else
Length = 1;
Frames = cache[cursorSrc]
.Skip(Start)
.Take(Length)
.ToArray();
Frames = Frames.Take(Length).ToArray();
if (d.ContainsKey("X"))
{

View File

@@ -79,8 +79,8 @@ namespace OpenRA.Graphics
}
public ModelRenderProxy RenderAsync(
WorldRenderer wr, IEnumerable<ModelAnimation> models, WRot camera, float scale,
float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
WorldRenderer wr, IEnumerable<ModelAnimation> models, in WRot camera, float scale,
float[] groundNormal, in WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
{
if (!isInFrame)

View File

@@ -17,6 +17,8 @@ namespace OpenRA
{
public enum GLProfile
{
Automatic,
ANGLE,
Modern,
Embedded,
Legacy
@@ -24,7 +26,7 @@ namespace OpenRA
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile, bool enableLegacyGL);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -101,8 +103,7 @@ namespace OpenRA
{
void Bind();
void SetData(T[] vertices, int length);
void SetData(T[] vertices, int start, int length);
void SetData(IntPtr data, int start, int length);
void SetData(T[] vertices, int offset, int start, int length);
}
public interface IShader

View File

@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
public interface ITintableRenderable
{
IRenderable WithTint(float3 newTint);
IRenderable WithTint(in float3 newTint);
}
public interface IFinalizedRenderable

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawLine(float3 start, float3 end, float width, Color startColor, Color endColor)
public void DrawLine(in float3 start, in float3 end, float width, Color startColor, Color endColor)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float3(-delta.Y, delta.X, delta.Z);
@@ -55,7 +55,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void DrawLine(float3 start, float3 end, float width, Color color)
public void DrawLine(in float3 start, in float3 end, float width, Color color)
{
var delta = (end - start) / (end - start).XY.Length;
var corner = width / 2 * new float2(-delta.Y, delta.X);
@@ -80,7 +80,7 @@ namespace OpenRA.Graphics
/// Will behave badly if the lines are parallel.
/// Z position is the average of a and b (ignores actual intersection point if it exists)
/// </summary>
float3 IntersectionOf(float3 a, float3 da, float3 b, float3 db)
float3 IntersectionOf(in float3 a, in float3 da, in float3 b, in float3 db)
{
var crossA = a.X * (a.Y + da.Y) - a.Y * (a.X + da.X);
var crossB = b.X * (b.Y + db.Y) - b.Y * (b.X + db.X);
@@ -193,14 +193,14 @@ namespace OpenRA.Graphics
DrawConnectedLine(vertices.Select(v => new float3(v, 0)).ToArray(), width, color, true);
}
public void DrawRect(float3 tl, float3 br, float width, Color color)
public void DrawRect(in float3 tl, in float3 br, float width, Color color)
{
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);
}
public void FillTriangle(float3 a, float3 b, float3 c, Color color)
public void FillTriangle(in float3 a, in float3 b, in float3 c, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -214,14 +214,14 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void FillRect(float3 tl, float3 br, Color color)
public void FillRect(in float3 tl, in float3 br, Color color)
{
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);
}
public void FillRect(float3 a, float3 b, float3 c, float3 d, Color color)
public void FillRect(in float3 a, in float3 b, in float3 c, in float3 d, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -238,7 +238,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
public void FillRect(float3 a, float3 b, float3 c, 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)
{
vertices[0] = VertexWithColor(a + Offset, topLeftColor);
vertices[1] = VertexWithColor(b + Offset, topRightColor);
@@ -250,7 +250,7 @@ namespace OpenRA.Graphics
parent.DrawRGBAVertices(vertices);
}
static Vertex VertexWithColor(float3 xyz, Color color)
static Vertex VertexWithColor(in float3 xyz, Color color)
{
color = Util.PremultiplyAlpha(color);
var cr = color.R / 255.0f;
@@ -261,7 +261,7 @@ namespace OpenRA.Graphics
return new Vertex(xyz, cr, cg, cb, ca, 0, 0);
}
public void FillEllipse(float3 tl, float3 br, Color color, int vertices = 32)
public void FillEllipse(in float3 tl, in float3 br, Color color, int vertices = 32)
{
// TODO: Create an ellipse polygon instead
var a = (br.X - tl.X) / 2;

View File

@@ -22,7 +22,7 @@ namespace OpenRA.Graphics
this.parent = parent;
}
public void DrawSprite(Sprite s, float3 location, float3 size)
public void DrawSprite(Sprite s, in float3 location, in float3 size)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -30,7 +30,7 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, location, 0, size);
}
public void DrawSprite(Sprite s, float3 location)
public void DrawSprite(Sprite s, in float3 location)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -38,7 +38,7 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, location, 0, s.Size);
}
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -46,7 +46,7 @@ namespace OpenRA.Graphics
parent.DrawSprite(s, a, b, c, d);
}
public void DrawSpriteWithTint(Sprite s, float3 location, float3 size, float3 tint)
public void DrawSpriteWithTint(Sprite s, in float3 location, in float3 size, in float3 tint)
{
if (s.Channel != TextureChannel.RGBA)
throw new InvalidOperationException("DrawRGBASprite requires a RGBA sprite.");
@@ -54,7 +54,7 @@ namespace OpenRA.Graphics
parent.DrawSpriteWithTint(s, location, 0, size, tint);
}
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 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.");

View File

@@ -41,21 +41,20 @@ namespace OpenRA.Graphics
public interface ISpriteSequenceLoader
{
Action<string> OnMissingSpriteError { get; set; }
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node);
IReadOnlyDictionary<string, ISpriteSequence> ParseSequences(ModData modData, string tileSet, SpriteCache cache, MiniYamlNode node);
}
public class SequenceProvider : IDisposable
{
readonly ModData modData;
readonly TileSet tileSet;
readonly string tileSet;
readonly Lazy<Sequences> sequences;
readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, MiniYaml additionalSequences)
public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, string tileSet, MiniYaml additionalSequences)
{
this.modData = modData;
this.tileSet = tileSet;
@@ -79,6 +78,8 @@ namespace OpenRA.Graphics
return seq;
}
public IEnumerable<string> Images { get { return sequences.Value.Keys; } }
public bool HasSequence(string unitName)
{
return sequences.Value.ContainsKey(unitName);
@@ -104,10 +105,11 @@ namespace OpenRA.Graphics
{
var nodes = MiniYaml.Load(fileSystem, modData.Manifest.Sequences, additionalSequences);
var items = new Dictionary<string, UnitSequences>();
foreach (var n in nodes)
foreach (var node in nodes)
{
// Work around the loop closure issue in older versions of C#
var node = n;
// Nodes starting with ^ are inheritable but never loaded directly
if (node.Key.StartsWith(ActorInfo.AbstractActorPrefix, StringComparison.Ordinal))
continue;
var key = node.Value.ToLines(node.Key).JoinWith("|");

View File

@@ -76,7 +76,7 @@ namespace OpenRA.Graphics
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, float3 spriteOffset)
public Sprite Add(byte[] src, Size size, float zRamp, in float3 spriteOffset)
{
// Don't bother allocating empty sprites
if (size.Width == 0 || size.Height == 0)
@@ -115,7 +115,7 @@ namespace OpenRA.Graphics
}
public Sprite Allocate(Size imageSize, float scale = 1f) { return Allocate(imageSize, 0, float3.Zero, scale); }
public Sprite Allocate(Size imageSize, float zRamp, float3 spriteOffset, float scale = 1f)
public Sprite Allocate(Size imageSize, float zRamp, in float3 spriteOffset, float scale = 1f)
{
if (imageSize.Width + p.X + margin > current.Size.Width)
{

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Graphics
public Sprite(Sheet sheet, Rectangle bounds, TextureChannel channel, float scale = 1)
: this(sheet, bounds, 0, float2.Zero, channel, BlendMode.Alpha, scale) { }
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
public Sprite(Sheet sheet, Rectangle bounds, float zRamp, in float3 offset, TextureChannel channel, BlendMode blendMode = BlendMode.Alpha, float scale = 1f)
{
Sheet = sheet;
Bounds = bounds;

View File

@@ -239,7 +239,15 @@ namespace OpenRA.Graphics
return new int2(0, size);
var lines = text.Split('\n');
return new int2((int)Math.Ceiling(lines.Max(lineWidth)), lines.Length * size);
return new int2((int)Math.Ceiling(MaxLineWidth(lines, lineWidth)), lines.Length * size);
}
static float MaxLineWidth(string[] lines, Func<string, float> lineWidth)
{
var maxWidth = 0f;
foreach (var line in lines)
maxWidth = Math.Max(maxWidth, lineWidth(line));
return maxWidth;
}
GlyphInfo CreateGlyph(char c)

View File

@@ -58,7 +58,7 @@ namespace OpenRA.Graphics
public IRenderable OffsetBy(WVec vec) { return new SpriteRenderable(sprite, pos + vec, offset, zOffset, palette, scale, tint, isDecoration, ignoreWorldTint); }
public IRenderable AsDecoration() { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, tint, true, ignoreWorldTint); }
public IRenderable WithTint(float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
public IRenderable WithTint(in float3 newTint) { return new SpriteRenderable(sprite, pos, offset, zOffset, palette, scale, newTint, isDecoration, ignoreWorldTint); }
float3 ScreenPosition(WorldRenderer wr)
{

View File

@@ -10,17 +10,21 @@
#endregion
using System;
using System.Linq;
using OpenRA.Primitives;
namespace OpenRA.Graphics
{
public class SpriteRenderer : Renderer.IBatchRenderer
{
const int SheetCount = 7;
static readonly string[] SheetIndexToTextureName = Exts.MakeArray(SheetCount, i => "Texture{0}".F(i));
readonly Renderer renderer;
readonly IShader shader;
readonly Vertex[] vertices;
readonly Sheet[] sheets = new Sheet[7];
readonly Sheet[] sheets = new Sheet[SheetCount];
BlendMode currentBlend = BlendMode.Alpha;
int nv = 0;
@@ -39,7 +43,7 @@ namespace OpenRA.Graphics
{
for (var i = 0; i < ns; i++)
{
shader.SetTexture("Texture{0}".F(i), sheets[i].GetTexture());
shader.SetTexture(SheetIndexToTextureName[i], sheets[i].GetTexture());
sheets[i] = null;
}
@@ -104,43 +108,43 @@ namespace OpenRA.Graphics
return new int2(sheetIndex, secondarySheetIndex);
}
internal void DrawSprite(Sprite s, float3 location, float paletteTextureIndex, float3 size)
internal void DrawSprite(Sprite s, in float3 location, float paletteTextureIndex, in float3 size)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, float3.Ones);
nv += 6;
}
public void DrawSprite(Sprite s, float3 location, PaletteReference pal)
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal)
{
DrawSprite(s, location, pal.TextureIndex, s.Size);
}
public void DrawSprite(Sprite s, float3 location, PaletteReference pal, float3 size)
public void DrawSprite(Sprite s, in float3 location, PaletteReference pal, float3 size)
{
DrawSprite(s, location, pal.TextureIndex, size);
}
public void DrawSprite(Sprite s, float3 a, float3 b, float3 c, float3 d)
public void DrawSprite(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, float3.Ones, nv);
nv += 6;
}
internal void DrawSpriteWithTint(Sprite s, float3 location, float paletteTextureIndex, float3 size, float3 tint)
internal void DrawSpriteWithTint(Sprite s, in float3 location, float paletteTextureIndex, in float3 size, in float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, location + s.FractionalOffset * size, s, samplers, paletteTextureIndex, nv, size, tint);
nv += 6;
}
public void DrawSpriteWithTint(Sprite s, float3 location, PaletteReference pal, float3 size, float3 tint)
public void DrawSpriteWithTint(Sprite s, in float3 location, PaletteReference pal, in float3 size, in float3 tint)
{
DrawSpriteWithTint(s, location, pal.TextureIndex, size, tint);
}
public void DrawSpriteWithTint(Sprite s, float3 a, float3 b, float3 c, float3 d, float3 tint)
public void DrawSpriteWithTint(Sprite s, in float3 a, in float3 b, in float3 c, in float3 d, in float3 tint)
{
var samplers = SetRenderStateForSprite(s);
Util.FastCreateQuad(vertices, a, b, c, d, s, samplers, 0, tint, nv);

View File

@@ -137,7 +137,7 @@ namespace OpenRA.Graphics
dirtyRows.Add(uv.V);
}
public void Update(MPos uv, Sprite sprite, float3 pos, bool ignoreTint)
public void Update(MPos uv, Sprite sprite, in float3 pos, bool ignoreTint)
{
if (sprite != null)
{
@@ -183,15 +183,7 @@ namespace OpenRA.Graphics
continue;
var rowOffset = rowStride * row;
unsafe
{
// The compiler / language spec won't let us calculate a pointer to
// an offset inside a generic array T[], and so we are forced to
// calculate the start-of-row pointer here to pass in to SetData.
fixed (Vertex* vPtr = &vertices[0])
vertexBuffer.SetData((IntPtr)(vPtr + rowOffset), rowOffset, rowStride);
}
vertexBuffer.SetData(vertices, rowOffset, rowOffset, rowStride);
}
Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer(

View File

@@ -40,7 +40,7 @@ namespace OpenRA.Graphics
readonly MersenneTwister random;
TileSet tileset;
public Theater(TileSet tileset)
public Theater(TileSet tileset, Action<uint, string> onMissingImage = null)
{
this.tileset = tileset;
var allocated = false;
@@ -63,9 +63,31 @@ namespace OpenRA.Graphics
foreach (var i in t.Value.Images)
{
var allFrames = frameCache[i];
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 : Enumerable.Range(0, frameCount);
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];
@@ -82,7 +104,7 @@ namespace OpenRA.Graphics
if (sheetBuilder == null)
sheetBuilder = new SheetBuilder(SheetBuilder.FrameTypeToSheetType(f.Type), allocate);
else if (type != sheetBuilder.Type)
throw new InvalidDataException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA.");
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);
@@ -107,6 +129,9 @@ namespace OpenRA.Graphics
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));
}
@@ -116,6 +141,11 @@ namespace OpenRA.Graphics
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))
@@ -138,10 +168,7 @@ namespace OpenRA.Graphics
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)
if (!tileset.TryGetTileInfo(tile, out var tileInfo))
continue;
var sprite = TileSprite(tile);

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Graphics
// yes, our channel order is nuts.
static readonly int[] ChannelMasks = { 2, 1, 0, 3 };
public static void FastCreateQuad(Vertex[] vertices, float3 o, Sprite r, int2 samplers, float paletteTextureIndex, int nv, float3 size, 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)
{
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);
@@ -29,9 +29,9 @@ namespace OpenRA.Graphics
}
public static void FastCreateQuad(Vertex[] vertices,
float3 a, float3 b, float3 c, float3 d,
in float3 a, in float3 b, in float3 c, in float3 d,
Sprite r, int2 samplers, float paletteTextureIndex,
float3 tint, int nv)
in float3 tint, int nv)
{
float sl = 0;
float st = 0;

View File

@@ -28,13 +28,13 @@ namespace OpenRA.Graphics
// Color tint
public readonly float R, G, B;
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c)
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) { }
public Vertex(float3 xyz, float s, float t, float u, float v, float p, float c, float3 tint)
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(float x, float y, float z, float s, float t, float u, float v, float p, float c, float3 tint)
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, float r, float g, float b)

View File

@@ -282,7 +282,7 @@ namespace OpenRA.Graphics
// Try and find the closest cell
if (candidates.Count > 0)
{
return candidates.OrderBy(uv =>
return candidates.MinBy(uv =>
{
var p = map.CenterOfCell(uv.ToCPos(map.Grid.Type));
var s = worldRenderer.ScreenPxPosition(p);
@@ -290,7 +290,7 @@ namespace OpenRA.Graphics
var dy = Math.Abs(s.Y - world.Y);
return dx * dx + dy * dy;
}).First().ToCPos(map);
}).ToCPos(map);
}
// Something is very wrong, but lets return something that isn't completely bogus and hope the caller can recover
@@ -314,7 +314,7 @@ namespace OpenRA.Graphics
public int2 ViewToWorldPx(int2 view) { return (graphicSettings.UIScale / Zoom * view.ToFloat2()).ToInt2() + TopLeft; }
public int2 WorldToViewPx(int2 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).ToFloat2()).ToInt2(); }
public int2 WorldToViewPx(float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
public int2 WorldToViewPx(in float3 world) { return ((Zoom / graphicSettings.UIScale) * (world - TopLeft).XY).ToInt2(); }
public void Center(IEnumerable<Actor> actors)
{

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Graphics
{
public sealed class WorldRenderer : IDisposable
{
public static readonly Func<IRenderable, int> RenderableScreenZPositionComparisonKey =
public static readonly Func<IRenderable, int> RenderableZPositionComparisonKey =
r => ZPosition(r.Pos, r.ZOffset);
public readonly Size TileSize;
@@ -133,7 +133,8 @@ namespace OpenRA.Graphics
foreach (var e in World.ScreenMap.RenderableEffectsInBox(Viewport.TopLeft, Viewport.BottomRight))
renderablesBuffer.AddRange(e.Render(this));
foreach (var renderable in renderablesBuffer.OrderBy(RenderableScreenZPositionComparisonKey))
// Renderables must be ordered using a stable sorting algorithm to avoid flickering artefacts
foreach (var renderable in renderablesBuffer.OrderBy(RenderableZPositionComparisonKey))
preparedRenderables.Add(renderable.PrepareRender(this));
// PERF: Reuse collection to avoid allocations.

View File

@@ -73,7 +73,7 @@ namespace OpenRA
readonly string[] reservedModuleNames =
{
"Metadata", "Folders", "MapFolders", "Packages", "Rules",
"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",
@@ -89,7 +89,25 @@ namespace OpenRA
{
Id = modId;
Package = package;
yaml = new MiniYaml(null, MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml")).ToDictionary();
var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml");
for (var i = nodes.Count - 1; i >= 0; i--)
{
if (nodes[i].Key != "Include")
continue;
// Replace `Includes: filename.yaml` with the contents of filename.yaml
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));
nodes.RemoveAt(i);
nodes.InsertRange(i, MiniYaml.FromStream(contents, filename));
}
// Merge inherited overrides
yaml = new MiniYaml(null, MiniYaml.Merge(new[] { nodes })).ToDictionary();
Metadata = FieldLoader.Load<ModMetadata>(yaml["Metadata"]);

View File

@@ -206,6 +206,8 @@ namespace OpenRA
public readonly MiniYaml NotificationDefinitions;
public readonly MiniYaml TranslationDefinitions;
public readonly Dictionary<CPos, TerrainTile> ReplacedInvalidTerrainTiles = new Dictionary<CPos, TerrainTile>();
// Generated data
public readonly MapGrid Grid;
public IReadOnlyPackage Package { get; private set; }
@@ -287,7 +289,6 @@ namespace OpenRA
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";
@@ -310,7 +311,7 @@ namespace OpenRA
Tiles.CellEntryChanged += UpdateRamp;
}
Tiles.Clear(tileRef);
Tiles.Clear(tileset.DefaultTerrainTile);
PostInit();
}
@@ -331,6 +332,9 @@ namespace OpenRA
throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, 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>();
@@ -427,12 +431,18 @@ namespace OpenRA
foreach (var uv in AllCells.MapCoords)
CustomTerrain[uv] = byte.MaxValue;
// Cache initial ramp state
// Replace invalid tiles and cache ramp state
var tileset = Rules.TileSet;
foreach (var uv in AllCells)
foreach (var uv in AllCells.MapCoords)
{
var tile = tileset.GetTileInfo(Tiles[uv]);
Ramp[uv] = tile != null ? tile.RampType : (byte)0;
if (!tileset.TryGetTileInfo(Tiles[uv], out var info))
{
ReplacedInvalidTerrainTiles[uv.ToCPos(this)] = Tiles[uv];
Tiles[uv] = tileset.DefaultTerrainTile;
info = tileset.GetTileInfo(tileset.DefaultTerrainTile);
}
Ramp[uv] = info.RampType;
}
AllEdgeCells = UpdateEdgeCells();
@@ -440,8 +450,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.TileSet.GetTileInfo(Tiles[cell]).RampType;
}
void InitializeCellProjection()
@@ -667,32 +676,26 @@ namespace OpenRA
Color left, right;
var tileset = Rules.TileSet;
var type = tileset.GetTileInfo(Tiles[uv]);
if (type != null)
if (type.MinColor != type.MaxColor)
{
if (type.MinColor != type.MaxColor)
{
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
}
else
left = right = type.MinColor;
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
{
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
}
left = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
right = Exts.ColorLerp(Game.CosmeticRandom.NextFloat(), type.MinColor, type.MaxColor);
}
else
left = right = Color.Black;
left = right = type.MinColor;
if (tileset.MinHeightColorBrightness != 1.0f || tileset.MaxHeightColorBrightness != 1.0f)
{
var scale = float2.Lerp(tileset.MinHeightColorBrightness, tileset.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight);
left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255));
right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255));
}
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)>();
@@ -712,76 +715,73 @@ namespace OpenRA
foreach (var worldimpsi in worldimpsis)
worldimpsi.PopulateMapPreviewSignatureCells(this, worldActorInfo, null, positions);
using (var stream = new MemoryStream())
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++)
{
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++)
for (var x = 0; x < width; x++)
{
for (var x = 0; x < width; x++)
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)
{
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, bitmapWidth, height);
return png.Save();
}
public bool Contains(CPos cell)
@@ -1282,16 +1282,12 @@ namespace OpenRA
throw new ArgumentOutOfRangeException("maxRange",
"The requested range ({0}) cannot exceed the value of MaximumTileSearchRange ({1})".F(maxRange, Grid.MaximumTileSearchRange));
Func<CPos, bool> valid = Contains;
if (allowOutsideBounds)
valid = Tiles.Contains;
for (var i = minRange; i <= maxRange; i++)
{
foreach (var offset in Grid.TilesByDistance[i])
{
var t = offset + center;
if (valid(t))
if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t))
yield return t;
}
}

View File

@@ -38,6 +38,8 @@ namespace OpenRA
object syncRoot = new object();
Queue<MapPreview> generateMinimap = new Queue<MapPreview>();
public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();
public MapCache(ModData modData)
{
this.modData = modData;
@@ -70,13 +72,10 @@ namespace OpenRA
try
{
// HACK: If the path is inside the the support directory then we may need to create it
if (Platform.IsPathRelativeToSupportDirectory(name))
{
// Assume that the path is a directory if there is not an existing file with the same name
var resolved = Platform.ResolvePath(name);
if (!File.Exists(resolved))
Directory.CreateDirectory(resolved);
}
// Assume that the path is a directory if there is not an existing file with the same name
var resolved = Platform.ResolvePath(name);
if (resolved.StartsWith(Platform.SupportDir) && !File.Exists(resolved))
Directory.CreateDirectory(resolved);
package = modData.ModFiles.OpenPackage(name);
}
@@ -142,12 +141,9 @@ namespace OpenRA
name = name.Substring(1);
// Don't try to open the map directory in the support directory if it doesn't exist
if (Platform.IsPathRelativeToSupportDirectory(name))
{
var resolved = Platform.ResolvePath(name);
if (!Directory.Exists(resolved) || !File.Exists(resolved))
continue;
}
var resolved = Platform.ResolvePath(name);
if (resolved.StartsWith(Platform.SupportDir) && (!Directory.Exists(resolved) || !File.Exists(resolved)))
continue;
using (var package = (IReadWritePackage)modData.ModFiles.OpenPackage(name))
{

View File

@@ -223,7 +223,7 @@ namespace OpenRA
if (yamlStream == null)
throw new FileNotFoundException("Required file map.yaml not present in this map");
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml")).ToDictionary();
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary();
}
Package = p;

View File

@@ -32,7 +32,19 @@ namespace OpenRA
public bool LockColor = false;
public Color Color = Game.ModData.Manifest.Get<DefaultPlayer>().Color;
/// <summary>
/// Sets the "Home" location, which can be used by traits and scripts to e.g. set the initial camera
/// location or choose the map edge for reinforcements.
/// This will usually be overridden for client (lobby slot) players with a location based on the Spawn index
/// </summary>
public CPos HomeLocation = CPos.Zero;
public bool LockSpawn = false;
/// <summary>
/// Sets the initial spawn point index that is used to override the "Home" location for client (lobby slot) players.
/// Map players always ignore this and use HomeLocation directly.
/// </summary>
public int Spawn = 0;
public bool LockTeam = false;

View File

@@ -22,11 +22,24 @@ namespace OpenRA
: base(gridType, size) { }
// Resolve an array index from map coordinates.
int Index(PPos uv)
public int Index(PPos uv)
{
return uv.V * Size.Width + uv.U;
}
public T this[int index]
{
get
{
return entries[index];
}
set
{
entries[index] = value;
}
}
/// <summary>Gets or sets the layer contents using projected map coordinates.</summary>
public T this[PPos uv]
{

View File

@@ -23,6 +23,8 @@ namespace OpenRA
}
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
public override string ToString() { return Type + "," + Index; }
}
public struct ResourceTile

View File

@@ -54,13 +54,6 @@ namespace OpenRA
readonly TerrainTileInfo[] tileInfo;
public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles)
{
Id = id;
Images = images;
Size = size;
}
public TerrainTemplateInfo(TileSet tileSet, MiniYaml my)
{
FieldLoader.Load(this, my);
@@ -72,8 +65,11 @@ namespace OpenRA
tileInfo = new TerrainTileInfo[Size.X * Size.Y];
foreach (var node in nodes)
{
if (!int.TryParse(node.Key, out var key) || key < 0 || key >= tileInfo.Length)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
if (!int.TryParse(node.Key, out var key))
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
if (key < 0 || key >= tileInfo.Length)
throw new YamlException("Tileset `{0}` template `{1}` references frame {2}, but only [0..{3}] are valid for a {4}x{5} Size template.".F(tileSet.Id, Id, key, tileInfo.Length - 1, Size.X, Size.Y));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
}
@@ -85,8 +81,11 @@ namespace OpenRA
var i = 0;
foreach (var node in nodes)
{
if (!int.TryParse(node.Key, out var key) || key != i++)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
if (!int.TryParse(node.Key, out var key))
throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(tileSet.Id, Id, node.Key));
if (key != i++)
throw new YamlException("Tileset `{0}` template `{1}` is missing a definition for frame {2}.".F(tileSet.Id, Id, i - 1));
tileInfo[key] = LoadTileInfo(tileSet, node.Value);
}
@@ -162,14 +161,14 @@ namespace OpenRA
.ToArray();
if (TerrainInfo.Length >= byte.MaxValue)
throw new InvalidDataException("Too many terrain types.");
throw new YamlException("Too many terrain types.");
for (byte i = 0; i < TerrainInfo.Length; i++)
{
var tt = TerrainInfo[i].Type;
if (terrainIndexByType.ContainsKey(tt))
throw new InvalidDataException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
throw new YamlException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
terrainIndexByType.Add(tt, i);
}
@@ -222,25 +221,30 @@ namespace OpenRA
public byte GetTerrainIndex(TerrainTile r)
{
if (!Templates.TryGetValue(r.Type, out var tpl))
return defaultWalkableTerrainIndex;
if (tpl.Contains(r.Index))
{
var tile = tpl[r.Index];
if (tile != null && tile.TerrainType != byte.MaxValue)
return tile.TerrainType;
}
var tile = Templates[r.Type][r.Index];
if (tile.TerrainType != byte.MaxValue)
return tile.TerrainType;
return defaultWalkableTerrainIndex;
}
public TerrainTileInfo GetTileInfo(TerrainTile r)
{
if (!Templates.TryGetValue(r.Type, out var tpl))
return null;
return tpl.Contains(r.Index) ? tpl[r.Index] : null;
return Templates[r.Type][r.Index];
}
public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info)
{
if (!Templates.TryGetValue(r.Type, out var tpl) || !tpl.Contains(r.Index))
{
info = null;
return false;
}
info = tpl[r.Index];
return info != null;
}
public TerrainTile DefaultTerrainTile { get { return new TerrainTile(Templates.First().Key, 0); } }
}
}

View File

@@ -99,7 +99,10 @@ namespace OpenRA
public MiniYaml Clone()
{
return new MiniYaml(Value, Nodes.Select(n => n.Clone()).ToList());
var clonedNodes = new MiniYamlNodes(Nodes.Count);
foreach (var node in Nodes)
clonedNodes.Add(node.Clone());
return new MiniYaml(Value, clonedNodes);
}
public Dictionary<string, MiniYaml> ToDictionary()
@@ -148,8 +151,11 @@ namespace OpenRA
return nd.ContainsKey(s) ? nd[s].Nodes : new List<MiniYamlNode>();
}
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace)
static List<MiniYamlNode> FromLines(IEnumerable<string> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
{
if (stringPool == null)
stringPool = new Dictionary<string, string>();
var levels = new List<List<MiniYamlNode>>();
levels.Add(new List<MiniYamlNode>());
@@ -263,6 +269,10 @@ namespace OpenRA
if (key != null || !discardCommentsAndWhitespace)
{
key = key == null ? null : stringPool.GetOrAdd(key, key);
value = value == null ? null : stringPool.GetOrAdd(value, value);
comment = comment == null ? null : stringPool.GetOrAdd(comment, comment);
var nodes = new List<MiniYamlNode>();
levels[level].Add(new MiniYamlNode(key, value, comment, nodes, location));
@@ -270,23 +280,33 @@ namespace OpenRA
}
}
foreach (var nodes in levels)
nodes.TrimExcess();
return levels[0];
}
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true)
public static List<MiniYamlNode> FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
{
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace);
return FromLines(File.ReadAllLines(path), path, discardCommentsAndWhitespace, stringPool);
}
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
public static List<MiniYamlNode> FromStream(Stream s, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
{
IEnumerable<string> Lines(StreamReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
yield return line;
}
using (var reader = new StreamReader(s))
return FromString(reader.ReadToEnd(), fileName, discardCommentsAndWhitespace);
return FromLines(Lines(reader), fileName, discardCommentsAndWhitespace, stringPool);
}
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true)
public static List<MiniYamlNode> FromString(string text, string fileName = "<no filename available>", bool discardCommentsAndWhitespace = true, Dictionary<string, string> stringPool = null)
{
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace);
return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), fileName, discardCommentsAndWhitespace, stringPool);
}
public static List<MiniYamlNode> Merge(IEnumerable<List<MiniYamlNode>> sources)
@@ -330,7 +350,7 @@ namespace OpenRA
static List<MiniYamlNode> ResolveInherits(string key, MiniYaml node, Dictionary<string, MiniYaml> tree, Dictionary<string, MiniYamlNode.SourceLocation> inherited)
{
var resolved = new List<MiniYamlNode>();
var resolved = new List<MiniYamlNode>(node.Nodes.Count);
// Inheritance is tracked from parent->child, but not from child->parentsiblings.
inherited = new Dictionary<string, MiniYamlNode.SourceLocation>(inherited);
@@ -361,6 +381,7 @@ namespace OpenRA
MergeIntoResolved(n, resolved, tree, inherited);
}
resolved.TrimExcess();
return resolved;
}
@@ -397,6 +418,7 @@ namespace OpenRA
}
}
ret.TrimExcess();
return ret;
}
@@ -437,6 +459,7 @@ namespace OpenRA
ret.Add(merged);
}
ret.TrimExcess();
return ret;
}

View File

@@ -79,7 +79,6 @@ namespace OpenRA
throw new InvalidOperationException("Unable to find a sequence loader for type '{0}'.".F(sequenceFormat.Type));
SpriteSequenceLoader = (ISpriteSequenceLoader)sequenceCtor.Invoke(new[] { this });
SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s);
var modelFormat = Manifest.Get<ModelSequenceFormat>();
var modelLoader = ObjectCreator.FindType(modelFormat.Type + "Loader");
@@ -108,7 +107,7 @@ namespace OpenRA
defaultSequences = Exts.Lazy(() =>
{
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Value, null));
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Key, null));
return (IReadOnlyDictionary<string, SequenceProvider>)(new ReadOnlyDictionary<string, SequenceProvider>(items));
});

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Network
if (lastClientFrame == -1)
lastClientFrame = framePackets
.Where(x => x.Value.ContainsKey(clientId))
.Select(x => x.Key).OrderBy(x => x).LastOrDefault();
.Select(x => x.Key).MaxByOrDefault(x => x);
clientQuitTimes[clientId] = lastClientFrame;
}

View File

@@ -117,7 +117,7 @@ namespace OpenRA.Network
LastOrdersFrame = rs.ReadInt32();
LastSyncFrame = rs.ReadInt32();
lastSyncPacket = rs.ReadBytes(5);
lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength);
var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength));
GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value);
@@ -190,6 +190,12 @@ namespace OpenRA.Network
// Sync packet - we only care about the last value
if (data.Length > 0 && data[0] == (byte)OrderType.SyncHash && frame > LastSyncFrame)
{
if (data.Length != Order.SyncHashOrderLength)
{
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(data.Length, Order.SyncHashOrderLength));
return;
}
LastSyncFrame = frame;
lastSyncPacket = data;
}
@@ -282,7 +288,7 @@ namespace OpenRA.Network
file.Write(BitConverter.GetBytes(MetadataMarker), 0, 4);
file.Write(BitConverter.GetBytes(LastOrdersFrame), 0, 4);
file.Write(BitConverter.GetBytes(LastSyncFrame), 0, 4);
file.Write(lastSyncPacket, 0, 5);
file.Write(lastSyncPacket, 0, Order.SyncHashOrderLength);
var globalSettingsNodes = new List<MiniYamlNode>() { GlobalSettings.Serialize() };
file.WriteString(Encoding.UTF8, globalSettingsNodes.WriteToString());

View File

@@ -56,7 +56,7 @@ namespace OpenRA.Network
"Mod", "Version", "ModTitle", "ModWebsite", "ModIcon32",
// Current server state
"Map", "State", "MaxPlayers", "Protected", "Authentication"
"Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints"
};
public const int ProtocolVersion = 2;
@@ -132,6 +132,9 @@ namespace OpenRA.Network
[FieldLoader.LoadUsing("LoadClients")]
public readonly GameClient[] Clients;
/// <summary>The list of spawnpoints that are disabled for this game</summary>
public readonly int[] DisabledSpawnPoints = { };
public string ModLabel { get { return "{0} ({1})".F(ModTitle, Version); } }
static object LoadClients(MiniYaml yaml)
@@ -226,6 +229,7 @@ namespace OpenRA.Network
Protected = !string.IsNullOrEmpty(server.Settings.Password);
Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Any();
Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray();
DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToArray() ?? Array.Empty<int>();
}
public string ToPOSTData(bool lanGame)

View File

@@ -49,10 +49,13 @@ namespace OpenRA
public sealed class Order
{
// Length of orders with type OrderType.SyncHash
public const int SyncHashOrderLength = 13;
public readonly string OrderString;
public readonly Actor Subject;
public readonly bool Queued;
public readonly Target Target;
public ref readonly Target Target => ref target;
public readonly Actor[] GroupedActors;
public string TargetString;
@@ -63,15 +66,18 @@ namespace OpenRA
public OrderType Type = OrderType.Fields;
public bool SuppressVisualFeedback;
public Target VisualFeedbackTarget;
public ref readonly Target VisualFeedbackTarget => ref visualFeedbackTarget;
public Player Player { get { return Subject != null ? Subject.Owner : null; } }
Order(string orderString, Actor subject, Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
readonly Target target;
readonly Target visualFeedbackTarget;
Order(string orderString, Actor subject, in Target target, string targetString, bool queued, Actor[] extraActors, CPos extraLocation, uint extraData, Actor[] groupedActors = null)
{
OrderString = orderString ?? "";
Subject = subject;
Target = target;
this.target = target;
TargetString = targetString;
Queued = queued;
ExtraActors = extraActors;
@@ -275,9 +281,15 @@ namespace OpenRA
public Order(string orderString, Actor subject, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, Target.Invalid, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
public Order(string orderString, Actor subject, in Target target, bool queued, Actor[] extraActors = null, Actor[] groupedActors = null)
: this(orderString, subject, target, null, queued, extraActors, CPos.Zero, 0, groupedActors) { }
public Order(string orderString, Actor subject, Target target, Target visualFeedbackTarget, bool queued)
: this(orderString, subject, target, null, queued, null, CPos.Zero, 0, null)
{
this.visualFeedbackTarget = visualFeedbackTarget;
}
public byte[] Serialize()
{
var minLength = 1 + OrderString.Length + 1;

View File

@@ -31,13 +31,14 @@ namespace OpenRA.Network
return ret;
}
public static byte[] SerializeSync(int sync)
public static byte[] SerializeSync(int sync, ulong defeatState)
{
var ms = new MemoryStream(1 + 4);
var ms = new MemoryStream(Order.SyncHashOrderLength);
using (var writer = new BinaryWriter(ms))
{
writer.Write((byte)OrderType.SyncHash);
writer.Write(sync);
writer.Write(defeatState);
}
return ms.GetBuffer();

View File

@@ -47,10 +47,10 @@ namespace OpenRA.Network
internal int GameSaveLastFrame = -1;
internal int GameSaveLastSyncFrame = -1;
List<Order> localOrders = new List<Order>();
List<Order> localImmediateOrders = new List<Order>();
readonly List<Order> localOrders = new List<Order>();
readonly List<Order> localImmediateOrders = new List<Order>();
List<ChatLine> chatCache = new List<ChatLine>();
readonly List<ChatLine> chatCache = new List<ChatLine>();
public readonly ReadOnlyList<ChatLine> ChatCache;
@@ -123,8 +123,16 @@ namespace OpenRA.Network
var frame = BitConverter.ToInt32(packet, 0);
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
frameData.ClientQuit(clientId, frame);
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
else if (packet.Length > 4 && packet[4] == (byte)OrderType.SyncHash)
{
if (packet.Length != 4 + Order.SyncHashOrderLength)
{
Log.Write("debug", "Dropped sync order with length {0}. Expected length {1}.".F(packet.Length, 4 + Order.SyncHashOrderLength));
return;
}
CheckSync(packet);
}
else if (frame == 0)
immediatePackets.Add((clientId, packet));
else
@@ -192,9 +200,16 @@ namespace OpenRA.Network
UnitOrders.ProcessOrder(this, World, order.Client, order.Order);
if (NetFrameNumber + FramesAhead >= GameSaveLastSyncFrame)
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash()));
{
var defeatState = 0UL;
for (var i = 0; i < World.Players.Length; i++)
if (World.Players[i].WinState == WinState.Lost)
defeatState |= 1UL << i;
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(World.SyncHash(), defeatState));
}
else
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0));
Connection.SendSync(NetFrameNumber, OrderIO.SerializeSync(0, 0));
if (generateSyncReport)
using (new PerfSample("sync_report"))

View File

@@ -55,28 +55,25 @@ namespace OpenRA.Network
using (var rs = File.OpenRead(replayFilename))
{
var packets = new List<(int ClientId, byte[] Packet)>();
var chunk = new Chunk();
while (rs.Position < rs.Length)
{
var client = rs.ReadInt32();
if (client == ReplayMetadata.MetaStartMarker)
break;
var packetLen = rs.ReadInt32();
var packet = rs.ReadBytes(packetLen);
var frame = BitConverter.ToInt32(packet, 0);
packets.Add((client, packet));
if (frame != int.MaxValue &&
(!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
if (frame != int.MaxValue && (!lastClientsFrame.ContainsKey(client) || frame > lastClientsFrame[client]))
lastClientsFrame[client] = frame;
if (packet.Length == 5 && packet[4] == (byte)OrderType.Disconnect)
continue; // disconnect
else if (packet.Length >= 5 && packet[4] == (byte)OrderType.SyncHash)
continue; // sync
else if (frame == 0)
if (packet.Length > 4 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
continue;
if (frame == 0)
{
// Parse replay metadata from orders stream
var orders = packet.ToOrderList(null);

View File

@@ -10,6 +10,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
@@ -25,9 +26,7 @@ namespace OpenRA.Network
static bool IsGameStart(byte[] data)
{
if (data.Length == 5 && data[4] == (byte)OrderType.Disconnect)
return false;
if (data.Length >= 5 && data[4] == (byte)OrderType.SyncHash)
if (data.Length > 4 && (data[4] == (byte)OrderType.Disconnect || data[4] == (byte)OrderType.SyncHash))
return false;
var frame = BitConverter.ToInt32(data, 0);
@@ -45,7 +44,7 @@ namespace OpenRA.Network
{
var filename = chooseFilename();
var mod = Game.ModData.Manifest;
var dir = Platform.ResolvePath(Platform.SupportDirPrefix, "Replays", mod.Id, mod.Metadata.Version);
var dir = Path.Combine(Platform.SupportDir, "Replays", mod.Id, mod.Metadata.Version);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
@@ -85,6 +84,14 @@ namespace OpenRA.Network
writer.Write(data);
}
public void ReceiveFrame(int clientID, int frame, byte[] data)
{
var ms = new MemoryStream(4 + data.Length);
ms.WriteArray(BitConverter.GetBytes(frame));
ms.WriteArray(data);
Receive(clientID, ms.GetBuffer());
}
bool disposed;
public void Dispose()

View File

@@ -26,6 +26,8 @@ namespace OpenRA.Network
// Keyed by the PlayerReference id that the slot corresponds to
public Dictionary<string, Slot> Slots = new Dictionary<string, Slot>();
public HashSet<int> DisabledSpawnPoints = new HashSet<int>();
public Global GlobalSettings = new Global();
public static string AnonymizeIP(IPAddress ip)
@@ -69,6 +71,9 @@ namespace OpenRA.Network
var s = Slot.Deserialize(node.Value);
session.Slots.Add(s.PlayerReference, s);
break;
case "DisabledSpawnPoints":
session.DisabledSpawnPoints = FieldLoader.GetValue<HashSet<int>>("DisabledSpawnPoints", node.Value.Value);
break;
}
}
@@ -267,7 +272,10 @@ namespace OpenRA.Network
public string Serialize()
{
var sessionData = new List<MiniYamlNode>();
var sessionData = new List<MiniYamlNode>()
{
new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints))
};
foreach (var client in Clients)
sessionData.Add(client.Serialize());

View File

@@ -27,13 +27,6 @@ namespace OpenRA.Network
internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
{
if (world != null)
{
if (!world.WorldActor.TraitsImplementing<IValidateOrder>().All(vo =>
vo.OrderValidation(orderManager, world, clientId, order)))
return;
}
switch (order.OrderString)
{
// Server message
@@ -46,7 +39,13 @@ namespace OpenRA.Network
{
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
if (client != null)
{
client.State = Session.ClientState.Disconnected;
var player = world?.FindPlayerByClient(client);
if (player != null)
world.OnPlayerDisconnected(player);
}
break;
}
@@ -302,10 +301,7 @@ namespace OpenRA.Network
{
var strings = node.Key.Split('@');
if (strings[0] == "GlobalSettings")
{
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
}
}
SetOrderLag(orderManager);
@@ -336,22 +332,27 @@ namespace OpenRA.Network
default:
{
if (world == null)
break;
if (order.GroupedActors == null)
ResolveOrder(order);
ResolveOrder(order, world, orderManager, clientId);
else
foreach (var subject in order.GroupedActors)
ResolveOrder(Order.FromGroupedOrder(order, subject));
ResolveOrder(Order.FromGroupedOrder(order, subject), world, orderManager, clientId);
break;
}
}
}
static void ResolveOrder(Order order)
static void ResolveOrder(Order order, World world, OrderManager orderManager, int clientId)
{
if (order.Subject != null && !order.Subject.IsDead)
foreach (var t in order.Subject.TraitsImplementing<IResolveOrder>())
t.ResolveOrder(order.Subject, order);
if (order.Subject == null || order.Subject.IsDead)
return;
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
order.Subject.ResolveOrder(order);
}
static void SetOrderLag(OrderManager o)

View File

@@ -15,6 +15,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA
{
@@ -41,26 +42,49 @@ namespace OpenRA
var resolvedPath = FileSystem.FileSystem.ResolveAssemblyPath(path, manifest, mods);
if (resolvedPath == null)
throw new FileNotFoundException("Assembly `{0}` not found.".F(path));
#if !MONO
var loader = new AssemblyLoader(resolvedPath);
var platformType = loader.LoadDefaultAssembly();
assemblyList.Add(platformType);
// .NET doesn't provide any way of querying the metadata of an assembly without either:
// (a) loading duplicate data into the application domain, breaking the world.
// (b) crashing if the assembly has already been loaded.
// We can't check the internal name of the assembly, so we'll work off the data instead
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
{
assembly = Assembly.LoadFile(resolvedPath);
ResolvedAssemblies.Add(hash, assembly);
}
assemblyList.Add(assembly);
#else
LoadAssembly(assemblyList, resolvedPath);
#endif
}
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
assemblies = assemblyList.SelectMany(asm => asm.GetNamespaces().Select(ns => (asm, ns))).ToArray();
}
void LoadAssembly(List<Assembly> assemblyList, string resolvedPath)
{
// .NET doesn't provide any way of querying the metadata of an assembly without either:
// (a) loading duplicate data into the application domain, breaking the world.
// (b) crashing if the assembly has already been loaded.
// We can't check the internal name of the assembly, so we'll work off the data instead
var hash = CryptoUtil.SHA1Hash(File.ReadAllBytes(resolvedPath));
if (!ResolvedAssemblies.TryGetValue(hash, out var assembly))
{
assembly = Assembly.LoadFile(resolvedPath);
ResolvedAssemblies.Add(hash, assembly);
// Allow mods to use libraries.
var assemblyPath = Path.GetDirectoryName(resolvedPath);
if (assemblyPath != null)
{
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
{
var depedencyPath = Path.Combine(assemblyPath, referencedAssembly.Name + ".dll");
if (File.Exists(depedencyPath))
LoadAssembly(assemblyList, depedencyPath);
}
}
}
assemblyList.Add(assembly);
}
Assembly ResolveAssembly(object sender, ResolveEventArgs e)
{
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<UseVSHostingProcess>false</UseVSHostingProcess>
@@ -10,7 +10,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
<RootNamespace>OpenRA</RootNamespace>
<OutputPath>..</OutputPath>
<OutputPath>../bin</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
@@ -31,34 +31,18 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetPlatform)' == 'win-x86'">
<Prefer32bit>true</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=ra</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Dune 2000'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=d2k</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Dawn'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=cnc</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Sun'">
<StartAction>Project</StartAction>
<StartArguments>Game.Mod=ts</StartArguments>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
<PackageReference Include="OpenRA-Eluant" Version="1.0.17" />
<PackageReference Include="OpenRA-Open.NAT" Version="1.0.0" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<AdditionalFiles Include="../stylecop.json" />
<AdditionalFiles Include="Properties/launchSettings.json" />
</ItemGroup>
<ItemGroup Condition="'$(Mono)' == ''">
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="5.0.0-preview.3-runtime.20214.6" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">
<!-- Disable code style analysis on Release builds to improve compile-time performance -->

View File

@@ -196,15 +196,17 @@ namespace OpenRA.Orders
public readonly IOrderTargeter Order;
public readonly IIssueOrder Trait;
public readonly string Cursor;
public readonly Target Target;
public ref readonly Target Target => ref target;
public UnitOrderResult(Actor actor, IOrderTargeter order, IIssueOrder trait, string cursor, Target target)
readonly Target target;
public UnitOrderResult(Actor actor, IOrderTargeter order, IIssueOrder trait, string cursor, in Target target)
{
Actor = actor;
Order = order;
Trait = trait;
Cursor = cursor;
Target = target;
this.target = target;
}
}

View File

@@ -22,12 +22,14 @@ namespace OpenRA
public static class Platform
{
public const string SupportDirPrefix = "^";
public static PlatformType CurrentPlatform { get { return currentPlatform.Value; } }
public static readonly Guid SessionGUID = Guid.NewGuid();
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);
static bool engineDirAccessed;
static string engineDir;
static bool supportDirInitialized;
static string systemSupportPath;
static string legacyUserSupportPath;
@@ -137,7 +139,7 @@ namespace OpenRA
}
// Use a local directory in the game root if it exists (shared with the system support dir)
var localSupportDir = Path.Combine(GameDir, "Support");
var localSupportDir = Path.Combine(EngineDir, "Support");
if (Directory.Exists(localSupportDir))
userSupportPath = systemSupportPath = localSupportDir + Path.DirectorySeparatorChar;
@@ -170,7 +172,45 @@ namespace OpenRA
userSupportPath = path;
}
public static string GameDir
public static string EngineDir
{
get
{
// Engine directory defaults to the location of the binaries,
// unless OverrideGameDir is called during startup.
if (!engineDirAccessed)
engineDir = BinDir;
engineDirAccessed = true;
return engineDir;
}
}
/// <summary>
/// Specify a custom engine directory that already exists on the filesystem.
/// Cannot be called after Platform.EngineDir has been accessed.
/// </summary>
public static void OverrideEngineDir(string path)
{
if (engineDirAccessed)
throw new InvalidOperationException("Attempted to override engine directory after it has already been accessed.");
// Note: Relative paths are interpreted as being relative to BinDir, not the current working dir.
if (!Path.IsPathRooted(path))
path = Path.Combine(BinDir, path);
if (!Directory.Exists(path))
throw new DirectoryNotFoundException(path);
if (!path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) &&
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
path += Path.DirectorySeparatorChar;
engineDirAccessed = true;
engineDir = path;
}
public static string BinDir
{
get
{
@@ -189,50 +229,25 @@ namespace OpenRA
{
path = path.TrimEnd(' ', '\t');
// Paths starting with ^ are relative to the support dir
if (IsPathRelativeToSupportDirectory(path))
path = SupportDir + path.Substring(1);
if (path == "^SupportDir")
return SupportDir;
// Paths starting with . are relative to the game dir
if (path == ".")
return GameDir;
if (path == "^EngineDir")
return EngineDir;
if (path.StartsWith("./", StringComparison.Ordinal) || path.StartsWith(".\\", StringComparison.Ordinal))
path = GameDir + path.Substring(2);
if (path == "^BinDir")
return BinDir;
if (path.StartsWith("^SupportDir|", StringComparison.Ordinal))
path = SupportDir + path.Substring(12);
if (path.StartsWith("^EngineDir|", StringComparison.Ordinal))
path = EngineDir + path.Substring(11);
if (path.StartsWith("^BinDir|", StringComparison.Ordinal))
path = BinDir + path.Substring(8);
return path;
}
/// <summary>Replace special character prefixes with full paths.</summary>
public static string ResolvePath(params string[] path)
{
return ResolvePath(Path.Combine(path));
}
/// <summary>
/// Replace the full path prefix with the special notation characters ^ or .
/// and transforms \ path separators to / on Windows
/// </summary>
public static string UnresolvePath(string path)
{
// Use a case insensitive comparison on windows to avoid problems
// with inconsistent drive letter case
var compare = CurrentPlatform == PlatformType.Windows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
if (path.StartsWith(SupportDir, compare))
path = SupportDirPrefix + path.Substring(SupportDir.Length);
if (path.StartsWith(GameDir, compare))
path = "./" + path.Substring(GameDir.Length);
if (CurrentPlatform == PlatformType.Windows)
path = path.Replace('\\', '/');
return path;
}
public static bool IsPathRelativeToSupportDirectory(string path)
{
return path.StartsWith(SupportDirPrefix, StringComparison.Ordinal);
}
}
}

View File

@@ -17,6 +17,7 @@ using Eluant.ObjectBinding;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Support;
using OpenRA.Traits;
using OpenRA.Widgets;
@@ -56,23 +57,38 @@ namespace OpenRA
public readonly bool NonCombatant = false;
public readonly bool Playable = true;
public readonly int ClientIndex;
public readonly CPos HomeLocation;
public readonly PlayerReference PlayerReference;
public readonly bool IsBot;
public readonly string BotType;
public readonly Shroud Shroud;
public readonly FrozenActorLayer FrozenActorLayer;
/// <summary>The faction (including Random, etc) that was selected in the lobby.</summary>
/// <summary>The faction (including Random, etc.) that was selected in the lobby.</summary>
public readonly FactionInfo DisplayFaction;
/// <summary>The spawn point index that was assigned for client-based players.</summary>
public readonly int SpawnPoint;
/// <summary>The spawn point index (including 0 for Random) that was selected in the lobby for client-based players.</summary>
public readonly int DisplaySpawnPoint;
public WinState WinState = WinState.Undefined;
public int SpawnPoint;
public bool HasObjectives = false;
public bool Spectating;
public bool Spectating
{
get
{
// Players in mission maps must not leave the player view
return !inMissionMap && (spectating || WinState != WinState.Undefined);
}
}
public World World { get; private set; }
readonly bool inMissionMap;
readonly bool spectating;
readonly IUnlocksRenderPlayer[] unlockRenderPlayer;
// Each player is identified with a unique bit in the set
@@ -94,19 +110,19 @@ namespace OpenRA
readonly StanceColors stanceColors;
static FactionInfo ChooseFaction(World world, string name, bool requireSelectable = true)
public static FactionInfo ResolveFaction(string factionName, IEnumerable<FactionInfo> factionInfos, MersenneTwister playerRandom, bool requireSelectable = true)
{
var selectableFactions = world.Map.Rules.Actors["world"].TraitInfos<FactionInfo>()
var selectableFactions = factionInfos
.Where(f => !requireSelectable || f.Selectable)
.ToList();
var selected = selectableFactions.FirstOrDefault(f => f.InternalName == name)
?? selectableFactions.Random(world.SharedRandom);
var selected = selectableFactions.FirstOrDefault(f => f.InternalName == factionName)
?? selectableFactions.Random(playerRandom);
// Don't loop infinite
for (var i = 0; i <= 10 && selected.RandomFactionMembers.Any(); i++)
{
var faction = selected.RandomFactionMembers.Random(world.SharedRandom);
var faction = selected.RandomFactionMembers.Random(playerRandom);
selected = selectableFactions.FirstOrDefault(f => f.InternalName == faction);
if (selected == null)
@@ -116,14 +132,32 @@ namespace OpenRA
return selected;
}
static FactionInfo ChooseDisplayFaction(World world, string factionName)
static FactionInfo ResolveFaction(World world, string factionName, MersenneTwister playerRandom, bool requireSelectable)
{
var factionInfos = world.Map.Rules.Actors["world"].TraitInfos<FactionInfo>();
return ResolveFaction(factionName, factionInfos, playerRandom, requireSelectable);
}
static FactionInfo ResolveDisplayFaction(World world, string factionName)
{
var factions = world.Map.Rules.Actors["world"].TraitInfos<FactionInfo>().ToArray();
return factions.FirstOrDefault(f => f.InternalName == factionName) ?? factions.First();
}
public Player(World world, Session.Client client, PlayerReference pr)
public static string ResolvePlayerName(Session.Client client, IEnumerable<Session.Client> clients, IEnumerable<IBotInfo> botInfos)
{
if (client.Bot != null)
{
var botInfo = botInfos.First(b => b.Type == client.Bot);
var botsOfSameType = clients.Where(c => c.Bot == client.Bot).ToArray();
return botsOfSameType.Length == 1 ? botInfo.Name : "{0} {1}".F(botInfo.Name, botsOfSameType.IndexOf(client) + 1);
}
return client.Name;
}
public Player(World world, Session.Client client, PlayerReference pr, MersenneTwister playerRandom)
{
World = world;
InternalName = pr.Name;
@@ -136,18 +170,16 @@ namespace OpenRA
{
ClientIndex = client.Index;
Color = client.Color;
if (client.Bot != null)
{
var botInfo = world.Map.Rules.Actors["player"].TraitInfos<IBotInfo>().First(b => b.Type == client.Bot);
var botsOfSameType = world.LobbyInfo.Clients.Where(c => c.Bot == client.Bot).ToArray();
PlayerName = botsOfSameType.Length == 1 ? botInfo.Name : "{0} {1}".F(botInfo.Name, botsOfSameType.IndexOf(client) + 1);
}
else
PlayerName = client.Name;
PlayerName = ResolvePlayerName(client, world.LobbyInfo.Clients, world.Map.Rules.Actors["player"].TraitInfos<IBotInfo>());
BotType = client.Bot;
Faction = ChooseFaction(world, client.Faction, !pr.LockFaction);
DisplayFaction = ChooseDisplayFaction(world, client.Faction);
Faction = ResolveFaction(world, client.Faction, playerRandom, !pr.LockFaction);
DisplayFaction = ResolveDisplayFaction(world, client.Faction);
var assignSpawnPoints = world.WorldActor.TraitOrDefault<IAssignSpawnPoints>();
HomeLocation = assignSpawnPoints?.AssignHomeLocation(world, client, playerRandom) ?? pr.HomeLocation;
SpawnPoint = assignSpawnPoints?.SpawnPointForPlayer(this) ?? client.SpawnPoint;
DisplaySpawnPoint = client.SpawnPoint;
}
else
{
@@ -157,13 +189,15 @@ namespace OpenRA
PlayerName = pr.Name;
NonCombatant = pr.NonCombatant;
Playable = pr.Playable;
Spectating = pr.Spectating;
spectating = pr.Spectating;
BotType = pr.Bot;
Faction = ChooseFaction(world, pr.Faction, false);
DisplayFaction = ChooseDisplayFaction(world, pr.Faction);
Faction = ResolveFaction(world, pr.Faction, playerRandom, false);
DisplayFaction = ResolveDisplayFaction(world, pr.Faction);
HomeLocation = pr.HomeLocation;
SpawnPoint = DisplaySpawnPoint = 0;
}
if (!Spectating)
if (!spectating)
PlayerMask = new LongBitSet<PlayerBitMask>(InternalName);
// Set this property before running any Created callbacks on the player actor
@@ -204,11 +238,27 @@ namespace OpenRA
return "{0} ({1})".F(PlayerName, ClientIndex);
}
public Dictionary<Player, Stance> Stances = new Dictionary<Player, Stance>();
public PlayerRelationship RelationshipWith(Player other)
{
if (this == other)
return PlayerRelationship.Ally;
// Observers are considered allies to active combatants
if (other == null || other.Spectating)
return NonCombatant ? PlayerRelationship.Neutral : PlayerRelationship.Ally;
if (AlliedPlayersMask.Overlaps(other.PlayerMask))
return PlayerRelationship.Ally;
if (EnemyPlayersMask.Overlaps(other.PlayerMask))
return PlayerRelationship.Enemy;
return PlayerRelationship.Neutral;
}
public bool IsAlliedWith(Player p)
{
// Observers are considered allies to active combatants
return p == null || Stances[p] == Stance.Ally || (p.Spectating && !NonCombatant);
return RelationshipWith(p) == PlayerRelationship.Ally;
}
public Color PlayerStanceColor(Actor a)

View File

@@ -18,6 +18,7 @@ namespace OpenRA.Primitives
static class LongBitSetAllocator<T> where T : class
{
static readonly Cache<string, long> Bits = new Cache<string, long>(Allocate);
static long allBits = 1;
static long nextBits = 1;
static long Allocate(string value)
@@ -25,6 +26,7 @@ namespace OpenRA.Primitives
lock (Bits)
{
var bits = nextBits;
allBits |= bits;
nextBits <<= 1;
if (nextBits == 0)
@@ -85,6 +87,8 @@ namespace OpenRA.Primitives
nextBits = 1;
}
}
public static long Mask { get { return allBits; } }
}
// Opitmized BitSet to be used only when guaranteed to be no more than 64 values.
@@ -114,6 +118,7 @@ namespace OpenRA.Primitives
public static bool operator ==(LongBitSet<T> me, LongBitSet<T> other) { return me.bits == other.bits; }
public static bool operator !=(LongBitSet<T> me, LongBitSet<T> other) { return !(me == other); }
public static LongBitSet<T> operator ~(LongBitSet<T> me) { return new LongBitSet<T>(me.bits ^ LongBitSetAllocator<T>.Mask); }
public bool Equals(LongBitSet<T> other) { return other == this; }
public override bool Equals(object obj) { return obj is LongBitSet<T> && Equals((LongBitSet<T>)obj); }

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.IO;
namespace OpenRA.Primitives
@@ -18,7 +19,7 @@ namespace OpenRA.Primitives
public Stream Stream1 { get; set; }
public Stream Stream2 { get; set; }
long VirtualLength { get; set; }
long VirtualLength { get; }
long position;
public MergedStream(Stream stream1, Stream stream2)
@@ -61,7 +62,21 @@ namespace OpenRA.Primitives
public override void SetLength(long value)
{
VirtualLength = value;
throw new NotSupportedException();
}
public override int ReadByte()
{
int value;
if (position >= Stream1.Length)
value = Stream2.ReadByte();
else
value = Stream1.ReadByte();
position++;
return value;
}
public override int Read(byte[] buffer, int offset, int count)
@@ -83,12 +98,14 @@ namespace OpenRA.Primitives
return bytesRead;
}
public override void WriteByte(byte value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (position >= Stream1.Length)
Stream2.Write(buffer, offset - (int)Stream1.Length, count);
else
Stream1.Write(buffer, offset, count);
throw new NotSupportedException();
}
public override bool CanRead
@@ -103,7 +120,7 @@ namespace OpenRA.Primitives
public override bool CanWrite
{
get { return Stream1.CanWrite && Stream2.CanWrite; }
get { return false; }
}
public override long Length

View File

@@ -48,9 +48,25 @@ namespace OpenRA.Primitives
public sealed override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
public sealed override void SetLength(long value) { throw new NotSupportedException(); }
public sealed override void WriteByte(byte value) { throw new NotSupportedException(); }
public sealed override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
public sealed override void Flush() { throw new NotSupportedException(); }
public sealed override int ReadByte()
{
if (data.Count > 0)
return data.Dequeue();
while (!baseStreamEmpty)
{
baseStreamEmpty = BufferData(baseStream, data);
if (data.Count > 0)
return data.Dequeue();
}
return -1;
}
public sealed override int Read(byte[] buffer, int offset, int count)
{
var copied = 0;
@@ -66,7 +82,8 @@ namespace OpenRA.Primitives
}
/// <summary>
/// Reads data into a buffer, which will be used to satisfy <see cref="Read(byte[], int, int)"/> calls.
/// Reads data into a buffer, which will be used to satisfy <see cref="ReadByte()"/> and
/// <see cref="Read(byte[], int, int)"/> calls.
/// </summary>
/// <param name="baseStream">The source stream from which bytes should be read.</param>
/// <param name="data">The queue where bytes should be enqueued. Do not dequeue from this buffer.</param>

View File

@@ -59,8 +59,35 @@ namespace OpenRA.Primitives
set { BaseStream.Position = BaseOffset + value; }
}
public override int Read(byte[] buffer, int offset, int count) { return BaseStream.Read(buffer, offset, count); }
public override void Write(byte[] buffer, int offset, int count) { BaseStream.Write(buffer, offset, count); }
public override int ReadByte()
{
if (Position < Length)
return BaseStream.ReadByte();
return -1;
}
public override int Read(byte[] buffer, int offset, int count)
{
var remaining = Length - Position;
if (remaining <= 0)
return 0;
return BaseStream.Read(buffer, offset, (int)Math.Min(remaining, count));
}
public override void WriteByte(byte value)
{
if (Position < Length)
BaseStream.WriteByte(value);
throw new IOException("Attempted to write past the end of the stream.");
}
public override void Write(byte[] buffer, int offset, int count)
{
if (Position + count >= Length)
throw new IOException("Attempted to write past the end of the stream.");
BaseStream.Write(buffer, offset, count);
}
public override void Flush() { BaseStream.Flush(); }
public override long Seek(long offset, SeekOrigin origin)
{

View File

@@ -17,7 +17,7 @@ namespace OpenRA
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Mimic a built-in type alias.")]
[StructLayout(LayoutKind.Sequential)]
public struct float3 : IEquatable<float3>
public readonly struct float3 : IEquatable<float3>
{
public readonly float X, Y, Z;
public float2 XY { get { return new float2(X, Y); } }
@@ -28,13 +28,13 @@ namespace OpenRA
public static implicit operator float3(int2 src) { return new float3(src.X, src.Y, 0); }
public static implicit operator float3(float2 src) { return new float3(src.X, src.Y, 0); }
public static float3 operator +(float3 a, float3 b) { return new float3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); }
public static float3 operator -(float3 a, float3 b) { return new float3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); }
public static float3 operator -(float3 a) { return new float3(-a.X, -a.Y, -a.Z); }
public static float3 operator *(float3 a, float3 b) { return new float3(a.X * b.X, a.Y * b.Y, a.Z * b.Z); }
public static float3 operator *(float a, float3 b) { return new float3(a * b.X, a * b.Y, a * b.Z); }
public static float3 operator /(float3 a, float3 b) { return new float3(a.X / b.X, a.Y / b.Y, a.Z / b.Z); }
public static float3 operator /(float3 a, float b) { return new float3(a.X / b, a.Y / b, a.Z / b); }
public static float3 operator +(in float3 a, in float3 b) { return new float3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); }
public static float3 operator -(in float3 a, in float3 b) { return new float3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); }
public static float3 operator -(in float3 a) { return new float3(-a.X, -a.Y, -a.Z); }
public static float3 operator *(in float3 a, in float3 b) { return new float3(a.X * b.X, a.Y * b.Y, a.Z * b.Z); }
public static float3 operator *(float a, in float3 b) { return new float3(a * b.X, a * b.Y, a * b.Z); }
public static float3 operator /(in float3 a, in float3 b) { return new float3(a.X / b.X, a.Y / b.Y, a.Z / b.Z); }
public static float3 operator /(in float3 a, float b) { return new float3(a.X / b, a.Y / b, a.Z / b); }
public static float3 Lerp(float3 a, float3 b, float t)
{
@@ -44,8 +44,8 @@ namespace OpenRA
float2.Lerp(a.Z, b.Z, t));
}
public static bool operator ==(float3 me, float3 other) { return me.X == other.X && me.Y == other.Y && me.Z == other.Z; }
public static bool operator !=(float3 me, float3 other) { return !(me == other); }
public static bool operator ==(in float3 me, in float3 other) { return me.X == other.X && me.Y == other.Y && me.Z == other.Z; }
public static bool operator !=(in float3 me, in float3 other) { return !(me == other); }
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); }
public bool Equals(float3 other)

View File

@@ -77,7 +77,7 @@ namespace OpenRA
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height),
graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize,
graphicSettings.VideoDisplay, graphicSettings.GLProfile);
graphicSettings.VideoDisplay, graphicSettings.GLProfile, !graphicSettings.DisableLegacyGL);
Context = Window.Context;

View File

@@ -161,8 +161,8 @@ namespace OpenRA.Scripting
.ToArray();
PlayerCommands = FilterCommands(world.Map.Rules.Actors["player"], knownPlayerCommands);
runtime.Globals["GameDir"] = Platform.GameDir;
runtime.DoBuffer(File.Open(Platform.ResolvePath(".", "lua", "scriptwrapper.lua"), FileMode.Open, FileAccess.Read).ReadAllText(), "scriptwrapper.lua").Dispose();
runtime.Globals["EngineDir"] = Platform.EngineDir;
runtime.DoBuffer(File.Open(Path.Combine(Platform.EngineDir, "lua", "scriptwrapper.lua"), FileMode.Open, FileAccess.Read).ReadAllText(), "scriptwrapper.lua").Dispose();
tick = (LuaFunction)runtime.Globals["Tick"];
// Register globals

View File

@@ -86,9 +86,11 @@ namespace OpenRA.Scripting
{
foreach (var arg in clrArgs)
{
if (!(arg is LuaValue[]))
var table = arg as LuaValue[];
if (table == null)
continue;
foreach (var value in (LuaValue[])arg)
foreach (var value in table)
value.Dispose();
}
}

View File

@@ -36,11 +36,11 @@ namespace OpenRA.Traits
if (a.Owner == viewer || viewer == null)
return basePriority;
switch (viewer.Stances[a.Owner])
switch (viewer.RelationshipWith(a.Owner))
{
case Stance.Ally: return basePriority - PriorityRange;
case Stance.Neutral: return basePriority - 2 * PriorityRange;
case Stance.Enemy: return basePriority - 3 * PriorityRange;
case PlayerRelationship.Ally: return basePriority - PriorityRange;
case PlayerRelationship.Neutral: return basePriority - 2 * PriorityRange;
case PlayerRelationship.Enemy: return basePriority - 3 * PriorityRange;
default:
throw new InvalidOperationException();

View File

@@ -25,6 +25,8 @@ namespace OpenRA.Server
// Order types are:
// - 0x65: World sync hash:
// - Int32 containing the sync hash value
// - UInt64 containing the current defeat state (a bit set
// to 1 means the corresponding player is defeated)
// - 0xBF: Player disconnected
// - 0xFE: Handshake (also used for ServerOrders for ProtocolVersion.Orders < 8)
// - Length-prefixed string specifying a name or key
@@ -66,6 +68,6 @@ namespace OpenRA.Server
// The protocol for server and world orders
// This applies after the handshake has completed, and is provided to support
// alternative server implementations that wish to support multiple versions in parallel
public const int Orders = 10;
public const int Orders = 11;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -88,6 +88,9 @@ namespace OpenRA
[Desc("Allow clients to see the country of other clients.")]
public bool EnableGeoIP = true;
[Desc("For dedicated servers only, save replays for all games played.")]
public bool RecordReplays = false;
public ServerSettings Clone()
{
return (ServerSettings)MemberwiseClone();
@@ -172,14 +175,18 @@ namespace OpenRA
[Desc("Disable operating-system provided cursor rendering.")]
public bool DisableHardwareCursors = false;
[Desc("Disable legacy OpenGL 2.1 support.")]
public bool DisableLegacyGL = true;
[Desc("Display index to use in a multi-monitor fullscreen setup.")]
public int VideoDisplay = 0;
[Desc("Preferred OpenGL profile to use.",
"Modern: OpenGL Core Profile 3.2 or greater.",
"Embedded: OpenGL ES 3.0 or greater.",
"Legacy: OpenGL 2.1 with framebuffer_object extension.")]
public GLProfile GLProfile = GLProfile.Modern;
"Legacy: OpenGL 2.1 with framebuffer_object extension (requires DisableLegacyGL: False)",
"Automatic: Use the first supported profile.")]
public GLProfile GLProfile = GLProfile.Automatic;
public int BatchSize = 8192;
public int SheetSize = 2048;

View File

@@ -61,22 +61,22 @@ namespace OpenRA
public static ushort ReadUInt16(this Stream s)
{
return BitConverter.ToUInt16(s.ReadBytes(2), 0);
return (ushort)(s.ReadUInt8() | s.ReadUInt8() << 8);
}
public static short ReadInt16(this Stream s)
{
return BitConverter.ToInt16(s.ReadBytes(2), 0);
return (short)(s.ReadUInt8() | s.ReadUInt8() << 8);
}
public static uint ReadUInt32(this Stream s)
{
return BitConverter.ToUInt32(s.ReadBytes(4), 0);
return (uint)(s.ReadUInt8() | s.ReadUInt8() << 8 | s.ReadUInt8() << 16 | s.ReadUInt8() << 24);
}
public static int ReadInt32(this Stream s)
{
return BitConverter.ToInt32(s.ReadBytes(4), 0);
return s.ReadUInt8() | s.ReadUInt8() << 8 | s.ReadUInt8() << 16 | s.ReadUInt8() << 24;
}
public static void Write(this Stream s, int value)

View File

@@ -0,0 +1,362 @@
#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
// Not used/usable on Mono. Only used for Dotnet Core. Based on https://github.com/natemcmaster/DotNetCorePlugins
#if !MONO
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace OpenRA.Support
{
public class AssemblyLoader
{
readonly string mainAssembly;
readonly AssemblyLoadContext context;
public Assembly LoadDefaultAssembly() => context.LoadFromAssemblyPath(mainAssembly);
public AssemblyLoader(string assemblyFile)
{
mainAssembly = assemblyFile;
var baseDir = Path.GetDirectoryName(assemblyFile);
context = CreateLoadContext(baseDir, assemblyFile);
}
static AssemblyLoadContext CreateLoadContext(string baseDir, string assemblyFile)
{
var depsJsonFile = Path.Combine(baseDir, Path.GetFileNameWithoutExtension(assemblyFile) + ".deps.json");
var builder = new AssemblyLoadContextBuilder();
builder.TryAddDependencyContext(depsJsonFile, out _);
builder.SetBaseDirectory(baseDir);
return builder.Build();
}
}
public class AssemblyLoadContextBuilder
{
readonly Dictionary<string, ManagedLibrary> managedLibraries = new Dictionary<string, ManagedLibrary>(StringComparer.Ordinal);
readonly Dictionary<string, NativeLibrary> nativeLibraries = new Dictionary<string, NativeLibrary>(StringComparer.Ordinal);
string basePath;
public AssemblyLoadContext Build()
{
return new ManagedLoadContext(basePath, managedLibraries, nativeLibraries);
}
public AssemblyLoadContextBuilder SetBaseDirectory(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Argument must not be null or empty.", nameof(path));
if (!Path.IsPathRooted(path))
throw new ArgumentException("Argument must be a full path.", nameof(path));
basePath = path;
return this;
}
public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library)
{
managedLibraries.Add(library.Name.Name, library);
return this;
}
public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library)
{
ValidateRelativePath(library.AppLocalPath);
nativeLibraries.Add(library.Name, library);
return this;
}
static void ValidateRelativePath(string probingPath)
{
if (string.IsNullOrEmpty(probingPath))
throw new ArgumentException("Value must not be null or empty.", nameof(probingPath));
if (Path.IsPathRooted(probingPath))
throw new ArgumentException("Argument must be a relative path.", nameof(probingPath));
}
}
class ManagedLoadContext : AssemblyLoadContext
{
readonly string basePath;
readonly Dictionary<string, ManagedLibrary> managedAssemblies;
readonly Dictionary<string, NativeLibrary> nativeLibraries;
static readonly string[] NativeLibraryExtensions;
static readonly string[] NativeLibraryPrefixes;
static readonly string[] ManagedAssemblyExtensions =
{
".dll",
".ni.dll",
".exe",
".ni.exe"
};
static ManagedLoadContext()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
NativeLibraryPrefixes = new[] { "" };
NativeLibraryExtensions = new[] { ".dll" };
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
NativeLibraryPrefixes = new[] { "", "lib", };
NativeLibraryExtensions = new[] { ".dylib" };
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
NativeLibraryPrefixes = new[] { "", "lib" };
NativeLibraryExtensions = new[] { ".so", ".so.1" };
}
else
{
NativeLibraryPrefixes = Array.Empty<string>();
NativeLibraryExtensions = Array.Empty<string>();
}
}
public ManagedLoadContext(string baseDirectory, Dictionary<string, ManagedLibrary> managedAssemblies, Dictionary<string, NativeLibrary> nativeLibraries)
{
basePath = baseDirectory ?? throw new ArgumentNullException(nameof(baseDirectory));
this.managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies));
this.nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries));
}
protected override Assembly Load(AssemblyName assemblyName)
{
// If default context is preferred, check first for types in the default context unless the dependency has been declared as private
try
{
var defaultAssembly = Default.LoadFromAssemblyName(assemblyName);
if (defaultAssembly != null)
return null;
}
catch
{
// Swallow errors in loading from the default context
}
if (managedAssemblies.TryGetValue(assemblyName.Name, out var library) && SearchForLibrary(library, out var path))
return LoadFromAssemblyPath(path);
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
foreach (var prefix in NativeLibraryPrefixes)
if (nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library) && SearchForLibrary(library, prefix, out var path))
return LoadUnmanagedDllFromPath(path);
return base.LoadUnmanagedDll(unmanagedDllName);
}
bool SearchForLibrary(ManagedLibrary library, out string path)
{
// 1. Search in base path
foreach (var ext in ManagedAssemblyExtensions)
{
var local = Path.Combine(basePath, library.Name.Name + ext);
if (File.Exists(local))
{
path = local;
return true;
}
}
path = null;
return false;
}
bool SearchForLibrary(NativeLibrary library, string prefix, out string path)
{
// 1. Search in base path
foreach (var ext in NativeLibraryExtensions)
{
var candidate = Path.Combine(basePath, $"{prefix}{library.Name}{ext}");
if (File.Exists(candidate))
{
path = candidate;
return true;
}
}
// 2. Search in base path + app local (for portable deployments of netcoreapp)
var local = Path.Combine(basePath, library.AppLocalPath);
if (File.Exists(local))
{
path = local;
return true;
}
path = null;
return false;
}
}
public class ManagedLibrary
{
public AssemblyName Name { get; private set; }
public static ManagedLibrary CreateFromPackage(string assetPath)
{
return new ManagedLibrary
{
Name = new AssemblyName(Path.GetFileNameWithoutExtension(assetPath))
};
}
}
public class NativeLibrary
{
public string Name { get; private set; }
public string AppLocalPath { get; private set; }
public static NativeLibrary CreateFromPackage(string assetPath)
{
return new NativeLibrary
{
Name = Path.GetFileNameWithoutExtension(assetPath),
AppLocalPath = assetPath
};
}
}
public static class DependencyContextExtensions
{
public static AssemblyLoadContextBuilder TryAddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath, out Exception error)
{
error = null;
try
{
builder.AddDependencyContext(depsFilePath);
}
catch (Exception ex)
{
error = ex;
}
return builder;
}
public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, string depsFilePath)
{
var reader = new DependencyContextJsonReader();
using (var file = File.OpenRead(depsFilePath))
{
var deps = reader.Read(file);
builder.SetBaseDirectory(Path.GetDirectoryName(depsFilePath));
builder.AddDependencyContext(deps);
}
return builder;
}
static string GetFallbackRid()
{
string ridBase;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
ridBase = "win10";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
ridBase = "linux";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
ridBase = "osx.10.12";
else
return "any";
switch (RuntimeInformation.OSArchitecture)
{
case Architecture.X86:
return ridBase + "-x86";
case Architecture.X64:
return ridBase + "-x64";
case Architecture.Arm:
return ridBase + "-arm";
case Architecture.Arm64:
return ridBase + "-arm64";
}
return ridBase;
}
public static AssemblyLoadContextBuilder AddDependencyContext(this AssemblyLoadContextBuilder builder, DependencyContext dependencyContext)
{
var ridGraph = dependencyContext.RuntimeGraph.Any()
? dependencyContext.RuntimeGraph
: DependencyContext.Default.RuntimeGraph;
var rid = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier();
var fallbackRid = GetFallbackRid();
var fallbackGraph = ridGraph.FirstOrDefault(g => g.Runtime == rid)
?? ridGraph.FirstOrDefault(g => g.Runtime == fallbackRid)
?? new RuntimeFallbacks("any");
foreach (var managed in dependencyContext.ResolveRuntimeAssemblies(fallbackGraph))
builder.AddManagedLibrary(managed);
foreach (var native in dependencyContext.ResolveNativeAssets(fallbackGraph))
builder.AddNativeLibrary(native);
return builder;
}
static IEnumerable<ManagedLibrary> ResolveRuntimeAssemblies(this DependencyContext depContext, RuntimeFallbacks runtimeGraph)
{
var rids = GetRids(runtimeGraph);
return from library in depContext.RuntimeLibraries
from assetPath in SelectAssets(rids, library.RuntimeAssemblyGroups)
select ManagedLibrary.CreateFromPackage(assetPath);
}
static IEnumerable<NativeLibrary> ResolveNativeAssets(this DependencyContext depContext, RuntimeFallbacks runtimeGraph)
{
var rids = GetRids(runtimeGraph);
return from library in depContext.RuntimeLibraries
from assetPath in SelectAssets(rids, library.NativeLibraryGroups)
where !assetPath.EndsWith(".a", StringComparison.Ordinal)
select NativeLibrary.CreateFromPackage(assetPath);
}
static IEnumerable<string> GetRids(RuntimeFallbacks runtimeGraph)
{
return Enumerable.Concat(new[] { runtimeGraph.Runtime }, runtimeGraph?.Fallbacks ?? Enumerable.Empty<string>());
}
static IEnumerable<string> SelectAssets(IEnumerable<string> rids, IEnumerable<RuntimeAssetGroup> groups)
{
foreach (var rid in rids)
{
var group = groups.FirstOrDefault(g => g.Runtime == rid);
if (group != null)
return group.AssetPaths;
}
return groups.GetDefaultAssets();
}
}
}
#endif

View File

@@ -21,26 +21,4 @@ namespace OpenRA
Success = 0,
Running = int.MaxValue
}
static class Program
{
[STAThread]
static int Main(string[] args)
{
if (Debugger.IsAttached || args.Contains("--just-die"))
return (int)Game.InitializeAndRun(args);
AppDomain.CurrentDomain.UnhandledException += (_, e) => ExceptionHandler.HandleFatalError((Exception)e.ExceptionObject);
try
{
return (int)Game.InitializeAndRun(args);
}
catch (Exception e)
{
ExceptionHandler.HandleFatalError(e);
return (int)RunStatus.Error;
}
}
}
}

View File

@@ -11,8 +11,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA
{
@@ -122,6 +124,16 @@ namespace OpenRA
t.Value.RemoveActor(a.ActorID);
}
public void ApplyToActorsWithTrait<T>(Action<Actor, T> action)
{
InnerGet<T>().ApplyToAll(action);
}
public void ApplyToActorsWithTraitTimed<T>(Action<Actor, T> action, string text)
{
InnerGet<T>().ApplyToAllTimed(action, text);
}
interface ITraitContainer
{
void Add(Actor actor, object trait);
@@ -277,6 +289,32 @@ namespace OpenRA
actors.RemoveRange(startIndex, count);
traits.RemoveRange(startIndex, count);
}
public void ApplyToAll(Action<Actor, T> action)
{
for (var i = 0; i < actors.Count; i++)
action(actors[i], traits[i]);
}
public void ApplyToAllTimed(Action<Actor, T> action, string text)
{
var longTickThresholdInStopwatchTicks = PerfTimer.LongTickThresholdInStopwatchTicks;
var start = Stopwatch.GetTimestamp();
for (var i = 0; i < actors.Count; i++)
{
var actor = actors[i];
var trait = traits[i];
action(actor, trait);
var current = Stopwatch.GetTimestamp();
if (current - start > longTickThresholdInStopwatchTicks)
{
PerfTimer.LogLongTick(start, current, text, trait);
start = Stopwatch.GetTimestamp();
}
else
start = current;
}
}
}
}
}

View File

@@ -13,54 +13,53 @@ using System;
namespace OpenRA.Traits
{
/* attributes used by OpenRA.Lint to understand the rules */
public enum LintDictionaryReference
{
None = 0,
Keys = 1,
Values = 2
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class ActorReferenceAttribute : Attribute
{
public Type[] RequiredTraits;
public ActorReferenceAttribute(params Type[] requiredTraits)
public readonly Type[] RequiredTraits;
public readonly LintDictionaryReference DictionaryReference;
public ActorReferenceAttribute(Type[] requiredTraits,
LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
{
RequiredTraits = requiredTraits;
DictionaryReference = dictionaryReference;
}
public ActorReferenceAttribute(Type requiredTrait = null,
LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
{
RequiredTraits = requiredTrait != null ? new[] { requiredTrait } : new Type[0];
DictionaryReference = dictionaryReference;
}
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class WeaponReferenceAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class VoiceSetReferenceAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class VoiceReferenceAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class LocomotorReferenceAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field)]
public sealed class NotificationReferenceAttribute : Attribute
{
public readonly string NotificationTypeFieldName = null;
public readonly string NotificationType = null;
public NotificationReferenceAttribute(string type = null, string typeFromField = null)
{
NotificationType = type;
NotificationTypeFieldName = typeFromField;
}
}
[AttributeUsage(AttributeTargets.Field)]
public sealed class SequenceReferenceAttribute : Attribute
{
public readonly string ImageReference; // The field name in the same trait info that contains the image name.
// The field name in the same trait info that contains the image name.
public readonly string ImageReference;
public readonly bool Prefix;
public readonly bool ActorNameFallback;
public SequenceReferenceAttribute(string imageReference = null, bool prefix = false, bool actorNameFallback = false)
public readonly bool AllowNullImage;
public readonly LintDictionaryReference DictionaryReference;
public SequenceReferenceAttribute(string imageReference = null, bool prefix = false, bool allowNullImage = false,
LintDictionaryReference dictionaryReference = LintDictionaryReference.None)
{
ImageReference = imageReference;
Prefix = prefix;
ActorNameFallback = actorNameFallback;
AllowNullImage = allowNullImage;
DictionaryReference = dictionaryReference;
}
}

View File

@@ -39,10 +39,11 @@ namespace OpenRA.Traits
readonly ICreatesFrozenActors frozenTrait;
readonly Player viewer;
readonly Shroud shroud;
readonly List<WPos> targetablePositions = new List<WPos>();
public Player Owner { get; private set; }
public BitSet<TargetableType> TargetTypes { get; private set; }
public WPos[] TargetablePositions { get; private set; }
public IEnumerable<WPos> TargetablePositions { get { return targetablePositions; } }
public ITooltipInfo TooltipInfo { get; private set; }
public Player TooltipOwner { get; private set; }
@@ -117,7 +118,8 @@ namespace OpenRA.Traits
{
Owner = actor.Owner;
TargetTypes = actor.GetEnabledTargetTypes();
TargetablePositions = actor.GetTargetablePositions().ToArray();
targetablePositions.Clear();
targetablePositions.AddRange(actor.GetTargetablePositions());
Hidden = !actor.CanBeViewedByPlayer(viewer);
if (health != null)

View File

@@ -99,6 +99,7 @@ namespace OpenRA.Traits
readonly ProjectedCellLayer<short> generatedShroudCount;
readonly ProjectedCellLayer<bool> explored;
readonly ProjectedCellLayer<bool> touched;
bool anyCellTouched;
// Per-cell cache of the resolved cell type (shroud/fog/visible)
readonly ProjectedCellLayer<ShroudCellType> resolvedType;
@@ -142,6 +143,7 @@ namespace OpenRA.Traits
generatedShroudCount = new ProjectedCellLayer<short>(map);
explored = new ProjectedCellLayer<bool>(map);
touched = new ProjectedCellLayer<bool>(map);
anyCellTouched = true;
// Defaults to 0 = Shroud
resolvedType = new ProjectedCellLayer<ShroudCellType>(map);
@@ -159,31 +161,42 @@ namespace OpenRA.Traits
void ITick.Tick(Actor self)
{
if (!anyCellTouched)
return;
anyCellTouched = false;
if (OnShroudChanged == null)
return;
foreach (var puv in map.ProjectedCells)
{
if (!touched[puv])
var index = touched.Index(puv);
if (!touched[index])
continue;
touched[puv] = false;
touched[index] = false;
var type = ShroudCellType.Shroud;
if (explored[puv] && (!shroudGenerationEnabled || generatedShroudCount[puv] == 0 || visibleCount[puv] > 0))
if (explored[index])
{
var count = visibleCount[puv];
if (passiveVisibilityEnabled)
count += passiveVisibleCount[puv];
var count = visibleCount[index];
if (!shroudGenerationEnabled || count > 0 || generatedShroudCount[index] == 0)
{
if (passiveVisibilityEnabled)
count += passiveVisibleCount[index];
type = count > 0 ? ShroudCellType.Visible : ShroudCellType.Fog;
type = count > 0 ? ShroudCellType.Visible : ShroudCellType.Fog;
}
}
var oldResolvedType = resolvedType[puv];
resolvedType[puv] = type;
var oldResolvedType = resolvedType[index];
if (type != oldResolvedType)
{
resolvedType[index] = type;
OnShroudChanged(puv);
}
}
Hash = Sync.HashPlayer(self.Owner) + self.World.WorldTick;
@@ -231,21 +244,23 @@ namespace OpenRA.Traits
if (!map.Contains(puv))
continue;
touched[puv] = true;
var index = touched.Index(puv);
touched[index] = true;
anyCellTouched = true;
switch (type)
{
case SourceType.PassiveVisibility:
passiveVisibilityEnabled = true;
passiveVisibleCount[puv]++;
explored[puv] = true;
passiveVisibleCount[index]++;
explored[index] = true;
break;
case SourceType.Visibility:
visibleCount[puv]++;
explored[puv] = true;
visibleCount[index]++;
explored[index] = true;
break;
case SourceType.Shroud:
shroudGenerationEnabled = true;
generatedShroudCount[puv]++;
generatedShroudCount[index]++;
break;
}
}
@@ -261,17 +276,19 @@ namespace OpenRA.Traits
// Cells outside the visible bounds don't increment visibleCount
if (map.Contains(puv))
{
touched[puv] = true;
var index = touched.Index(puv);
touched[index] = true;
anyCellTouched = true;
switch (state.Type)
{
case SourceType.PassiveVisibility:
passiveVisibleCount[puv]--;
passiveVisibleCount[index]--;
break;
case SourceType.Visibility:
visibleCount[puv]--;
visibleCount[index]--;
break;
case SourceType.Shroud:
generatedShroudCount[puv]--;
generatedShroudCount[index]--;
break;
}
}
@@ -284,10 +301,15 @@ namespace OpenRA.Traits
{
foreach (var puv in cells)
{
if (map.Contains(puv) && !explored[puv])
if (map.Contains(puv))
{
touched[puv] = true;
explored[puv] = true;
var index = touched.Index(puv);
if (!explored[index])
{
touched[index] = true;
anyCellTouched = true;
explored[index] = true;
}
}
}
}
@@ -299,10 +321,12 @@ namespace OpenRA.Traits
foreach (var puv in map.ProjectedCells)
{
if (!explored[puv] && s.explored[puv])
var index = touched.Index(puv);
if (!explored[index] && s.explored[index])
{
touched[puv] = true;
explored[puv] = true;
touched[index] = true;
anyCellTouched = true;
explored[index] = true;
}
}
}
@@ -311,10 +335,12 @@ namespace OpenRA.Traits
{
foreach (var puv in map.ProjectedCells)
{
if (!explored[puv])
var index = touched.Index(puv);
if (!explored[index])
{
touched[puv] = true;
explored[puv] = true;
touched[index] = true;
anyCellTouched = true;
explored[index] = true;
}
}
}
@@ -323,9 +349,12 @@ namespace OpenRA.Traits
{
foreach (var puv in map.ProjectedCells)
{
touched[puv] = true;
explored[puv] = (visibleCount[puv] + passiveVisibleCount[puv]) > 0;
var index = touched.Index(puv);
touched[index] = true;
explored[index] = (visibleCount[index] + passiveVisibleCount[index]) > 0;
}
anyCellTouched = true;
}
public bool IsExplored(WPos pos)

View File

@@ -16,7 +16,7 @@ using System.Linq;
namespace OpenRA.Traits
{
public enum TargetType : byte { Invalid, Actor, Terrain, FrozenActor }
public struct Target
public readonly struct Target
{
public static readonly Target[] None = { };
public static readonly Target Invalid = default(Target);
@@ -83,7 +83,7 @@ namespace OpenRA.Traits
}
public static Target FromPos(WPos p) { return new Target(p); }
public static Target FromTargetPositions(Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); }
public static Target FromTargetPositions(in Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); }
public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target(w, c, subCell); }
public static Target FromActor(Actor a) { return a != null ? new Target(a) : Invalid; }
public static Target FromFrozenActor(FrozenActor fa) { return new Target(fa); }

View File

@@ -18,6 +18,7 @@ using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA.Traits
{
@@ -67,7 +68,7 @@ namespace OpenRA.Traits
}
[Flags]
public enum Stance
public enum PlayerRelationship
{
None = 0,
Enemy = 1,
@@ -77,7 +78,7 @@ namespace OpenRA.Traits
public static class StanceExts
{
public static bool HasStance(this Stance s, Stance stance)
public static bool HasStance(this PlayerRelationship s, PlayerRelationship stance)
{
// PERF: Enum.HasFlag is slower and requires allocations.
return (s & stance) == stance;
@@ -127,7 +128,7 @@ namespace OpenRA.Traits
public interface IIssueOrder
{
IEnumerable<IOrderTargeter> Orders { get; }
Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued);
Order IssueOrder(Actor self, IOrderTargeter order, in Target target, bool queued);
}
[Flags]
@@ -146,9 +147,9 @@ namespace OpenRA.Traits
{
string OrderID { get; }
int OrderPriority { get; }
bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor);
bool CanTarget(Actor self, in Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor);
bool IsQueued { get; }
bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers);
bool TargetOverridesSelection(Actor self, in Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers);
}
public interface IResolveOrder { void ResolveOrder(Actor self, Order order); }
@@ -195,7 +196,7 @@ namespace OpenRA.Traits
public interface ITooltipInfo : ITraitInfoInterface
{
string TooltipForPlayerStance(Stance stance);
string TooltipForPlayerStance(PlayerRelationship stance);
bool IsOwnerRowVisible { get; }
}
@@ -267,6 +268,7 @@ namespace OpenRA.Traits
public interface ISelectionDecorations
{
IEnumerable<IRenderable> RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color);
int2 GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin);
}
public interface IMapPreviewSignatureInfo : ITraitInfoInterface
@@ -364,7 +366,27 @@ namespace OpenRA.Traits
}
[RequireExplicitImplementation]
public interface ICreatePlayers { void CreatePlayers(World w); }
public interface ICreatePlayers { void CreatePlayers(World w, MersenneTwister playerRandom); }
[RequireExplicitImplementation]
public interface ICreatePlayersInfo : ITraitInfoInterface
{
void CreateServerPlayers(MapPreview map, Session lobbyInfo, List<GameInformation.Player> players, MersenneTwister playerRandom);
}
[RequireExplicitImplementation]
public interface IAssignSpawnPoints
{
CPos AssignHomeLocation(World world, Session.Client client, MersenneTwister playerRandom);
int SpawnPointForPlayer(Player player);
}
[RequireExplicitImplementation]
public interface IAssignSpawnPointsInfo : ITraitInfoInterface
{
object InitializeState(MapPreview map, Session lobbyInfo);
int AssignSpawnPoint(object state, Session lobbyInfo, Session.Client client, MersenneTwister playerRandom);
}
public interface IBotInfo : ITraitInfoInterface
{
@@ -485,7 +507,10 @@ namespace OpenRA.Traits
bool AlwaysEnabled { get; }
}
public interface IMoveInfo : ITraitInfoInterface { }
public interface IMoveInfo : ITraitInfoInterface
{
Color GetTargetLineColor();
}
[RequireExplicitImplementation]
public interface IGameOver { void GameOver(World world); }
@@ -495,7 +520,7 @@ namespace OpenRA.Traits
int Delay { get; }
bool IsValidAgainst(Actor victim, Actor firedBy);
bool IsValidAgainst(FrozenActor victim, Actor firedBy);
void DoImpact(Target target, WarheadArgs args);
void DoImpact(in Target target, WarheadArgs args);
}
public interface IRulesetLoaded<TInfo> { void RulesetLoaded(Ruleset rules, TInfo info); }
@@ -541,8 +566,8 @@ namespace OpenRA.Traits
{
static readonly Dictionary<string, string> BoolValues = new Dictionary<string, string>()
{
{ true.ToString(), "enabled" },
{ false.ToString(), "disabled" }
{ true.ToString(), "Enabled" },
{ false.ToString(), "Disabled" }
};
public LobbyBooleanOption(string id, string name, string description, bool visible, int displayorder, bool defaultValue, bool locked)
@@ -550,7 +575,7 @@ namespace OpenRA.Traits
public override string ValueChangedMessage(string playerName, string newValue)
{
return playerName + " " + BoolValues[newValue] + " " + Name + ".";
return playerName + " " + BoolValues[newValue].ToLowerInvariant() + " " + Name + ".";
}
}

View File

@@ -31,7 +31,7 @@ namespace OpenRA.UtilityCommands
if (args[2] == "user" || args[2] == "both")
type |= ModRegistration.User;
new ExternalMods().Register(utility.ModData.Manifest, args[1], type);
new ExternalMods().Register(utility.ModData.Manifest, args[1], Enumerable.Empty<string>(), type);
}
}
}

View File

@@ -16,7 +16,7 @@ namespace OpenRA
/// <summary>
/// 3d World rotation.
/// </summary>
public struct WRot : IEquatable<WRot>
public readonly struct WRot : IEquatable<WRot>
{
// The Euler angle representation is a lot more intuitive for public use
public readonly WAngle Roll, Pitch, Yaw;
@@ -84,11 +84,11 @@ namespace OpenRA
public static WRot FromFacing(int facing) { return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing)); }
public static WRot FromYaw(WAngle yaw) { return new WRot(WAngle.Zero, WAngle.Zero, yaw); }
public static WRot operator +(WRot a, WRot b) { return new WRot(a.Roll + b.Roll, a.Pitch + b.Pitch, a.Yaw + b.Yaw); }
public static WRot operator -(WRot a, WRot b) { return new WRot(a.Roll - b.Roll, a.Pitch - b.Pitch, a.Yaw - b.Yaw); }
public static WRot operator -(WRot a) { return new WRot(-a.x, -a.y, -a.z, a.w, -a.Roll, -a.Pitch, -a.Yaw); }
public static WRot operator +(in WRot a, in WRot b) { return new WRot(a.Roll + b.Roll, a.Pitch + b.Pitch, a.Yaw + b.Yaw); }
public static WRot operator -(in WRot a, in WRot b) { return new WRot(a.Roll - b.Roll, a.Pitch - b.Pitch, a.Yaw - b.Yaw); }
public static WRot operator -(in WRot a) { return new WRot(-a.x, -a.y, -a.z, a.w, -a.Roll, -a.Pitch, -a.Yaw); }
public WRot Rotate(WRot rot)
public WRot Rotate(in WRot rot)
{
if (this == None)
return rot;
@@ -104,12 +104,12 @@ namespace OpenRA
return new WRot((int)rx, (int)ry, (int)rz, (int)rw);
}
public static bool operator ==(WRot me, WRot other)
public static bool operator ==(in WRot me, in WRot other)
{
return me.Roll == other.Roll && me.Pitch == other.Pitch && me.Yaw == other.Yaw;
}
public static bool operator !=(WRot me, WRot other) { return !(me == other); }
public static bool operator !=(in WRot me, in WRot other) { return !(me == other); }
public WRot WithRoll(WAngle roll)
{

View File

@@ -44,7 +44,7 @@ namespace OpenRA
public long VerticalLengthSquared { get { return (long)Z * Z; } }
public int VerticalLength { get { return (int)Exts.ISqrt(VerticalLengthSquared); } }
public WVec Rotate(WRot rot)
public WVec Rotate(in WRot rot)
{
rot.AsMatrix(out var mtx);
return Rotate(ref mtx);

View File

@@ -142,6 +142,8 @@ namespace OpenRA
public readonly ScreenMap ScreenMap;
public readonly WorldType Type;
public readonly IValidateOrder[] OrderValidators;
readonly GameInformation gameInfo;
// Hide the OrderManager from mod code
@@ -203,28 +205,14 @@ namespace OpenRA
ActorMap = WorldActor.Trait<IActorMap>();
ScreenMap = WorldActor.Trait<ScreenMap>();
Selection = WorldActor.Trait<ISelection>();
OrderValidators = WorldActor.TraitsImplementing<IValidateOrder>().ToArray();
// Reset mask
LongBitSet<PlayerBitMask>.Reset();
// Add players
// Create an isolated RNG to simplify synchronization between client and server player faction/spawn assignments
var playerRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
foreach (var cmp in WorldActor.TraitsImplementing<ICreatePlayers>())
cmp.CreatePlayers(this);
// Set defaults for any unset stances
foreach (var p in Players)
{
if (!p.Spectating)
AllPlayersMask = AllPlayersMask.Union(p.PlayerMask);
foreach (var q in Players)
{
SetUpPlayerMask(p, q);
if (!p.Stances.ContainsKey(q))
p.Stances[q] = Stance.Neutral;
}
}
cmp.CreatePlayers(this, playerRandom);
Game.Sound.SoundVolumeModifier = 1.0f;
@@ -240,25 +228,6 @@ namespace OpenRA
RulesContainTemporaryBlocker = map.Rules.Actors.Any(a => a.Value.HasTraitInfo<ITemporaryBlockerInfo>());
}
void SetUpPlayerMask(Player p, Player q)
{
if (q.Spectating)
return;
var bitSet = q.PlayerMask;
switch (p.Stances[q])
{
case Stance.Enemy:
case Stance.Neutral:
p.EnemyPlayersMask = p.EnemyPlayersMask.Union(bitSet);
break;
case Stance.Ally:
p.AlliedPlayersMask = p.AlliedPlayersMask.Union(bitSet);
break;
}
}
public void AddToMaps(Actor self, IOccupySpace ios)
{
ActorMap.AddInfluence(self, ios);
@@ -315,6 +284,8 @@ namespace OpenRA
foreach (var player in Players)
gameInfo.AddPlayer(player, OrderManager.LobbyInfo);
gameInfo.DisabledSpawnPoints = OrderManager.LobbyInfo.DisabledSpawnPoints;
var echo = OrderManager.Connection as EchoConnection;
var rc = echo != null ? echo.Recorder : null;
@@ -461,7 +432,7 @@ namespace OpenRA
foreach (var a in actors.Values)
a.Tick();
ActorsWithTrait<ITick>().DoTimed(x => x.Trait.Tick(x.Actor), "Trait");
ApplyToActorsWithTraitTimed<ITick>((Actor actor, ITick trait) => trait.Tick(actor), "Trait");
effects.DoTimed(e => e.Tick(this), "Effect");
}
@@ -473,7 +444,7 @@ namespace OpenRA
// For things that want to update their render state once per tick, ignoring pause state
public void TickRender(WorldRenderer wr)
{
ActorsWithTrait<ITickRender>().DoTimed(x => x.Trait.TickRender(wr, x.Actor), "Render");
ApplyToActorsWithTraitTimed<ITickRender>((Actor actor, ITickRender trait) => trait.TickRender(wr, actor), "Render");
ScreenMap.TickRender();
}
@@ -532,6 +503,11 @@ namespace OpenRA
return TraitDict.ActorsWithTrait<T>();
}
public void ApplyToActorsWithTraitTimed<T>(Action<Actor, T> action, string text)
{
TraitDict.ApplyToActorsWithTraitTimed<T>(action, text);
}
public IEnumerable<Actor> ActorsHavingTrait<T>()
{
return TraitDict.ActorsHavingTrait<T>();
@@ -552,6 +528,15 @@ namespace OpenRA
}
}
public void OnPlayerDisconnected(Player player)
{
var pi = gameInfo.GetPlayer(player);
if (pi == null)
return;
pi.DisconnectFrame = OrderManager.NetFrameNumber;
}
public void RequestGameSave(string filename)
{
// Allow traits to save arbitrary data that will be passed back via IGameSaveTraitData.ResolveTraitData

View File

@@ -87,11 +87,5 @@ namespace OpenRA
}
}
}
public static bool AreMutualAllies(Player a, Player b)
{
return a.Stances[b] == Stance.Ally &&
b.Stances[a] == Stance.Ally;
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<loadFromRemoteSources enabled="true" />
</runtime>
</configuration>

View File

@@ -0,0 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework Condition="'$(Mono)' == ''">net5.0</TargetFramework>
<TargetFramework Condition="'$(Mono)' != ''">netstandard2.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<UseVSHostingProcess>false</UseVSHostingProcess>
<LangVersion>7.3</LangVersion>
<DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
<OutputPath>../bin</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<PlatformTarget>AnyCPU</PlatformTarget>
<ExternalConsole>false</ExternalConsole>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<CodeAnalysisRuleSet>..\OpenRA.ruleset</CodeAnalysisRuleSet>
<Configurations>Release;Debug</Configurations>
<AssemblyName>OpenRA</AssemblyName>
<IsPublishable Condition="'$(CopyGenericLauncher)' == 'False'">false</IsPublishable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Windows'))">win-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('Linux'))">linux-x64</TargetPlatform>
<TargetPlatform Condition="$([MSBuild]::IsOsPlatform('OSX'))">osx-x64</TargetPlatform>
</PropertyGroup>
<ItemGroup>
<!-- Work around an issue where Rider does not detect files in the project root using the default glob -->
<Compile Include="**/*.cs" Exclude="$(DefaultItemExcludes)" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetPlatform)' == 'win-x86'">
<Prefer32bit>true</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Red Alert'">
<StartAction>Project</StartAction>
<StartArguments>Engine.EngineDir=".." Game.Mod=ra</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Dune 2000'">
<StartAction>Project</StartAction>
<StartArguments>Engine.EngineDir=".." Game.Mod=d2k</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Dawn'">
<StartAction>Project</StartAction>
<StartArguments>Engine.EngineDir=".." Game.Mod=cnc</StartArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(RunConfiguration)' == 'Tiberian Sun'">
<StartAction>Project</StartAction>
<StartArguments>Engine.EngineDir=".." Game.Mod=ts</StartArguments>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj" />
<AdditionalFiles Include="../stylecop.json" />
<AdditionalFiles Include="Properties/launchSettings.json" />
</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>
<ItemGroup>
<TrimmerRootAssembly Include="netstandard" />
<TrimmerRootAssembly Include="System.IO.Pipes" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
#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.Diagnostics;
using System.Linq;
namespace OpenRA.Launcher
{
static class Program
{
[STAThread]
static int Main(string[] args)
{
if (Debugger.IsAttached || args.Contains("--just-die"))
return (int)Game.InitializeAndRun(args);
AppDomain.CurrentDomain.UnhandledException += (_, e) => ExceptionHandler.HandleFatalError((Exception)e.ExceptionObject);
try
{
return (int)Game.InitializeAndRun(args);
}
catch (Exception e)
{
ExceptionHandler.HandleFatalError(e);
return (int)RunStatus.Error;
}
}
}
}

View File

@@ -2,19 +2,19 @@
"profiles": {
"Tiberian Dawn": {
"commandName": "Project",
"commandLineArgs": "Game.Mod=cnc"
"commandLineArgs": "Engine.EngineDir=\"..\" Game.Mod=cnc"
},
"Red Alert": {
"commandName": "Project",
"commandLineArgs": "Game.Mod=ra"
"commandLineArgs": "Engine.EngineDir=\"..\" Game.Mod=ra"
},
"Dune 2000": {
"commandName": "Project",
"commandLineArgs": "Game.Mod=d2k"
"commandLineArgs": "Engine.EngineDir=\"..\" Game.Mod=d2k"
},
"Tiberian Sun": {
"commandName": "Project",
"commandLineArgs": "Game.Mod=ts"
"commandLineArgs": "Engine.EngineDir=\"..\" Game.Mod=ts"
}
}
}
}

View File

@@ -24,14 +24,14 @@ namespace OpenRA.Mods.Cnc.Activities
readonly INotifyInfiltration[] notifiers;
Actor enterActor;
public Infiltrate(Actor self, Target target, Infiltrates infiltrates)
: base(self, target, Color.Crimson)
public Infiltrate(Actor self, in Target target, Infiltrates infiltrates, Color? targetLineColor)
: base(self, target, targetLineColor)
{
this.infiltrates = infiltrates;
notifiers = self.TraitsImplementing<INotifyInfiltration>().ToArray();
}
protected override void TickInner(Actor self, Target target, bool targetIsDeadOrHiddenActor)
protected override void TickInner(Actor self, in Target target, bool targetIsDeadOrHiddenActor)
{
if (infiltrates.IsTraitDisabled)
Cancel(self, true);

View File

@@ -26,6 +26,7 @@ namespace OpenRA.Mods.Cnc.Activities
readonly Minelayer minelayer;
readonly AmmoPool[] ammoPools;
readonly IMove movement;
readonly IMoveInfo moveInfo;
readonly RearmableInfo rearmableInfo;
List<CPos> minefield;
@@ -37,6 +38,7 @@ namespace OpenRA.Mods.Cnc.Activities
minelayer = self.Trait<Minelayer>();
ammoPools = self.TraitsImplementing<AmmoPool>().ToArray();
movement = self.Trait<IMove>();
moveInfo = self.Info.TraitInfo<IMoveInfo>();
rearmableInfo = self.Info.TraitInfoOrDefault<RearmableInfo>();
this.minefield = minefield;
}
@@ -69,7 +71,7 @@ namespace OpenRA.Mods.Cnc.Activities
if (rearmableInfo != null && ammoPools.Any(p => p.Info.Name == minelayer.Info.AmmoPoolName && !p.HasAmmo))
{
// Rearm (and possibly repair) at rearm building, then back out here to refill the minefield some more
rearmTarget = self.World.Actors.Where(a => self.Owner.Stances[a.Owner] == Stance.Ally && rearmableInfo.RearmActors.Contains(a.Info.Name))
rearmTarget = self.World.Actors.Where(a => self.Owner.RelationshipWith(a.Owner) == PlayerRelationship.Ally && rearmableInfo.RearmActors.Contains(a.Info.Name))
.ClosestTo(self);
if (rearmTarget == null)
@@ -118,17 +120,17 @@ namespace OpenRA.Mods.Cnc.Activities
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
{
if (returnToBase)
yield return new TargetLineNode(Target.FromActor(rearmTarget), Color.Green);
yield return new TargetLineNode(Target.FromActor(rearmTarget), moveInfo.GetTargetLineColor());
if (minefield == null || minefield.Count == 0)
yield break;
var nextCell = NextValidCell(self);
if (nextCell != null)
yield return new TargetLineNode(Target.FromCell(self.World, nextCell.Value), Color.Crimson);
yield return new TargetLineNode(Target.FromCell(self.World, nextCell.Value), minelayer.Info.TargetLineColor);
foreach (var c in minefield)
yield return new TargetLineNode(Target.FromCell(self.World, c), Color.Crimson, tile: minelayer.Tile);
yield return new TargetLineNode(Target.FromCell(self.World, c), minelayer.Info.TargetLineColor, tile: minelayer.Tile);
}
static bool CanLayMine(Actor self, CPos p)

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