Compare commits

...

60 Commits

Author SHA1 Message Date
abcdefg30
5943ecc943 Correct the support dir location in ExtractSettingsDocsCommand 2020-04-26 12:38:42 +01:00
Paul Chote
c13e078ebd Disable debug callbacks on Intel HD 4000. 2020-04-25 21:03:25 +02:00
Paul Chote
426c3d68a1 Expose GL Profile in settings menu. 2020-04-25 21:03:23 +02:00
Paul Chote
5b6b4ab4ee Restore legacy OpenGL 2.1 support. 2020-04-25 21:03:20 +02:00
Paul Chote
835a9e605a Replace PreferGLES settings flag with GLProfile enum. 2020-04-25 21:03:05 +02:00
Paul Chote
390fbe6718 Split GLProfile from GLFeatures. 2020-04-25 21:00:51 +02:00
abcdefg30
1bf3d090d5 Fix a crash when completing objectives in Allies06 out of order 2020-04-23 21:57:22 +01:00
abcdefg30
9e279b9987 Treat transit-only tiles as invalid locations for minelayers 2020-04-18 13:35:27 +01:00
Adam Mitchell
4b6780bc67 Fix units stuck on transit-only when resupplying 2020-04-17 11:10:10 +02:00
abcdefg30
0a9aa488db Adapt the utility commands to import crates as well 2020-04-17 10:49:53 +02:00
abcdefg30
97ecfa548e Remove a TODO about grey nod colors 2020-04-17 10:49:38 +02:00
abcdefg30
82a98ae6e6 Set the wcrate and scrate sequences up 2020-04-17 10:49:25 +02:00
abcdefg30
32b241eae7 Add missing money crates to TD campaign missions 2020-04-17 10:49:09 +02:00
abcdefg30
e8084ade68 Move money crates to a default in the shared campaign rules 2020-04-17 10:48:58 +02:00
Paul Chote
16c2cbf577 Work around and explain color picker conversion issue. 2020-04-17 10:40:32 +02:00
Paul Chote
b51980a9ea Fix map-specific factions remaining selected when changing map. 2020-04-16 16:49:16 +02:00
Paul Chote
631a61e219 Block profiles with revoked keys from joining auth-only servers. 2020-04-16 16:43:33 +02:00
abcdefg30
5ff2546a5d Add more engineers to the wave in nod07
Otherwise buildings will only be damaged
2020-04-16 13:21:53 +02:00
abcdefg30
bf2640308a Fix APC reinforcements in nod07a and b 2020-04-16 13:21:52 +02:00
abcdefg30
63cee6a0e3 Fix a crash in nod07b 2020-04-16 13:21:50 +02:00
Paul Chote
cab47c4975 Disable IP tooltip in skirmish games. 2020-04-15 23:16:07 +02:00
Paul Chote
6385c24875 Switch GeoIP database from MaxMind to IP2Location.
The IP2Location data is licensed under CC BY-SA, which
allows us to distribute the database with releases.
2020-04-15 23:16:05 +02:00
Paul Chote
1d1bafd836 Fix long player locations overrunning the player tooltip. 2020-04-15 23:16:03 +02:00
netnazgul
5cacce3c93 Modify preset colors to not get flagged by color validator 2020-04-14 18:31:35 +02:00
Paul Chote
798c92abeb Fix mine layer desync.
World.FogObscures depends on the local RenderPlayer and should not
be used from simulation code!
2020-04-12 23:06:25 +02:00
abcdefg30
9236a05d53 Fix a yaml error in GDI08a 2020-04-07 20:59:46 +02:00
netnazgul
fc7b3d83e5 Fix tile errors on the map "Pie of Animosity" 2020-04-07 20:53:59 +02:00
Punsho
00ddf82e85 RA Balance patch 2020-03-29 21:51:52 +02:00
Paul Chote
c10ebb455b Fix actors with no footprint leaving stale data when deleted. 2020-03-29 21:08:25 +02:00
Paul Chote
d999a0534b Move selectableActor check inside InputOverridesSelection. 2020-03-29 13:26:05 +02:00
Ivaylo Draganov
02454fb764 Adjust spacing and width in editor category dropdown 2020-03-28 20:41:31 +00:00
abcdefg30
df81857abe Adjust the map visibility panel height 2020-03-28 20:41:23 +00:00
abcdefg30
ae0745b542 Start with randomized wind strength 2020-03-28 19:47:21 +01:00
Paul Chote
89b5e34320 Fix and simplify WeatherOverlay:
- Fix rendering issues
- Track particles in world pixels instead of screen pixels
- Removed un/underused fade in/out support
- Update wind once per tick instead of once per particle
- Make Particle struct readonly
2020-03-28 19:47:19 +01:00
Paul Chote
47cc6eda7e Make the right edge of the airfield transitable. 2020-03-28 19:14:43 +01:00
Paul Chote
6da23ed3d7 Fix minelayers leaking enemy mine positions through the fog. 2020-03-28 18:49:24 +01:00
Paul Chote
3ee4613a98 Fix SpriteEffect updating twice in the first tick. 2020-03-28 17:18:12 +01:00
Paul Chote
616bfbe553 Fix and simplify ScrollPanelWidget thumb rect calculation. 2020-03-26 16:53:09 +01:00
Ivaylo Draganov
ecbcc9c8bb Align lobby bits in the player tab in TD 2020-03-25 13:02:34 +01:00
Paul Chote
068ce9b215 Restore ability to configure RMB orders + RMB panning. 2020-03-25 12:37:43 +01:00
Paul Chote
170bcd500c Remove text caching from CncLoadScreen.
We have repeatedly failed at invalidating these
cached values when things change, so the small perf
win is not worth the hassle.
2020-03-25 12:21:15 +01:00
Paul Chote
6efb40c9a6 Fix detection circle line rendering. 2020-03-24 20:59:58 +01:00
Ivaylo Draganov
3c92c2d789 Adjust the stroke of the muted indicator glyph 2020-03-24 16:57:48 +01:00
Paul Chote
f823d20a86 Replace deprecated native OpenAL with OpenAL Soft on macOS. 2020-03-23 11:17:46 +01:00
Paul Chote
ca4b97cd03 Remove invalid caching from GCOT. 2020-03-23 11:17:08 +01:00
Matthias Mailänder
8b5e322891 Remove .lua scripts from the .NET solution file. 2020-03-21 21:24:03 +01:00
unknown
70e1c4bb0c Add gdi09ea 2020-03-21 21:11:14 +01:00
abcdefg30
8690ad4e19 Add support for destroying enemy carryalls 2020-03-21 11:00:39 +01:00
abcdefg30
a01233c3df Add support for an announcement function for carryall reinforcements 2020-03-21 11:00:14 +01:00
abcdefg30
d0d55b3fc1 Add Ordos06a 2020-03-21 10:59:59 +01:00
abcdefg30
2a26a07ce6 Let VS2019 remove a duplicate line from the solution 2020-03-21 10:59:45 +01:00
abcdefg30
65e088204f Fix the settings tooltip container being overwritten ingame 2020-03-20 16:06:46 +01:00
Michael Silber
77e38ceea9 Add gdi08a 2020-03-17 19:04:53 +01:00
Paul Chote
989d1475b9 Update macOS launcher to fix "View Logs" button. 2020-03-16 19:17:12 +00:00
Paul Chote
580a96cda4 Cache CandidateMovementCells within the same tick. 2020-03-12 17:07:45 +01:00
Paul Chote
5a8964aabe Fix infantry switching subcells and blocking eachother while moving. 2020-03-11 15:40:58 +01:00
Paul Chote
c9dcf305c9 Fix FreeSubCell ignoring preferred subcell requests. 2020-03-11 15:40:56 +01:00
Paul Chote
5489d6909c Fix pathing across transit-only cells. 2020-03-11 15:40:54 +01:00
Paul Chote
85412a6d98 Fix variable naming in Locomotor. 2020-03-11 15:40:53 +01:00
abcdefg30
b69806123a Fix aircraft not taking off properly 2020-03-08 17:19:56 +01:00
130 changed files with 4760 additions and 841 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@@ -40,7 +40,7 @@
WHITELISTED_OPENRA_ASSEMBLIES = OpenRA.Game.exe OpenRA.Utility.exe OpenRA.Platforms.Default.dll OpenRA.Mods.Common.dll OpenRA.Mods.Cnc.dll OpenRA.Mods.D2k.dll OpenRA.Game.dll
# These are explicitly shipped alongside our core files by the packaging script
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll MaxMind.Db.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.dll
WHITELISTED_THIRDPARTY_ASSEMBLIES = ICSharpCode.SharpZipLib.dll FuzzyLogicLibrary.dll Eluant.dll rix0rrr.BeaconLib.dll Open.Nat.dll SDL2-CS.dll OpenAL-CS.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
@@ -157,10 +157,11 @@ ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
else
@$(MSBUILD) -t:build -p:Configuration=Release
endif
@./fetch-geoip.sh
clean:
@ $(MSBUILD) -t:clean
@-$(RM_F) *.config
@-$(RM_F) *.config IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
@-$(RM_F) *.exe *.dll *.dylib ./OpenRA*/*.dll *.pdb mods/**/*.dll mods/**/*.pdb *.resources
@-$(RM_RF) ./*/bin ./*/obj
@-$(RM_RF) ./thirdparty/download
@@ -218,6 +219,7 @@ install-engine:
@$(INSTALL_DATA) VERSION "$(DATA_INSTALL_DIR)/VERSION"
@$(INSTALL_DATA) AUTHORS "$(DATA_INSTALL_DIR)/AUTHORS"
@$(INSTALL_DATA) COPYING "$(DATA_INSTALL_DIR)/COPYING"
@$(INSTALL_DATA) IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP "$(DATA_INSTALL_DIR)/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
@@ -227,7 +229,6 @@ install-engine:
@$(INSTALL_PROGRAM) ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) Open.Nat.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) MaxMind.Db.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) rix0rrr.BeaconLib.dll "$(DATA_INSTALL_DIR)"
install-common-mod-files:

View File

@@ -15,9 +15,16 @@ using OpenRA.Primitives;
namespace OpenRA
{
public enum GLProfile
{
Modern,
Embedded,
Legacy
}
public interface IPlatform
{
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay);
IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile);
ISoundEngine CreateSound(string device);
IFont CreateFont(byte[] data);
}
@@ -60,6 +67,10 @@ namespace OpenRA
void SetHardwareCursor(IHardwareCursor cursor);
void SetRelativeMouseMode(bool mode);
void SetScaleModifier(float scale);
GLProfile GLProfile { get; }
GLProfile[] SupportedGLProfiles { get; }
}
public interface IGraphicsContext : IDisposable

View File

@@ -19,6 +19,11 @@ namespace OpenRA.Graphics
[Flags]
public enum ScrollDirection { None = 0, Up = 1, Left = 2, Down = 4, Right = 8 }
public interface INotifyViewportZoomExtentsChanged
{
void ViewportZoomExtentsChanged(float minZoom, float maxZoom);
}
public static class ViewportExts
{
public static bool Includes(this ScrollDirection d, ScrollDirection s)
@@ -237,6 +242,9 @@ namespace OpenRA.Graphics
Zoom = minZoom;
else
Zoom = Zoom.Clamp(minZoom, maxZoom);
foreach (var t in worldRenderer.World.WorldActor.TraitsImplementing<INotifyViewportZoomExtentsChanged>())
t.ViewportZoomExtentsChanged(minZoom, maxZoom);
}
public CPos ViewToWorld(int2 view)

View File

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

View File

@@ -58,10 +58,6 @@
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MaxMind.Db">
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
<Private>False</Private>
</Reference>
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

View File

@@ -96,7 +96,7 @@ namespace OpenRA.Orders
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAtMouse(xy)
.Where(a => !a.Actor.IsDead)
.Where(a => !a.Actor.IsDead && a.Actor.Info.HasTraitInfo<SelectableInfo>() && (a.Actor.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(a.Actor)))
.WithHighestSelectionPriority(xy, mi.Modifiers);
if (actor == null)

View File

@@ -66,7 +66,10 @@ namespace OpenRA
this.platform = platform;
var resolution = GetResolution(graphicSettings);
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, graphicSettings.VideoDisplay);
Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height),
graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize,
graphicSettings.VideoDisplay, graphicSettings.GLProfile);
Context = Window.Context;
TempBufferSize = graphicSettings.BatchSize;
@@ -301,6 +304,8 @@ namespace OpenRA
public Size NativeResolution { get { return Window.NativeWindowSize; } }
public float WindowScale { get { return Window.EffectiveWindowScale; } }
public float NativeWindowScale { get { return Window.NativeWindowScale; } }
public GLProfile GLProfile { get { return Window.GLProfile; } }
public GLProfile[] SupportedGLProfiles { get { return Window.SupportedGLProfiles; } }
public interface IBatchRenderer { void Flush(); }

View File

@@ -149,7 +149,8 @@ namespace OpenRA.Server
randomSeed = (int)DateTime.Now.ToBinary();
GeoIP.Initialize(settings.GeoIPDatabase);
if (type != ServerType.Local && settings.EnableGeoIP)
GeoIP.Initialize();
if (UPnP.Status == UPnPStatus.Enabled)
UPnP.ForwardPort(Settings.ListenPort, Settings.ListenPort).Wait();
@@ -348,7 +349,7 @@ namespace OpenRA.Server
{
Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name),
IPAddress = ipAddress.ToString(),
AnonymizedIPAddress = Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
AnonymizedIPAddress = Type != ServerType.Local && Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null,
Location = GeoIP.LookupCountry(ipAddress),
Index = newConn.PlayerIndex,
PreferredColor = handshake.Client.PreferredColor,
@@ -498,10 +499,16 @@ namespace OpenRA.Server
profile.ProfileName, profile.ProfileID);
}
else if (profile.KeyRevoked)
{
profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
else
{
profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
}
else
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -165,6 +165,12 @@ namespace OpenRA.Mods.Common.Activities
public override void Cancel(Actor self, bool keepQueue = false)
{
// HACK: force move activities to ignore the transit-only cells when cancelling
// The idle handler will take over and move them into a safe cell
if (ChildActivity != null)
foreach (var c in ChildActivity.ActivitiesImplementing<Move>())
c.Cancel(self, false, true);
foreach (var t in transportCallers)
t.MovementCancelled(self);

View File

@@ -62,16 +62,18 @@ namespace OpenRA.Mods.Common.Effects
world.ScreenMap.Add(this, pos, anim.Image);
initialized = true;
}
else
{
anim.Tick();
anim.Tick();
pos = posFunc();
world.ScreenMap.Update(this, pos, anim.Image);
pos = posFunc();
world.ScreenMap.Update(this, pos, anim.Image);
}
}
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (!visibleThroughFog && world.FogObscures(pos))
if (!initialized || (!visibleThroughFog && world.FogObscures(pos)))
return SpriteRenderable.None;
return anim.Render(pos, wr.Palette(palette));

View File

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

View File

@@ -122,6 +122,7 @@ namespace OpenRA.Mods.Common.LoadScreens
}
// Saved settings may have been invalidated by a hardware change
Game.Settings.Graphics.GLProfile = Game.Renderer.GLProfile;
Game.Settings.Graphics.VideoDisplay = Game.Renderer.CurrentDisplay;
// If a ModContent section is defined then we need to make sure that the

View File

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

View File

@@ -84,7 +84,7 @@ namespace OpenRA.Mods.Common.Traits
{
if (reservedForAircraft.GetActorBelow() == self)
{
if (rallyPoint != null)
if (rallyPoint != null && rallyPoint.Path.Count > 0)
foreach (var cell in rallyPoint.Path)
reservedFor.QueueActivity(reservedForAircraft.MoveTo(cell, 1, targetLineColor: Color.Green));
else

View File

@@ -35,9 +35,7 @@ namespace OpenRA.Mods.Common.Traits
ConditionManager conditionManager;
int conditionToken = ConditionManager.InvalidConditionToken;
string cachedTerrain;
CPos cachedLocation;
public GrantConditionOnTerrain(ActorInitializer init, GrantConditionOnTerrainInfo info)
{
@@ -53,9 +51,10 @@ namespace OpenRA.Mods.Common.Traits
void ITick.Tick(Actor self)
{
var loc = self.Location;
if (conditionManager == null || loc == cachedLocation)
if (conditionManager == null)
return;
// The terrain type may change between ticks without the actor moving
var currentTerrain = loc.Layer == 0 ? self.World.Map.GetTerrainInfo(loc).Type :
tileSet[self.World.GetCustomMovementLayers()[loc.Layer].GetTerrainIndex(loc)].Type;
@@ -69,7 +68,6 @@ namespace OpenRA.Mods.Common.Traits
}
cachedTerrain = currentTerrain;
cachedLocation = loc;
}
}
}

View File

@@ -866,7 +866,13 @@ namespace OpenRA.Mods.Common.Traits
void INotifyBecomingIdle.OnBecomingIdle(Actor self)
{
if (self.Location.Layer == 0)
{
// Make sure that units aren't left idling in a transit-only cell
// HACK: activities should be making sure that this can't happen in the first place!
if (!Locomotor.CanStayInCell(self.Location))
self.QueueActivity(MoveTo(self.Location, evaluateNearestMovableCell: true));
return;
}
var cml = self.World.WorldActor.TraitsImplementing<ICustomMovementLayer>()
.First(l => l.Index == self.Location.Layer);

View File

@@ -276,7 +276,7 @@ namespace OpenRA.Mods.Common.Traits
public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, bool checkTransient = true)
{
if (preferredSubCell > SubCell.Any && !AnyActorsAt(cell, preferredSubCell, checkTransient))
if (preferredSubCell != SubCell.Any && !AnyActorsAt(cell, preferredSubCell, checkTransient))
return preferredSubCell;
if (!AnyActorsAt(cell))
@@ -291,7 +291,7 @@ namespace OpenRA.Mods.Common.Traits
public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell, Func<Actor, bool> checkIfBlocker)
{
if (preferredSubCell > SubCell.Any && !AnyActorsAt(cell, preferredSubCell, checkIfBlocker))
if (preferredSubCell != SubCell.Any && !AnyActorsAt(cell, preferredSubCell, checkIfBlocker))
return preferredSubCell;
if (!AnyActorsAt(cell))

View File

@@ -133,12 +133,13 @@ namespace OpenRA.Mods.Common.Traits
if (!preview.Bounds.IsEmpty)
screenMap.Add(preview, preview.Bounds);
foreach (var kv in preview.Footprint)
AddPreviewLocation(preview, kv.Key);
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
if (!preview.Footprint.Any())
AddPreviewLocation(preview, worldRenderer.World.Map.CellContaining(preview.CenterPosition));
var footprint = preview.Footprint.Select(kv => kv.Key).ToArray();
if (!footprint.Any())
footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) };
foreach (var cell in footprint)
AddPreviewLocation(preview, cell);
if (!initialSetup)
{
@@ -154,16 +155,21 @@ namespace OpenRA.Mods.Common.Traits
previews.Remove(preview);
screenMap.Remove(preview);
foreach (var kv in preview.Footprint)
// Fallback to the actor's CenterPosition for the ActorMap if it has no Footprint
var footprint = preview.Footprint.Select(kv => kv.Key).ToArray();
if (!footprint.Any())
footprint = new[] { worldRenderer.World.Map.CellContaining(preview.CenterPosition) };
foreach (var cell in footprint)
{
List<EditorActorPreview> list;
if (!cellMap.TryGetValue(kv.Key, out list))
if (!cellMap.TryGetValue(cell, out list))
continue;
list.Remove(preview);
if (!list.Any())
cellMap.Remove(kv.Key);
cellMap.Remove(cell);
}
UpdateNeighbours(preview.Footprint);

View File

@@ -304,7 +304,7 @@ namespace OpenRA.Mods.Common.Traits
var otherActors = subCell == SubCell.FullCell ? world.ActorMap.GetActorsAt(cell) : world.ActorMap.GetActorsAt(cell, subCell);
foreach (var otherActor in otherActors)
if (IsBlockedBy(actor, otherActor, ignoreActor, check, cellFlag))
if (IsBlockedBy(actor, otherActor, ignoreActor, cell, check, cellFlag))
return false;
return true;
@@ -322,7 +322,7 @@ namespace OpenRA.Mods.Common.Traits
if (check > BlockedByActor.None)
{
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check, GetCache(cell).CellFlag);
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, cell, check, GetCache(cell).CellFlag);
if (!sharesCell)
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
@@ -336,14 +336,14 @@ namespace OpenRA.Mods.Common.Traits
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
}
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, BlockedByActor check, CellFlag cellFlag)
bool IsBlockedBy(Actor actor, Actor otherActor, Actor ignoreActor, CPos cell, BlockedByActor check, CellFlag cellFlag)
{
if (otherActor == ignoreActor)
return false;
// If the check allows: We are not blocked by units that we can force to move out of the way.
if (check <= BlockedByActor.Immovable && cellFlag.HasCellFlag(CellFlag.HasMovableActor) &&
self.Owner.Stances[otherActor.Owner] == Stance.Ally)
actor.Owner.Stances[otherActor.Owner] == Stance.Ally)
{
var mobile = otherActor.TraitOrDefault<Mobile>();
if (mobile != null && !mobile.IsTraitDisabled && !mobile.IsTraitPaused && !mobile.IsImmovable)
@@ -352,14 +352,22 @@ namespace OpenRA.Mods.Common.Traits
// If the check allows: we are not blocked by moving units.
if (check <= BlockedByActor.Stationary && cellFlag.HasCellFlag(CellFlag.HasMovingActor) &&
IsMoving(self, otherActor))
IsMoving(actor, otherActor))
return false;
if (cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker))
{
// If there is a temporary blocker in our path, but we can remove it, we are not blocked.
var temporaryBlocker = otherActor.TraitOrDefault<ITemporaryBlocker>();
if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, self))
if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, actor))
return false;
}
if (cellFlag.HasCellFlag(CellFlag.HasTransitOnlyActor))
{
// Transit only tiles should not block movement
var building = otherActor.TraitOrDefault<Building>();
if (building != null && building.TransitOnlyCells().Contains(cell))
return false;
}
@@ -371,7 +379,7 @@ namespace OpenRA.Mods.Common.Traits
// PERF: Avoid LINQ.
var crushables = otherActor.TraitsImplementing<ICrushable>();
foreach (var crushable in crushables)
if (crushable.CrushableBy(otherActor, self, Info.Crushes))
if (crushable.CrushableBy(otherActor, actor, Info.Crushes))
return false;
return true;

View File

@@ -9,9 +9,10 @@
*/
#endregion
using System.Collections.Generic;
using System;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
@@ -70,245 +71,178 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new WeatherOverlay(init.World, this); }
}
public class WeatherOverlay : ITick, IRenderAboveWorld
public class WeatherOverlay : ITick, IRenderAboveWorld, INotifyViewportZoomExtentsChanged
{
readonly WeatherOverlayInfo info;
readonly World world;
struct Particle
{
public float PosX;
public float PosY;
public int Size;
public float DirectionScatterX;
public float Gravity;
public float SwingOffset;
public float SwingSpeed;
public int SwingDirection;
public float SwingAmplitude;
public Color Color;
public Color TailColor;
public readonly float2 Pos;
public readonly int Size;
public readonly float DirectionScatterX;
public readonly float Gravity;
public readonly float SwingOffset;
public readonly float SwingSpeed;
public readonly int SwingDirection;
public readonly float SwingAmplitude;
public readonly Color Color;
public readonly Color TailColor;
public Particle(WeatherOverlayInfo info, MersenneTwister r, Rectangle viewport)
{
var x = r.Next(viewport.Left, viewport.Right);
var y = r.Next(viewport.Top, viewport.Bottom);
Pos = new int2(x, y);
Size = r.Next(info.ParticleSize[0], info.ParticleSize[1] + 1);
DirectionScatterX = info.ScatterDirection[0] + r.Next(info.ScatterDirection[1] - info.ScatterDirection[0]);
Gravity = float2.Lerp(info.Gravity[0], info.Gravity[1], r.NextFloat());
SwingOffset = float2.Lerp(info.SwingOffset[0], info.SwingOffset[1], r.NextFloat());
SwingSpeed = float2.Lerp(info.SwingSpeed[0], info.SwingSpeed[1], r.NextFloat());
SwingDirection = r.Next(2) == 0 ? 1 : -1;
SwingAmplitude = float2.Lerp(info.SwingAmplitude[0], info.SwingAmplitude[1], r.NextFloat());
Color = info.ParticleColors.Random(r);
TailColor = Color.FromArgb(info.LineTailAlphaValue, Color.R, Color.G, Color.B);
}
Particle(Particle source)
{
Pos = source.Pos;
Size = source.Size;
DirectionScatterX = source.DirectionScatterX;
Gravity = source.Gravity;
SwingOffset = source.SwingOffset;
SwingSpeed = source.SwingSpeed;
SwingDirection = source.SwingDirection;
SwingAmplitude = source.SwingAmplitude;
Color = source.Color;
TailColor = source.TailColor;
}
public Particle(Particle source, float2 pos)
: this(source)
{
Pos = pos;
}
public Particle(Particle source, float2 pos, int swingDirection, float swingOffset)
: this(source)
{
Pos = pos;
SwingDirection = swingDirection;
SwingOffset = swingOffset;
}
}
readonly List<Particle> particleList = new List<Particle>();
readonly int maxParticleCount;
readonly WeatherOverlayInfo info;
readonly World world;
enum ParticleCountFaderType { Hold, FadeIn, FadeOut }
ParticleCountFaderType particleCountFader = ParticleCountFaderType.FadeIn;
float targetWindXOffset = 0f;
float currentWindXOffset = 0f;
int currentWindIndex = 0;
long windTickCountdown = 1500;
float2 antiScrollPrevTopLeft;
float windStrength;
int targetWindStrengthIndex;
long windUpdateCountdown;
Particle[] particles;
Size viewportSize;
public WeatherOverlay(World world, WeatherOverlayInfo info)
{
this.info = info;
this.world = world;
currentWindIndex = info.WindLevels.Length / 2;
targetWindXOffset = info.WindLevels[0];
maxParticleCount = CalculateParticleCount(Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height);
targetWindStrengthIndex = info.ChangingWindLevel ? world.LocalRandom.Next(info.WindLevels.Length) : 0;
windUpdateCountdown = world.LocalRandom.Next(info.WindTick[0], info.WindTick[1]);
windStrength = info.WindLevels[targetWindStrengthIndex];
}
int CalculateParticleCount(int x, int y)
void INotifyViewportZoomExtentsChanged.ViewportZoomExtentsChanged(float minZoom, float maxZoom)
{
return (int)(x * y * info.ParticleDensityFactor / 10000);
}
// Track particles in a viewport fixed to the minimum zoom level
var s = (1f / minZoom * new float2(Game.Renderer.NativeResolution)).ToInt2();
viewportSize = new Size(s.X, s.Y);
void SpawnParticles(int count, int rangeY, int spawnChancePercent)
{
for (var i = 0; i < count; i++)
{
if (Game.CosmeticRandom.Next(100) < spawnChancePercent)
{
var tempColor = info.ParticleColors.Random(Game.CosmeticRandom);
var tempColorTail = Color.FromArgb(info.LineTailAlphaValue, tempColor.R, tempColor.G, tempColor.B);
var tempSwingDirection = Game.CosmeticRandom.Next(2) == 0 ? 1 : -1;
particleList.Add(
new Particle
{
PosX = Game.CosmeticRandom.Next(Game.Renderer.Resolution.Width),
PosY = Game.CosmeticRandom.Next(rangeY),
Size = Game.CosmeticRandom.Next(info.ParticleSize[0], info.ParticleSize[1] + 1),
DirectionScatterX = info.ScatterDirection[0] + Game.CosmeticRandom.Next(info.ScatterDirection[1] - info.ScatterDirection[0]),
Gravity = float2.Lerp(info.Gravity[0], info.Gravity[1], Game.CosmeticRandom.NextFloat()),
SwingOffset = float2.Lerp(info.SwingOffset[0], info.SwingOffset[1], Game.CosmeticRandom.NextFloat()),
SwingSpeed = float2.Lerp(info.SwingSpeed[0], info.SwingSpeed[1], Game.CosmeticRandom.NextFloat()),
SwingDirection = tempSwingDirection,
SwingAmplitude = float2.Lerp(info.SwingAmplitude[0], info.SwingAmplitude[1], Game.CosmeticRandom.NextFloat()),
Color = tempColor,
TailColor = tempColorTail
});
}
}
}
void ParticlesCountLogic(WorldRenderer wr)
{
// Logic to switch between the states of the particleCountFader
if (particleCountFader == ParticleCountFaderType.Hold && particleList.Count < maxParticleCount)
particleCountFader = ParticleCountFaderType.FadeIn;
else if (particleCountFader == ParticleCountFaderType.FadeIn && particleList.Count >= maxParticleCount)
particleCountFader = ParticleCountFaderType.Hold;
else if (particleCountFader == ParticleCountFaderType.FadeOut && particleList.Count == 0)
particleCountFader = ParticleCountFaderType.Hold;
// Do the fade functions
if (particleCountFader == ParticleCountFaderType.FadeIn)
FadeInParticleCount(wr);
else if (particleCountFader == ParticleCountFaderType.FadeOut)
FadeOutParticleCount(wr);
}
void FadeInParticleCount(WorldRenderer wr)
{
SpawnParticles(1, 0, 100);
// Remove Particles, which are getting replaced from the top to the bottom by the "EdgeCheckReplace",
// when scrolling down, as long as the FadeIn is not completed,
// to avoid having particles at the top and bottom, but not in the middle of the screen.
for (var i = 0; i < particleList.Count; i++)
if (particleList[i].PosY < 0)
particleList.RemoveAt(i);
// Add Particles when the weather is fading in and scrolling up, to fill areas above
if (antiScrollPrevTopLeft.Y > wr.Viewport.TopLeft.Y)
{
// Get delta Y and limit to the max value
var tempRangeY = antiScrollPrevTopLeft.Y - wr.Viewport.TopLeft.Y;
var tempParticleCount = CalculateParticleCount(Game.Renderer.Resolution.Width, (int)tempRangeY);
if (particleList.Count + tempParticleCount > maxParticleCount)
tempParticleCount = maxParticleCount - particleList.Count;
SpawnParticles(tempParticleCount, (int)tempRangeY, 50);
}
}
void FadeOutParticleCount(WorldRenderer wr)
{
for (var i = 0; i < particleList.Count; i++)
if (particleList[i].PosY > (Game.Renderer.Resolution.Height - particleList[i].Gravity))
particleList.RemoveAt(i);
}
void XAxisSwing(ref Particle tempParticle)
{
// Direction turn
if (tempParticle.SwingOffset < -tempParticle.SwingAmplitude || tempParticle.SwingOffset > tempParticle.SwingAmplitude)
tempParticle.SwingDirection *= -1;
// Perform the X-Axis-Swing
tempParticle.SwingOffset += tempParticle.SwingDirection * tempParticle.SwingSpeed;
// Randomly distribute particles within the initial viewport
var particleCount = viewportSize.Width * viewportSize.Height * info.ParticleDensityFactor / 10000;
particles = new Particle[particleCount];
var rect = new Rectangle(int2.Zero, viewportSize);
for (var i = 0; i < particles.Length; i++)
particles[i] = new Particle(info, world.LocalRandom, rect);
}
void ITick.Tick(Actor self)
{
windTickCountdown--;
}
if (!info.ChangingWindLevel || info.WindLevels.Length == 1)
return;
void WindLogic(ref Particle tempParticle)
{
if (!info.ChangingWindLevel)
targetWindXOffset = info.WindLevels[0];
else if (windTickCountdown <= 0)
if (--windUpdateCountdown <= 0)
{
windTickCountdown = Game.CosmeticRandom.Next(info.WindTick[0], info.WindTick[1]);
if (Game.CosmeticRandom.Next(2) == 1 && currentWindIndex > 0)
{
currentWindIndex--;
targetWindXOffset = info.WindLevels[currentWindIndex];
}
else if (currentWindIndex < info.WindLevels.Length - 1)
{
currentWindIndex++;
targetWindXOffset = info.WindLevels[currentWindIndex];
}
windUpdateCountdown = self.World.LocalRandom.Next(info.WindTick[0], info.WindTick[1]);
if (targetWindStrengthIndex > 0 && self.World.LocalRandom.Next(2) == 1)
targetWindStrengthIndex--;
else if (targetWindStrengthIndex < info.WindLevels.Length - 1)
targetWindStrengthIndex++;
}
// Fading the wind in little steps towards the TargetWindOffset
var targetWindLevel = info.WindLevels[targetWindStrengthIndex];
if (info.InstantWindChanges)
currentWindXOffset = targetWindXOffset;
else if (currentWindXOffset != targetWindXOffset)
windStrength = targetWindLevel;
else if (Math.Abs(windStrength - targetWindLevel) > 0.01f)
{
if (currentWindXOffset > targetWindXOffset)
currentWindXOffset -= 0.00001f;
else if (currentWindXOffset < targetWindXOffset)
currentWindXOffset += 0.00001f;
}
}
void Movement(ref Particle tempParticle)
{
tempParticle.PosX += tempParticle.DirectionScatterX + tempParticle.SwingOffset + currentWindXOffset;
tempParticle.PosY += tempParticle.Gravity;
}
// AntiScroll keeps the particles in place when scrolling the viewport
void AntiScroll(ref Particle tempParticle, WorldRenderer wr)
{
tempParticle.PosX += antiScrollPrevTopLeft.X - wr.Viewport.TopLeft.X;
tempParticle.PosY += antiScrollPrevTopLeft.Y - wr.Viewport.TopLeft.Y;
}
void EdgeCheckReplace(ref Particle tempParticle, WorldRenderer wr)
{
tempParticle.PosX %= Game.Renderer.Resolution.Width;
if (tempParticle.PosX < 0)
tempParticle.PosX += Game.Renderer.Resolution.Width;
tempParticle.PosY %= Game.Renderer.Resolution.Height;
if (tempParticle.PosY < 0 && particleCountFader != ParticleCountFaderType.FadeIn)
tempParticle.PosY += Game.Renderer.Resolution.Height;
}
void UpdateWeatherOverlay(WorldRenderer wr)
{
if (!world.Paused)
ParticlesCountLogic(wr);
for (var i = 0; i < particleList.Count; i++)
{
Particle tempParticle = particleList[i];
if (!world.Paused)
{
XAxisSwing(ref tempParticle);
WindLogic(ref tempParticle);
Movement(ref tempParticle);
}
AntiScroll(ref tempParticle, wr);
EdgeCheckReplace(ref tempParticle, wr);
particleList[i] = tempParticle;
}
antiScrollPrevTopLeft = wr.Viewport.TopLeft;
}
void DrawWeatherOverlay(WorldRenderer wr)
{
var topLeft = wr.Viewport.TopLeft;
foreach (var item in particleList)
{
var tempPos = new float2(item.PosX + topLeft.X, item.PosY + topLeft.Y);
if (info.UseSquares)
Game.Renderer.WorldRgbaColorRenderer.FillRect(tempPos, tempPos + new float2(item.Size, item.Size), item.Color);
else
{
var tempPosTail = new float2(topLeft.X + item.PosX - currentWindXOffset, item.PosY - (item.Gravity * 2 / 3) + topLeft.Y);
Game.Renderer.WorldRgbaColorRenderer.DrawLine(tempPos, tempPosTail, item.Size, item.TailColor);
}
if (windStrength > targetWindLevel)
windStrength -= 0.01f;
else if (windStrength < targetWindLevel)
windStrength += 0.01f;
}
}
void IRenderAboveWorld.RenderAboveWorld(Actor self, WorldRenderer wr)
{
UpdateWeatherOverlay(wr);
var center = wr.Viewport.CenterLocation;
var viewport = new Rectangle(center - new int2(viewportSize) / 2, viewportSize);
var wcr = Game.Renderer.WorldRgbaColorRenderer;
DrawWeatherOverlay(wr);
for (var i = 0; i < particles.Length; i++)
{
// Simulate wind and gravity effects on the particle
var p = particles[i];
if (!world.Paused)
{
var swingDirection = p.SwingDirection;
if (p.SwingOffset < -p.SwingAmplitude || p.SwingOffset > p.SwingAmplitude)
swingDirection *= -1;
var swingOffset = p.SwingOffset + p.SwingDirection * p.SwingSpeed;
var pos = p.Pos + new float2(p.DirectionScatterX + p.SwingOffset + windStrength, p.Gravity);
particles[i] = p = new Particle(p, pos, swingDirection, swingOffset);
}
// Move the particle back inside the viewport if necessary
if (!viewport.Contains(p.Pos.ToInt2()))
{
var dx = (p.Pos.X - viewport.Left) % viewport.Width;
var dy = (p.Pos.Y - viewport.Top) % viewport.Height;
if (dx < 0)
dx += viewport.Width;
if (dy < 0)
dy += viewport.Height;
particles[i] = p = new Particle(p, new float2(viewport.Left + dx, viewport.Top + dy));
}
// Render the particle
// We must provide a z coordinate to stop the GL near and far Z limits from culling the geometry
var a = new float3(p.Pos.X, p.Pos.Y, p.Pos.Y);
if (info.UseSquares)
{
var b = a + new float2(p.Size, p.Size);
wcr.FillRect(a, b, p.Color);
}
else
{
var tail = p.Pos + new float2(-windStrength, -p.Gravity * 2 / 3);
var b = new float3(tail.X, tail.Y, tail.Y);
wcr.DrawLine(a, b, p.Size, p.TailColor);
}
}
}
}
}

View File

@@ -40,8 +40,14 @@ namespace OpenRA.Mods.Common.UtilityCommands
Console.WriteLine("All settings can be changed by starting the game via a command-line parameter like `Game.Mod=ra`.");
Console.WriteLine();
Console.WriteLine("## Location");
Console.WriteLine("* Windows: `My Documents\\OpenRA\\settings.yaml`");
Console.WriteLine("* Windows: `%APPDATA%\\OpenRA\\settings.yaml`");
Console.WriteLine("* Mac OS X: `~/Library/Application Support/OpenRA/settings.yaml`");
Console.WriteLine("* Linux `~/.config/openra/settings.yaml`");
Console.WriteLine();
Console.WriteLine(
"Older releases (before playtest-20190825) used different locations, " +
"which newer versions may continue to use in some circumstances:");
Console.WriteLine("* Windows: `%USERPROFILE%\\Documents\\OpenRA\\settings.yaml`");
Console.WriteLine("* Linux `~/.openra/settings.yaml`");
Console.WriteLine();
Console.WriteLine(

View File

@@ -72,7 +72,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic
mixer.SetPaletteRange(validator.HsvSaturationRange[0], validator.HsvSaturationRange[1], validator.HsvValueRange[0], validator.HsvValueRange[1]);
mixer.Set(initialColor);
hueSlider.Value = HueFromColor(initialColor);
onChange(mixer.Color);
// HACK: the value returned from the color mixer will generally not
// be equal to the given initialColor due to its internal RGB -> HSL -> RGB
// conversion. This conversion can sometimes convert a valid initial value
// into an invalid (too close to terrain / another player) color.
// We use the original colour here instead of the mixer color to make sure
// that we keep the player's previous colour value if they don't change anything
onChange(initialColor);
// Setup tab controls
var mixerTab = widget.Get("MIXER_TAB");

View File

@@ -77,12 +77,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var mouseControlDescClassic = widget.Get("MOUSE_CONTROL_DESC_CLASSIC");
mouseControlDescClassic.IsVisible = () => gs.UseClassicMouseStyle;
var classicScrollRight = mouseControlDescClassic.Get("DESC_SCROLL_RIGHT");
classicScrollRight.IsVisible = () => !gs.ClassicMouseMiddleScroll;
var classicScrollMiddle = mouseControlDescClassic.Get("DESC_SCROLL_MIDDLE");
classicScrollMiddle.IsVisible = () => gs.ClassicMouseMiddleScroll;
var mouseControlDescModern = widget.Get("MOUSE_CONTROL_DESC_MODERN");
mouseControlDescModern.IsVisible = () => !gs.UseClassicMouseStyle;
@@ -92,6 +86,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var container in new[] { mouseControlDescClassic, mouseControlDescModern })
{
var classicScrollRight = container.Get("DESC_SCROLL_RIGHT");
classicScrollRight.IsVisible = () => gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton;
var classicScrollMiddle = container.Get("DESC_SCROLL_MIDDLE");
classicScrollMiddle.IsVisible = () => !gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton;
var zoomDesc = container.Get("DESC_ZOOM");
zoomDesc.IsVisible = () => gs.ZoomModifier == Modifiers.None;

View File

@@ -322,8 +322,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (client.Location != null)
{
var locationFont = Game.Renderer.Fonts[locationLabel.Font];
var locationWidth = widget.Bounds.Width - 2 * locationLabel.Bounds.X;
var location = WidgetUtils.TruncateText(client.Location, locationWidth, locationFont);
locationLabel.IsVisible = () => true;
locationLabel.GetText = () => client.Location;
locationLabel.GetText = () => location;
widget.Bounds.Height += locationLabel.Bounds.Height;
ipLabel.Bounds.Y += locationLabel.Bounds.Height;
adminLabel.Bounds.Y += locationLabel.Bounds.Height;

View File

@@ -29,6 +29,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
static readonly WindowMode OriginalGraphicsMode;
static readonly int2 OriginalGraphicsWindowedSize;
static readonly int2 OriginalGraphicsFullscreenSize;
static readonly GLProfile OriginalGLProfile;
static readonly bool OriginalServerDiscoverNatDevices;
readonly Dictionary<PanelType, Action> leavePanelActions = new Dictionary<PanelType, Action>();
@@ -57,6 +58,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
OriginalVideoDisplay = original.Graphics.VideoDisplay;
OriginalGraphicsWindowedSize = original.Graphics.WindowedSize;
OriginalGraphicsFullscreenSize = original.Graphics.FullscreenSize;
OriginalGLProfile = original.Graphics.GLProfile;
OriginalServerDiscoverNatDevices = original.Server.DiscoverNatDevices;
}
@@ -98,6 +100,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
current.Graphics.VideoDisplay != OriginalVideoDisplay ||
current.Graphics.WindowedSize != OriginalGraphicsWindowedSize ||
current.Graphics.FullscreenSize != OriginalGraphicsFullscreenSize ||
current.Graphics.GLProfile != OriginalGLProfile ||
current.Server.DiscoverNatDevices != OriginalServerDiscoverNatDevices)
{
Action restart = () =>
@@ -253,16 +256,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
windowModeDropdown.GetText = () => ds.Mode == WindowMode.Windowed ?
"Windowed" : ds.Mode == WindowMode.Fullscreen ? "Fullscreen (Legacy)" : "Fullscreen";
var modeChangesDesc = panel.Get("MODE_CHANGES_DESC");
modeChangesDesc.IsVisible = () => ds.Mode != WindowMode.Windowed && (ds.Mode != OriginalGraphicsMode ||
ds.VideoDisplay != OriginalVideoDisplay);
var displaySelectionDropDown = panel.Get<DropDownButtonWidget>("DISPLAY_SELECTION_DROPDOWN");
displaySelectionDropDown.OnMouseDown = _ => ShowDisplaySelectionDropdown(displaySelectionDropDown, ds);
var displaySelectionLabel = new CachedTransform<int, string>(i => "Display {0}".F(i + 1));
displaySelectionDropDown.GetText = () => displaySelectionLabel.Update(ds.VideoDisplay);
displaySelectionDropDown.IsDisabled = () => Game.Renderer.DisplayCount < 2;
var glProfileLabel = new CachedTransform<GLProfile, string>(p => p.ToString());
var glProfileDropdown = panel.Get<DropDownButtonWidget>("GL_PROFILE_DROPDOWN");
glProfileDropdown.OnMouseDown = _ => ShowGLProfileDropdown(glProfileDropdown, ds);
glProfileDropdown.GetText = () => glProfileLabel.Update(ds.GLProfile);
glProfileDropdown.IsDisabled = () => Game.Renderer.SupportedGLProfiles.Length < 2;
var statusBarsDropDown = panel.Get<DropDownButtonWidget>("STATUS_BAR_DROPDOWN");
statusBarsDropDown.OnMouseDown = _ => ShowStatusBarsDropdown(statusBarsDropDown, gs);
statusBarsDropDown.GetText = () => gs.StatusBars == StatusBarsType.Standard ?
@@ -307,10 +312,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var windowHeight = panel.Get<TextFieldWidget>("WINDOW_HEIGHT");
var origHeightText = windowHeight.Text = ds.WindowedSize.Y.ToString();
windowHeight.Text = ds.WindowedSize.Y.ToString();
var windowChangesDesc = panel.Get("WINDOW_CHANGES_DESC");
windowChangesDesc.IsVisible = () => ds.Mode == WindowMode.Windowed &&
(ds.Mode != OriginalGraphicsMode || origWidthText != windowWidth.Text || origHeightText != windowHeight.Text);
var restartDesc = panel.Get("RESTART_REQUIRED_DESC");
restartDesc.IsVisible = () => ds.Mode != OriginalGraphicsMode || ds.VideoDisplay != OriginalVideoDisplay || ds.GLProfile != OriginalGLProfile ||
(ds.Mode == WindowMode.Windowed && (origWidthText != windowWidth.Text || origHeightText != windowHeight.Text));
var frameLimitCheckbox = panel.Get<CheckboxWidget>("FRAME_LIMIT_CHECKBOX");
var frameLimitOrigLabel = frameLimitCheckbox.Text;
@@ -380,6 +386,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
ds.CapFramerate = dds.CapFramerate;
ds.MaxFramerate = dds.MaxFramerate;
ds.Language = dds.Language;
ds.GLProfile = dds.GLProfile;
ds.Mode = dds.Mode;
ds.VideoDisplay = dds.VideoDisplay;
ds.WindowedSize = dds.WindowedSize;
@@ -505,7 +512,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
var gs = Game.Settings.Game;
BindCheckboxPref(panel, "CLASSIC_MOUSE_MIDDLE_SCROLL_CHECKBOX", gs, "ClassicMouseMiddleScroll");
BindCheckboxPref(panel, "ALTERNATE_SCROLL_CHECKBOX", gs, "UseAlternateScrollButton");
BindCheckboxPref(panel, "EDGESCROLL_CHECKBOX", gs, "ViewportEdgeScroll");
BindCheckboxPref(panel, "LOCKMOUSE_CHECKBOX", gs, "LockMouseWindow");
BindSliderPref(panel, "ZOOMSPEED_SLIDER", gs, "ZoomSpeed");
@@ -520,23 +527,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic
mouseScrollDropdown.OnMouseDown = _ => ShowMouseScrollDropdown(mouseScrollDropdown, gs);
mouseScrollDropdown.GetText = () => gs.MouseScroll.ToString();
var classicMouseMiddleScrollCheckbox = panel.Get<CheckboxWidget>("CLASSIC_MOUSE_MIDDLE_SCROLL_CHECKBOX");
classicMouseMiddleScrollCheckbox.IsVisible = () => gs.UseClassicMouseStyle;
var mouseControlDescClassic = panel.Get("MOUSE_CONTROL_DESC_CLASSIC");
mouseControlDescClassic.IsVisible = () => gs.UseClassicMouseStyle;
var classicScrollRight = mouseControlDescClassic.Get("DESC_SCROLL_RIGHT");
classicScrollRight.IsVisible = () => !gs.ClassicMouseMiddleScroll;
var classicScrollMiddle = mouseControlDescClassic.Get("DESC_SCROLL_MIDDLE");
classicScrollMiddle.IsVisible = () => gs.ClassicMouseMiddleScroll;
var mouseControlDescModern = panel.Get("MOUSE_CONTROL_DESC_MODERN");
mouseControlDescModern.IsVisible = () => !gs.UseClassicMouseStyle;
foreach (var container in new[] { mouseControlDescClassic, mouseControlDescModern })
{
var classicScrollRight = container.Get("DESC_SCROLL_RIGHT");
classicScrollRight.IsVisible = () => gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton;
var classicScrollMiddle = container.Get("DESC_SCROLL_MIDDLE");
classicScrollMiddle.IsVisible = () => !gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton;
var zoomDesc = container.Get("DESC_ZOOM");
zoomDesc.IsVisible = () => gs.ZoomModifier == Modifiers.None;
@@ -635,7 +639,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
gs.UseClassicMouseStyle = dgs.UseClassicMouseStyle;
gs.MouseScroll = dgs.MouseScroll;
gs.ClassicMouseMiddleScroll = dgs.ClassicMouseMiddleScroll;
gs.UseAlternateScrollButton = dgs.UseAlternateScrollButton;
gs.LockMouseWindow = dgs.LockMouseWindow;
gs.ViewportEdgeScroll = dgs.ViewportEdgeScroll;
gs.ViewportEdgeScrollStep = dgs.ViewportEdgeScrollStep;
@@ -875,6 +879,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, Enumerable.Range(0, Game.Renderer.DisplayCount), setupItem);
}
static void ShowGLProfileDropdown(DropDownButtonWidget dropdown, GraphicSettings s)
{
Func<GLProfile, ScrollItemWidget, ScrollItemWidget> setupItem = (o, itemTemplate) =>
{
var item = ScrollItemWidget.Setup(itemTemplate,
() => s.GLProfile == o,
() => s.GLProfile = o);
var label = o.ToString();
item.Get<LabelWidget>("LABEL").GetText = () => label;
return item;
};
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, Game.Renderer.SupportedGLProfiles, setupItem);
}
static void ShowTargetLinesDropdown(DropDownButtonWidget dropdown, GameSettings s)
{
var options = new Dictionary<string, TargetLinesType>()

View File

@@ -146,13 +146,16 @@ namespace OpenRA.Mods.Common.Widgets
UpdateSmoothScrolling();
var rb = RenderBounds;
var scrollbarHeight = rb.Height - 2 * ScrollbarWidth;
var thumbHeight = ContentHeight == 0 ? 0 : Math.Max(MinimumThumbSize, (int)(scrollbarHeight * Math.Min(rb.Height * 1f / ContentHeight, 1f)));
var thumbOrigin = rb.Y + ScrollbarWidth + (int)((scrollbarHeight - thumbHeight) * (-1f * currentListOffset / (ContentHeight - rb.Height)));
if (thumbHeight == scrollbarHeight)
thumbHeight = 0;
// Scroll thumb is only visible if the content does not fit within the panel bounds
var thumbHeight = 0;
var thumbOrigin = rb.Y + ScrollbarWidth;
if (ContentHeight > rb.Height)
{
thumbHeight = Math.Max(MinimumThumbSize, scrollbarHeight * rb.Height / ContentHeight);
thumbOrigin += (int)((scrollbarHeight - thumbHeight) * currentListOffset / (rb.Height - ContentHeight));
}
switch (ScrollBar)
{

View File

@@ -318,7 +318,7 @@ namespace OpenRA.Mods.Common.Widgets
}
var gs = Game.Settings.Game;
var scrollButton = gs.UseClassicMouseStyle && !gs.ClassicMouseMiddleScroll ? MouseButton.Right : MouseButton.Middle;
var scrollButton = gs.UseClassicMouseStyle ^ gs.UseAlternateScrollButton ? MouseButton.Right : MouseButton.Middle;
var scrollType = mi.Button.HasFlag(scrollButton) ? gs.MouseScroll : MouseScrollType.Disabled;
if (scrollType == MouseScrollType.Disabled)

View File

@@ -124,19 +124,13 @@ namespace OpenRA.Mods.Common.Widgets
{
if (useClassicMouseStyle && HasMouseFocus)
{
if (!IsValidDragbox && World.Selection.Actors.Any() && !multiClick)
if (!IsValidDragbox && World.Selection.Actors.Any() && !multiClick && uog.InputOverridesSelection(World, mousePos, mi))
{
var selectableActor = World.ScreenMap.ActorsAtMouse(mousePos).Select(a => a.Actor).Any(x =>
x.Info.HasTraitInfo<SelectableInfo>() && (x.Owner.IsAlliedWith(World.RenderPlayer) || !World.FogObscures(x)));
if (!selectableActor || uog.InputOverridesSelection(World, mousePos, mi))
{
// Order units instead of selecting
ApplyOrders(World, mi);
isDragging = false;
YieldMouseFocus(mi);
return true;
}
// Order units instead of selecting
ApplyOrders(World, mi);
isDragging = false;
YieldMouseFocus(mi);
return true;
}
}

View File

@@ -16,9 +16,9 @@ namespace OpenRA.Platforms.Default
{
public class DefaultPlatform : IPlatform
{
public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay)
public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile)
{
return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize, videoDisplay);
return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize, videoDisplay, profile);
}
public ISoundEngine CreateSound(string device)

View File

@@ -50,7 +50,7 @@ namespace OpenRA.Platforms.Default
OpenGL.glBindRenderbuffer(OpenGL.GL_RENDERBUFFER, depth);
OpenGL.CheckGLError();
var glDepth = OpenGL.Features.HasFlag(OpenGL.GLFeatures.GLES) ? OpenGL.GL_DEPTH_COMPONENT16 : OpenGL.GL_DEPTH_COMPONENT;
var glDepth = OpenGL.Profile == GLProfile.Embedded ? OpenGL.GL_DEPTH_COMPONENT16 : OpenGL.GL_DEPTH_COMPONENT;
OpenGL.glRenderbufferStorage(OpenGL.GL_RENDERBUFFER, glDepth, size.Width, size.Height);
OpenGL.CheckGLError();

View File

@@ -29,12 +29,11 @@ namespace OpenRA.Platforms.Default
public enum GLFeatures
{
None = 0,
Core = 1,
GLES = 2,
DebugMessagesCallback = 4,
ESReadFormatBGRA = 8
DebugMessagesCallback = 1,
ESReadFormatBGRA = 2,
}
public static GLProfile Profile { get; private set; }
public static GLFeatures Features { get; private set; }
public static string Version { get; private set; }
@@ -479,7 +478,7 @@ namespace OpenRA.Platforms.Default
#endregion
public static void Initialize()
public static void Initialize(bool preferLegacyProfile)
{
try
{
@@ -489,13 +488,18 @@ namespace OpenRA.Platforms.Default
glGetError = Bind<GetError>("glGetError");
glGetStringInternal = Bind<GetString>("glGetString");
glGetStringiInternal = Bind<GetStringi>("glGetStringi");
glGetIntegerv = Bind<GetIntegerv>("glGetIntegerv");
}
catch (Exception e)
{
throw new InvalidProgramException("Failed to initialize low-level OpenGL bindings. GPU information is not available.", e);
}
DetectGLFeatures();
if (!DetectGLFeatures(preferLegacyProfile))
{
WriteGraphicsLog("Unsupported OpenGL version: " + glGetString(GL_VERSION));
throw new InvalidProgramException("OpenGL Version Error: See graphics.log for details.");
}
// Allow users to force-disable the debug message callback feature to work around driver bugs
if (Features.HasFlag(GLFeatures.DebugMessagesCallback) && Game.Settings.Graphics.DisableGLDebugMessageCallback)
@@ -509,10 +513,12 @@ namespace OpenRA.Platforms.Default
Features ^= GLFeatures.DebugMessagesCallback;
}
if (!Features.HasFlag(GLFeatures.Core))
// Older Intel on Windows is broken too
if (Features.HasFlag(GLFeatures.DebugMessagesCallback) && Platform.CurrentPlatform == PlatformType.Windows)
{
WriteGraphicsLog("Unsupported OpenGL version: " + glGetString(GL_VERSION));
throw new InvalidProgramException("OpenGL Version Error: See graphics.log for details.");
var renderer = glGetString(GL_RENDERER);
if (renderer.Contains("HD Graphics") && !renderer.Contains("UHD Graphics"))
Features ^= GLFeatures.DebugMessagesCallback;
}
// Setup the debug message callback handler
@@ -520,7 +526,7 @@ namespace OpenRA.Platforms.Default
{
try
{
var suffix = Features.HasFlag(GLFeatures.GLES) ? "KHR" : "";
var suffix = Profile == GLProfile.Embedded ? "KHR" : "";
glDebugMessageCallback = Bind<DebugMessageCallback>("glDebugMessageCallback" + suffix);
glDebugMessageInsert = Bind<DebugMessageInsert>("glDebugMessageInsert" + suffix);
@@ -545,7 +551,6 @@ namespace OpenRA.Platforms.Default
glViewport = Bind<Viewport>("glViewport");
glClear = Bind<Clear>("glClear");
glClearColor = Bind<ClearColor>("glClearColor");
glGetIntegerv = Bind<GetIntegerv>("glGetIntegerv");
glFinish = Bind<Finish>("glFinish");
glCreateProgram = Bind<CreateProgram>("glCreateProgram");
glUseProgram = Bind<UseProgram>("glUseProgram");
@@ -574,10 +579,7 @@ namespace OpenRA.Platforms.Default
glBufferData = Bind<BufferData>("glBufferData");
glBufferSubData = Bind<BufferSubData>("glBufferSubData");
glDeleteBuffers = Bind<DeleteBuffers>("glDeleteBuffers");
glGenVertexArrays = Bind<GenVertexArrays>("glGenVertexArrays");
glBindVertexArray = Bind<BindVertexArray>("glBindVertexArray");
glBindAttribLocation = Bind<BindAttribLocation>("glBindAttribLocation");
glBindFragDataLocation = Bind<BindFragDataLocation>("glBindFragDataLocation");
glVertexAttribPointer = Bind<VertexAttribPointer>("glVertexAttribPointer");
glEnableVertexAttribArray = Bind<EnableVertexAttribArray>("glEnableVertexAttribArray");
glDisableVertexAttribArray = Bind<DisableVertexAttribArray>("glDisableVertexAttribArray");
@@ -597,16 +599,39 @@ namespace OpenRA.Platforms.Default
glGetTexImage = Bind<GetTexImage>("glGetTexImage");
glTexParameteri = Bind<TexParameteri>("glTexParameteri");
glTexParameterf = Bind<TexParameterf>("glTexParameterf");
glGenFramebuffers = Bind<GenFramebuffers>("glGenFramebuffers");
glBindFramebuffer = Bind<BindFramebuffer>("glBindFramebuffer");
glFramebufferTexture2D = Bind<FramebufferTexture2D>("glFramebufferTexture2D");
glDeleteFramebuffers = Bind<DeleteFramebuffers>("glDeleteFramebuffers");
glGenRenderbuffers = Bind<GenRenderbuffers>("glGenRenderbuffers");
glBindRenderbuffer = Bind<BindRenderbuffer>("glBindRenderbuffer");
glRenderbufferStorage = Bind<RenderbufferStorage>("glRenderbufferStorage");
glDeleteRenderbuffers = Bind<DeleteRenderbuffers>("glDeleteRenderbuffers");
glFramebufferRenderbuffer = Bind<FramebufferRenderbuffer>("glFramebufferRenderbuffer");
glCheckFramebufferStatus = Bind<CheckFramebufferStatus>("glCheckFramebufferStatus");
if (Profile != GLProfile.Legacy)
{
glGenVertexArrays = Bind<GenVertexArrays>("glGenVertexArrays");
glBindVertexArray = Bind<BindVertexArray>("glBindVertexArray");
glBindFragDataLocation = Bind<BindFragDataLocation>("glBindFragDataLocation");
glGenFramebuffers = Bind<GenFramebuffers>("glGenFramebuffers");
glBindFramebuffer = Bind<BindFramebuffer>("glBindFramebuffer");
glFramebufferTexture2D = Bind<FramebufferTexture2D>("glFramebufferTexture2D");
glDeleteFramebuffers = Bind<DeleteFramebuffers>("glDeleteFramebuffers");
glGenRenderbuffers = Bind<GenRenderbuffers>("glGenRenderbuffers");
glBindRenderbuffer = Bind<BindRenderbuffer>("glBindRenderbuffer");
glRenderbufferStorage = Bind<RenderbufferStorage>("glRenderbufferStorage");
glDeleteRenderbuffers = Bind<DeleteRenderbuffers>("glDeleteRenderbuffers");
glFramebufferRenderbuffer = Bind<FramebufferRenderbuffer>("glFramebufferRenderbuffer");
glCheckFramebufferStatus = Bind<CheckFramebufferStatus>("glCheckFramebufferStatus");
}
else
{
glGenVertexArrays = null;
glBindVertexArray = null;
glBindFragDataLocation = null;
glGenFramebuffers = Bind<GenFramebuffers>("glGenFramebuffersEXT");
glBindFramebuffer = Bind<BindFramebuffer>("glBindFramebufferEXT");
glFramebufferTexture2D = Bind<FramebufferTexture2D>("glFramebufferTexture2DEXT");
glDeleteFramebuffers = Bind<DeleteFramebuffers>("glDeleteFramebuffersEXT");
glGenRenderbuffers = Bind<GenRenderbuffers>("glGenRenderbuffersEXT");
glBindRenderbuffer = Bind<BindRenderbuffer>("glBindRenderbufferEXT");
glRenderbufferStorage = Bind<RenderbufferStorage>("glRenderbufferStorageEXT");
glDeleteRenderbuffers = Bind<DeleteRenderbuffers>("glDeleteRenderbuffersEXT");
glFramebufferRenderbuffer = Bind<FramebufferRenderbuffer>("glFramebufferRenderbufferEXT");
glCheckFramebufferStatus = Bind<CheckFramebufferStatus>("glCheckFramebufferStatusEXT");
}
}
catch (Exception e)
{
@@ -620,8 +645,9 @@ namespace OpenRA.Platforms.Default
return (T)(object)Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress(name), typeof(T));
}
public static void DetectGLFeatures()
public static bool DetectGLFeatures(bool preferLegacyProfile)
{
var hasValidConfiguration = false;
try
{
Version = glGetString(GL_VERSION);
@@ -641,20 +667,34 @@ namespace OpenRA.Platforms.Default
var hasBGRA = SDL.SDL_GL_ExtensionSupported("GL_EXT_texture_format_BGRA8888") == SDL.SDL_bool.SDL_TRUE;
if (Version.Contains(" ES") && hasBGRA && major >= 3)
{
Features = GLFeatures.Core | GLFeatures.GLES;
hasValidConfiguration = true;
Profile = GLProfile.Embedded;
if (SDL.SDL_GL_ExtensionSupported("GL_EXT_read_format_bgra") == SDL.SDL_bool.SDL_TRUE)
Features |= GLFeatures.ESReadFormatBGRA;
}
else if (major > 3 || (major == 3 && minor >= 2))
Features = GLFeatures.Core;
{
hasValidConfiguration = true;
Profile = GLProfile.Modern;
}
// Debug callbacks were introduced in GL 4.3
var hasDebugMessagesCallback = SDL.SDL_GL_ExtensionSupported("GL_KHR_debug") == SDL.SDL_bool.SDL_TRUE;
if (hasDebugMessagesCallback)
Features |= GLFeatures.DebugMessagesCallback;
if (preferLegacyProfile || (major == 2 && minor == 1) || (major == 3 && minor < 2))
{
if (SDL.SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object") == SDL.SDL_bool.SDL_TRUE)
{
hasValidConfiguration = true;
Profile = GLProfile.Legacy;
}
}
}
catch (Exception) { }
return hasValidConfiguration;
}
public static void WriteGraphicsLog(string message)
@@ -677,10 +717,15 @@ namespace OpenRA.Platforms.Default
Log.Write("graphics", "Shader Version: {0}", glGetString(GL_SHADING_LANGUAGE_VERSION));
Log.Write("graphics", "Available extensions:");
int extensionCount;
glGetIntegerv(GL_NUM_EXTENSIONS, out extensionCount);
for (var i = 0; i < extensionCount; i++)
Log.Write("graphics", glGetStringi(GL_EXTENSIONS, (uint)i));
if (Profile != GLProfile.Legacy)
{
int extensionCount;
glGetIntegerv(GL_NUM_EXTENSIONS, out extensionCount);
for (var i = 0; i < extensionCount; i++)
Log.Write("graphics", glGetStringi(GL_EXTENSIONS, (uint)i));
}
else
Log.Write("graphics", glGetString(GL_EXTENSIONS));
}
public static void CheckGLError()

View File

@@ -35,14 +35,17 @@ namespace OpenRA.Platforms.Default
if (context == IntPtr.Zero || SDL.SDL_GL_MakeCurrent(window.Window, context) < 0)
throw new InvalidOperationException("Can not create OpenGL context. (Error: {0})".F(SDL.SDL_GetError()));
OpenGL.Initialize();
OpenGL.Initialize(window.GLProfile == GLProfile.Legacy);
OpenGL.CheckGLError();
uint vao;
OpenGL.CheckGLError();
OpenGL.glGenVertexArrays(1, out vao);
OpenGL.CheckGLError();
OpenGL.glBindVertexArray(vao);
OpenGL.CheckGLError();
if (OpenGL.Profile != GLProfile.Legacy)
{
uint vao;
OpenGL.glGenVertexArrays(1, out vao);
OpenGL.CheckGLError();
OpenGL.glBindVertexArray(vao);
OpenGL.CheckGLError();
}
OpenGL.glEnableVertexAttribArray(Shader.VertexPosAttributeIndex);
OpenGL.CheckGLError();

View File

@@ -12,6 +12,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using OpenRA.Primitives;
using SDL2;
@@ -34,6 +35,8 @@ namespace OpenRA.Platforms.Default
float windowScale = 1f;
int2? lockedMousePosition;
float scaleModifier;
readonly GLProfile profile;
readonly GLProfile[] supportedProfiles;
internal IntPtr Window
{
@@ -105,12 +108,31 @@ namespace OpenRA.Platforms.Default
}
}
public GLProfile GLProfile
{
get
{
lock (syncObject)
return profile;
}
}
public GLProfile[] SupportedGLProfiles
{
get
{
lock (syncObject)
return supportedProfiles;
}
}
public event Action<float, float, float, float> OnWindowScaleChanged = (oldNative, oldEffective, newNative, newEffective) => { };
[DllImport("user32.dll")]
static extern bool SetProcessDPIAware();
public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay)
public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode,
float scaleModifier, int batchSize, int videoDisplay, GLProfile requestProfile)
{
// Lock the Window/Surface properties until initialization is complete
lock (syncObject)
@@ -122,31 +144,24 @@ namespace OpenRA.Platforms.Default
SetProcessDPIAware();
SDL.SDL_Init(SDL.SDL_INIT_NOPARACHUTE | SDL.SDL_INIT_VIDEO);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 0);
// Decide between OpenGL and OpenGL ES rendering
// Test whether we can use the preferred renderer and fall back to the other if that fails
// If neither works we will throw a graphics error later when trying to create the real window
bool useGLES;
if (Game.Settings.Graphics.PreferGLES)
useGLES = CanCreateGLWindow(3, 0, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES);
// Decide which OpenGL profile to use.
// We first need to query the available profiles on Windows/Linux.
// On macOS, known/consistent OpenGL support is provided by the OS.
if (Platform.CurrentPlatform == PlatformType.OSX)
supportedProfiles = new[] { GLProfile.Modern, GLProfile.Legacy };
else
useGLES = !CanCreateGLWindow(3, 2, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE);
supportedProfiles = new[] { GLProfile.Modern, GLProfile.Embedded, GLProfile.Legacy }
.Where(CanCreateGLWindow)
.ToArray();
var glMajor = 3;
var glMinor = useGLES ? 0 : 2;
var glProfile = useGLES ? SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES : SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE;
if (!supportedProfiles.Any())
throw new InvalidOperationException("No supported OpenGL profiles were found.");
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, glMajor);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, glMinor);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)glProfile);
Console.WriteLine("Using SDL 2 with OpenGL{0} renderer", useGLES ? " ES" : "");
profile = supportedProfiles.Contains(requestProfile) ? requestProfile : supportedProfiles.First();
SetSDLAttributes(profile);
Console.WriteLine("Using SDL 2 with OpenGL ({0}) renderer", profile);
if (videoDisplay < 0 || videoDisplay >= DisplayCount)
videoDisplay = 0;
@@ -449,12 +464,38 @@ namespace OpenRA.Platforms.Default
return input.SetClipboardText(text);
}
static bool CanCreateGLWindow(int major, int minor, SDL.SDL_GLprofile profile)
static void SetSDLAttributes(GLProfile profile)
{
SDL.SDL_GL_ResetAttributes();
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 0);
switch (profile)
{
case GLProfile.Modern:
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE);
break;
case GLProfile.Embedded:
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES);
break;
case GLProfile.Legacy:
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 1);
break;
}
}
static bool CanCreateGLWindow(GLProfile profile)
{
// Implementation inspired by TestIndividualGLVersion from Veldrid
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, major);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, minor);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)profile);
SetSDLAttributes(profile);
var flags = SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN | SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL;
var window = SDL.SDL_CreateWindow("", 0, 0, 1, 1, flags);

View File

@@ -23,6 +23,7 @@ namespace OpenRA.Platforms.Default
public const int TexMetadataAttributeIndex = 2;
readonly Dictionary<string, int> samplers = new Dictionary<string, int>();
readonly Dictionary<int, int> legacySizeUniforms = new Dictionary<int, int>();
readonly Dictionary<int, ITexture> textures = new Dictionary<int, ITexture>();
readonly Queue<int> unbindTextures = new Queue<int>();
readonly uint program;
@@ -33,7 +34,9 @@ namespace OpenRA.Platforms.Default
var filename = Path.Combine(Platform.GameDir, "glsl", name + "." + ext);
var code = File.ReadAllText(filename);
var version = OpenGL.Features.HasFlag(OpenGL.GLFeatures.GLES) ? "300 es" : "140";
var version = OpenGL.Profile == GLProfile.Embedded ? "300 es" :
OpenGL.Profile == GLProfile.Legacy ? "120" : "140";
code = code.Replace("{VERSION}", version);
var shader = OpenGL.glCreateShader(type);
@@ -80,8 +83,12 @@ namespace OpenRA.Platforms.Default
OpenGL.CheckGLError();
OpenGL.glBindAttribLocation(program, TexMetadataAttributeIndex, "aVertexTexMetadata");
OpenGL.CheckGLError();
OpenGL.glBindFragDataLocation(program, 0, "fragColor");
OpenGL.CheckGLError();
if (OpenGL.Profile != GLProfile.Legacy)
{
OpenGL.glBindFragDataLocation(program, 0, "fragColor");
OpenGL.CheckGLError();
}
OpenGL.glAttachShader(program, vertexShader);
OpenGL.CheckGLError();
@@ -132,6 +139,13 @@ namespace OpenRA.Platforms.Default
OpenGL.glUniform1i(loc, nextTexUnit);
OpenGL.CheckGLError();
if (OpenGL.Profile == GLProfile.Legacy)
{
var sizeLoc = OpenGL.glGetUniformLocation(program, sampler + "Size");
if (sizeLoc >= 0)
legacySizeUniforms.Add(nextTexUnit, sizeLoc);
}
nextTexUnit++;
}
}
@@ -146,13 +160,21 @@ namespace OpenRA.Platforms.Default
// bind the textures
foreach (var kv in textures)
{
var id = ((ITextureInternal)kv.Value).ID;
var texture = (ITextureInternal)kv.Value;
// Evict disposed textures from the cache
if (OpenGL.glIsTexture(id))
if (OpenGL.glIsTexture(texture.ID))
{
OpenGL.glActiveTexture(OpenGL.GL_TEXTURE0 + kv.Key);
OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, id);
OpenGL.glBindTexture(OpenGL.GL_TEXTURE_2D, texture.ID);
// Work around missing textureSize GLSL function by explicitly tracking sizes in a uniform
int param;
if (OpenGL.Profile == GLProfile.Legacy && legacySizeUniforms.TryGetValue(kv.Key, out param))
{
OpenGL.glUniform2f(param, texture.Size.Width, texture.Size.Height);
OpenGL.CheckGLError();
}
}
else
unbindTextures.Enqueue(kv.Key);

View File

@@ -75,7 +75,7 @@ namespace OpenRA.Platforms.Default
void SetData(IntPtr data, int width, int height)
{
PrepareTexture();
var glInternalFormat = OpenGL.Features.HasFlag(OpenGL.GLFeatures.GLES) ? OpenGL.GL_BGRA : OpenGL.GL_RGBA8;
var glInternalFormat = OpenGL.Profile == GLProfile.Embedded ? OpenGL.GL_BGRA : OpenGL.GL_RGBA8;
OpenGL.glTexImage2D(OpenGL.GL_TEXTURE_2D, 0, glInternalFormat, width, height,
0, OpenGL.GL_BGRA, OpenGL.GL_UNSIGNED_BYTE, data);
OpenGL.CheckGLError();
@@ -119,7 +119,7 @@ namespace OpenRA.Platforms.Default
var data = new byte[4 * Size.Width * Size.Height];
// GLES doesn't support glGetTexImage so data must be read back via a frame buffer
if (OpenGL.Features.HasFlag(OpenGL.GLFeatures.GLES))
if (OpenGL.Profile == GLProfile.Embedded)
{
// Query the active framebuffer so we can restore it afterwards
int lastFramebuffer;

View File

@@ -17,165 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenRA.Platforms.Default",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenRA.Mods.Common", "OpenRA.Mods.Common\OpenRA.Mods.Common.csproj", "{FE6C8CC0-2F07-442A-B29F-17617B3B7FC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tiberian Dawn Lua scripts", "Tiberian Dawn Lua scripts", "{62FCD0D0-6D24-435D-9DD8-3CCADCF7ECAB}"
ProjectSection(SolutionItems) = preProject
mods\cnc\maps\cnc64gdi01\cnc64gdi01.lua = mods\cnc\maps\cnc64gdi01\cnc64gdi01.lua
mods\cnc\maps\gdi01\gdi01.lua = mods\cnc\maps\gdi01\gdi01.lua
mods\cnc\maps\gdi02\gdi02.lua = mods\cnc\maps\gdi02\gdi02.lua
mods\cnc\maps\gdi03\gdi03.lua = mods\cnc\maps\gdi03\gdi03.lua
mods\cnc\maps\gdi04a\gdi04a.lua = mods\cnc\maps\gdi04a\gdi04a.lua
mods\cnc\maps\gdi04b\gdi04b.lua = mods\cnc\maps\gdi04b\gdi04b.lua
mods\cnc\maps\gdi04c\gdi04c.lua = mods\cnc\maps\gdi04c\gdi04c.lua
mods\cnc\maps\gdi05a\gdi05a.lua = mods\cnc\maps\gdi05a\gdi05a.lua
mods\cnc\maps\gdi05b\gdi05b.lua = mods\cnc\maps\gdi05b\gdi05b.lua
mods\cnc\maps\gdi06\gdi06.lua = mods\cnc\maps\gdi06\gdi06.lua
mods\cnc\maps\gdi07\gdi07.lua = mods\cnc\maps\gdi07\gdi07.lua
mods\cnc\maps\nod01\nod01.lua = mods\cnc\maps\nod01\nod01.lua
mods\cnc\maps\nod02a\nod02a.lua = mods\cnc\maps\nod02a\nod02a.lua
mods\cnc\maps\nod02b\nod02b.lua = mods\cnc\maps\nod02b\nod02b.lua
mods\cnc\maps\nod03a\nod03a.lua = mods\cnc\maps\nod03a\nod03a.lua
mods\cnc\maps\nod03b\nod03b.lua = mods\cnc\maps\nod03b\nod03b.lua
mods\cnc\maps\nod04a\nod04a.lua = mods\cnc\maps\nod04a\nod04a.lua
mods\cnc\maps\nod04b\nod04b.lua = mods\cnc\maps\nod04b\nod04b.lua
mods\cnc\maps\nod05\nod05.lua = mods\cnc\maps\nod05\nod05.lua
mods\cnc\maps\nod06a\nod06a.lua = mods\cnc\maps\nod06a\nod06a.lua
mods\cnc\maps\nod06b\nod06b.lua = mods\cnc\maps\nod06b\nod06b.lua
mods\cnc\maps\nod06c\nod06c.lua = mods\cnc\maps\nod06c\nod06c.lua
mods\cnc\maps\nod07a\nod07a-AI.lua = mods\cnc\maps\nod07a\nod07a-AI.lua
mods\cnc\maps\nod07a\nod07a.lua = mods\cnc\maps\nod07a\nod07a.lua
mods\cnc\maps\nod07b\nod07b-AI.lua = mods\cnc\maps\nod07b\nod07b-AI.lua
mods\cnc\maps\nod07b\nod07b.lua = mods\cnc\maps\nod07b\nod07b.lua
mods\cnc\maps\nod07c\nod07c.lua = mods\cnc\maps\nod07c\nod07c.lua
mods\cnc\maps\nod08a\nod08a-AI.lua = mods\cnc\maps\nod08a\nod08a-AI.lua
mods\cnc\maps\nod08a\nod08a.lua = mods\cnc\maps\nod08a\nod08a.lua
mods\cnc\maps\nod08b\nod08b-AI.lua = mods\cnc\maps\nod08b\nod08b-AI.lua
mods\cnc\maps\nod08b\nod08b.lua = mods\cnc\maps\nod08b\nod08b.lua
mods\cnc\maps\nod09\nod09-AI.lua = mods\cnc\maps\nod09\nod09-AI.lua
mods\cnc\maps\nod09\nod09.lua = mods\cnc\maps\nod09\nod09.lua
mods\cnc\maps\funpark01\scj01ea.lua = mods\cnc\maps\funpark01\scj01ea.lua
mods\cnc\maps\shellmap\shellmap.lua = mods\cnc\maps\shellmap\shellmap.lua
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Red Alert Lua scripts", "Red Alert Lua scripts", "{B35D533F-BEB6-4674-A466-324EEFD97259}"
ProjectSection(SolutionItems) = preProject
mods\ra\maps\allies-01\allies01.lua = mods\ra\maps\allies-01\allies01.lua
mods\ra\maps\allies-02\allies02.lua = mods\ra\maps\allies-02\allies02.lua
mods\ra\maps\allies-03a\allies03a.lua = mods\ra\maps\allies-03a\allies03a.lua
mods\ra\maps\allies-03b\allies03b.lua = mods\ra\maps\allies-03b\allies03b.lua
mods\ra\maps\allies-05a\allies05a-AI.lua = mods\ra\maps\allies-05a\allies05a-AI.lua
mods\ra\maps\allies-05a\allies05a.lua = mods\ra\maps\allies-05a\allies05a.lua
mods\ra\maps\allies-06a\allies06a-AI.lua = mods\ra\maps\allies-06a\allies06a-AI.lua
mods\ra\maps\allies-06a\allies06a.lua = mods\ra\maps\allies-06a\allies06a.lua
mods\ra\maps\allies-06b\allies06b-AI.lua = mods\ra\maps\allies-06b\allies06b-AI.lua
mods\ra\maps\allies-06b\allies06b.lua = mods\ra\maps\allies-06b\allies06b.lua
mods\ra\maps\desert-shellmap\desert-shellmap.lua = mods\ra\maps\desert-shellmap\desert-shellmap.lua
mods\ra\maps\evacuation\evacuation.lua = mods\ra\maps\evacuation\evacuation.lua
mods\ra\maps\exodus\exodus.lua = mods\ra\maps\exodus\exodus.lua
mods\ra\maps\fort-lonestar\fort-lonestar-AI.lua = mods\ra\maps\fort-lonestar\fort-lonestar-AI.lua
mods\ra\maps\fort-lonestar\fort-lonestar.lua = mods\ra\maps\fort-lonestar\fort-lonestar.lua
mods\ra\maps\infiltration\infiltration.lua = mods\ra\maps\infiltration\infiltration.lua
mods\ra\maps\intervention\intervention.lua = mods\ra\maps\intervention\intervention.lua
mods\ra\maps\monster-tank-madness\monster-tank-madness.lua = mods\ra\maps\monster-tank-madness\monster-tank-madness.lua
mods\ra\maps\soviet-soldier-volkov-n-chitzkoi\scu35ea-AI.lua = mods\ra\maps\soviet-soldier-volkov-n-chitzkoi\scu35ea-AI.lua
mods\ra\maps\soviet-soldier-volkov-n-chitzkoi\scu35ea.lua = mods\ra\maps\soviet-soldier-volkov-n-chitzkoi\scu35ea.lua
mods\ra\maps\top-o-the-world\scu36ea.lua = mods\ra\maps\top-o-the-world\scu36ea.lua
mods\ra\maps\soviet-01\soviet01.lua = mods\ra\maps\soviet-01\soviet01.lua
mods\ra\maps\soviet-02a\soviet02a.lua = mods\ra\maps\soviet-02a\soviet02a.lua
mods\ra\maps\soviet-02b\soviet02b.lua = mods\ra\maps\soviet-02b\soviet02b.lua
mods\ra\maps\soviet-03\soviet03.lua = mods\ra\maps\soviet-03\soviet03.lua
mods\ra\maps\soviet-04a\soviet04a-AI.lua = mods\ra\maps\soviet-04a\soviet04a-AI.lua
mods\ra\maps\soviet-04a\soviet04a-reinforcements_teams.lua = mods\ra\maps\soviet-04a\soviet04a-reinforcements_teams.lua
mods\ra\maps\soviet-04a\soviet04a.lua = mods\ra\maps\soviet-04a\soviet04a.lua
mods\ra\maps\soviet-04b\soviet04b-AI.lua = mods\ra\maps\soviet-04b\soviet04b-AI.lua
mods\ra\maps\soviet-04b\soviet04b-reinforcements_teams.lua = mods\ra\maps\soviet-04b\soviet04b-reinforcements_teams.lua
mods\ra\maps\soviet-04b\soviet04b.lua = mods\ra\maps\soviet-04b\soviet04b.lua
mods\ra\maps\soviet-05\soviet05-AI.lua = mods\ra\maps\soviet-05\soviet05-AI.lua
mods\ra\maps\soviet-05\soviet05-reinforcements_teams.lua = mods\ra\maps\soviet-05\soviet05-reinforcements_teams.lua
mods\ra\maps\soviet-05\soviet05.lua = mods\ra\maps\soviet-05\soviet05.lua
mods\ra\maps\soviet-06a\soviet06a-AI.lua = mods\ra\maps\soviet-06a\soviet06a-AI.lua
mods\ra\maps\soviet-06a\soviet06a-reinforcements_teams.lua = mods\ra\maps\soviet-06a\soviet06a-reinforcements_teams.lua
mods\ra\maps\soviet-06a\soviet06a.lua = mods\ra\maps\soviet-06a\soviet06a.lua
mods\ra\maps\soviet-06b\soviet06b-AI.lua = mods\ra\maps\soviet-06b\soviet06b-AI.lua
mods\ra\maps\soviet-06b\soviet06b-reinforcements_teams.lua = mods\ra\maps\soviet-06b\soviet06b-reinforcements_teams.lua
mods\ra\maps\soviet-06b\soviet06b.lua = mods\ra\maps\soviet-06b\soviet06b.lua
mods\ra\maps\soviet-07\soviet07.lua = mods\ra\maps\soviet-07\soviet07.lua
mods\ra\maps\survival01\survival01.lua = mods\ra\maps\survival01\survival01.lua
mods\ra\maps\survival02\survival02.lua = mods\ra\maps\survival02\survival02.lua
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dune 2000 Lua scripts", "Dune 2000 Lua scripts", "{06B1AE07-DDB0-4287-8700-A8CD9A0E652E}"
ProjectSection(SolutionItems) = preProject
mods\d2k\maps\atreides-01a\atreides01a.lua = mods\d2k\maps\atreides-01a\atreides01a.lua
mods\d2k\maps\atreides-01b\atreides01b.lua = mods\d2k\maps\atreides-01b\atreides01b.lua
mods\d2k\maps\atreides-02a\atreides02a-AI.lua = mods\d2k\maps\atreides-02a\atreides02a-AI.lua
mods\d2k\maps\atreides-02a\atreides02a.lua = mods\d2k\maps\atreides-02a\atreides02a.lua
mods\d2k\maps\atreides-02b\atreides02b-AI.lua = mods\d2k\maps\atreides-02b\atreides02b-AI.lua
mods\d2k\maps\atreides-02b\atreides02b.lua = mods\d2k\maps\atreides-02b\atreides02b.lua
mods\d2k\maps\atreides-03a\atreides03a-AI.lua = mods\d2k\maps\atreides-03a\atreides03a-AI.lua
mods\d2k\maps\atreides-03a\atreides03a.lua = mods\d2k\maps\atreides-03a\atreides03a.lua
mods\d2k\maps\atreides-03b\atreides03b-AI.lua = mods\d2k\maps\atreides-03b\atreides03b-AI.lua
mods\d2k\maps\atreides-03b\atreides03b.lua = mods\d2k\maps\atreides-03b\atreides03b.lua
mods\d2k\maps\atreides-04\atreides04-AI.lua = mods\d2k\maps\atreides-04\atreides04-AI.lua
mods\d2k\maps\atreides-04\atreides04.lua = mods\d2k\maps\atreides-04\atreides04.lua
mods\d2k\maps\atreides-05\atreides05-AI.lua = mods\d2k\maps\atreides-05\atreides05-AI.lua
mods\d2k\maps\atreides-05\atreides05.lua = mods\d2k\maps\atreides-05\atreides05.lua
mods\d2k\bits\scripts\campaign-global.lua = mods\d2k\bits\scripts\campaign-global.lua
mods\d2k\maps\harkonnen-01a\harkonnen01a.lua = mods\d2k\maps\harkonnen-01a\harkonnen01a.lua
mods\d2k\maps\harkonnen-01b\harkonnen01b.lua = mods\d2k\maps\harkonnen-01b\harkonnen01b.lua
mods\d2k\maps\harkonnen-02a\harkonnen02a-AI.lua = mods\d2k\maps\harkonnen-02a\harkonnen02a-AI.lua
mods\d2k\maps\harkonnen-02a\harkonnen02a.lua = mods\d2k\maps\harkonnen-02a\harkonnen02a.lua
mods\d2k\maps\harkonnen-02b\harkonnen02b-AI.lua = mods\d2k\maps\harkonnen-02b\harkonnen02b-AI.lua
mods\d2k\maps\harkonnen-02b\harkonnen02b.lua = mods\d2k\maps\harkonnen-02b\harkonnen02b.lua
mods\d2k\maps\harkonnen-03a\harkonnen03a-AI.lua = mods\d2k\maps\harkonnen-03a\harkonnen03a-AI.lua
mods\d2k\maps\harkonnen-03a\harkonnen03a.lua = mods\d2k\maps\harkonnen-03a\harkonnen03a.lua
mods\d2k\maps\harkonnen-03b\harkonnen03b-AI.lua = mods\d2k\maps\harkonnen-03b\harkonnen03b-AI.lua
mods\d2k\maps\harkonnen-03b\harkonnen03b.lua = mods\d2k\maps\harkonnen-03b\harkonnen03b.lua
mods\d2k\maps\harkonnen-04\harkonnen04-AI.lua = mods\d2k\maps\harkonnen-04\harkonnen04-AI.lua
mods\d2k\maps\harkonnen-04\harkonnen04.lua = mods\d2k\maps\harkonnen-04\harkonnen04.lua
mods\d2k\maps\harkonnen-05\harkonnen05-AI.lua = mods\d2k\maps\harkonnen-05\harkonnen05-AI.lua
mods\d2k\maps\harkonnen-05\harkonnen05.lua = mods\d2k\maps\harkonnen-05\harkonnen05.lua
mods\d2k\maps\harkonnen-06a\harkonnen06a-AI.lua = mods\d2k\maps\harkonnen-06a\harkonnen06a-AI.lua
mods\d2k\maps\harkonnen-06a\harkonnen06a.lua = mods\d2k\maps\harkonnen-06a\harkonnen06a.lua
mods\d2k\maps\harkonnen-06b\harkonnen06b-AI.lua = mods\d2k\maps\harkonnen-06b\harkonnen06b-AI.lua
mods\d2k\maps\harkonnen-06b\harkonnen06b.lua = mods\d2k\maps\harkonnen-06b\harkonnen06b.lua
mods\d2k\maps\harkonnen-07\harkonnen07-AI.lua = mods\d2k\maps\harkonnen-07\harkonnen07-AI.lua
mods\d2k\maps\harkonnen-07\harkonnen07.lua = mods\d2k\maps\harkonnen-07\harkonnen07.lua
mods\d2k\maps\harkonnen-08\harkonnen08-AI.lua = mods\d2k\maps\harkonnen-08\harkonnen08-AI.lua
mods\d2k\maps\harkonnen-08\harkonnen08.lua = mods\d2k\maps\harkonnen-08\harkonnen08.lua
mods\d2k\maps\harkonnen-09a\harkonnen09a-AI.lua = mods\d2k\maps\harkonnen-09a\harkonnen09a-AI.lua
mods\d2k\maps\harkonnen-09a\harkonnen09a.lua = mods\d2k\maps\harkonnen-09a\harkonnen09a.lua
mods\d2k\maps\harkonnen-09b\harkonnen09b-AI.lua = mods\d2k\maps\harkonnen-09b\harkonnen09b-AI.lua
mods\d2k\maps\harkonnen-09b\harkonnen09b.lua = mods\d2k\maps\harkonnen-09b\harkonnen09b.lua
mods\d2k\maps\ordos-01a\ordos01a.lua = mods\d2k\maps\ordos-01a\ordos01a.lua
mods\d2k\maps\ordos-01b\ordos01b.lua = mods\d2k\maps\ordos-01b\ordos01b.lua
mods\d2k\maps\ordos-02a\ordos02a-AI.lua = mods\d2k\maps\ordos-02a\ordos02a-AI.lua
mods\d2k\maps\ordos-02a\ordos02a.lua = mods\d2k\maps\ordos-02a\ordos02a.lua
mods\d2k\maps\ordos-02b\ordos02b-AI.lua = mods\d2k\maps\ordos-02b\ordos02b-AI.lua
mods\d2k\maps\ordos-02b\ordos02b.lua = mods\d2k\maps\ordos-02b\ordos02b.lua
mods\d2k\maps\ordos-03a\ordos03a-AI.lua = mods\d2k\maps\ordos-03a\ordos03a-AI.lua
mods\d2k\maps\ordos-03a\ordos03a.lua = mods\d2k\maps\ordos-03a\ordos03a.lua
mods\d2k\maps\ordos-03b\ordos03b-AI.lua = mods\d2k\maps\ordos-03b\ordos03b-AI.lua
mods\d2k\maps\ordos-03b\ordos03b.lua = mods\d2k\maps\ordos-03b\ordos03b.lua
mods\d2k\maps\ordos-04\ordos04-AI.lua = mods\d2k\maps\ordos-04\ordos04-AI.lua
mods\d2k\maps\ordos-04\ordos04.lua = mods\d2k\maps\ordos-04\ordos04.lua
mods\d2k\maps\ordos-05\ordos05-AI.lua = mods\d2k\maps\ordos-05\ordos05-AI.lua
mods\d2k\maps\ordos-05\ordos05.lua = mods\d2k\maps\ordos-05\ordos05.lua
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "System Lua scripts", "System Lua scripts", "{A4D6AEA4-8009-4256-903B-8D227E50452B}"
ProjectSection(SolutionItems) = preProject
lua\sandbox.lua = lua\sandbox.lua
lua\scriptwrapper.lua = lua\scriptwrapper.lua
lua\stacktraceplus.lua = lua\stacktraceplus.lua
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenRA.Test", "OpenRA.Test\OpenRA.Test.csproj", "{6CB8E1B7-6B36-4D93-8633-7C573E194AC4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tiberian Sun Lua scripts", "Tiberian Sun Lua scripts", "{85159569-F5BD-458E-B5C0-EB16690C432B}"
ProjectSection(SolutionItems) = preProject
mods\ts\maps\fields-of-green\fields-of-green.lua = mods\ts\maps\fields-of-green\fields-of-green.lua
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenRA.PostProcess", "OpenRA.PostProcess\OpenRA.PostProcess.csproj", "{EE63AF7E-92EA-48FB-81E2-53D7D92F8050}"
EndProject
Global
@@ -201,7 +44,6 @@ Global
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Release|Any CPU.Build.0 = Release|Any CPU
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Release-x86|Any CPU.ActiveCfg = Release-x86|Any CPU
{F33337BE-CB69-4B24-850F-07D23E408DDF}.Release-x86|Any CPU.Build.0 = Release-x86|Any CPU
{76F621A1-3D8E-4A99-9F7E-B071EB657817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

View File

@@ -51,6 +51,7 @@ test_script:
- nunit3-console OpenRA.Test.dll --result=myresults.xml;format=AppVeyor
after_test:
- appveyor DownloadFile "https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP" -FileName IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
- appveyor DownloadFile "https://raw.githubusercontent.com/wiki/OpenRA/OpenRA/Changelog.md" -FileName Changelog.md
- make docs
- ps: dir *.md | % {gc $_ -Raw | .\ConvertFrom-Markdown.ps1 | Out-File -FilePath "$($_.Name.TrimEnd(".md")).html"}

21
fetch-geoip.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Download the IP2Location country database for use by the game server
####
# This file must stay /bin/sh and POSIX compliant for macOS and BSD portability.
# Copy-paste the entire script into http://shellcheck.net to check.
####
# Set the working directory to the location of this script
cd "$(dirname "$0")" || exit 1
# Database does not exist or is older than 30 days.
if [ -z "$(find . -path ./IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP -mtime -30 -print)" ]; then
rm -f IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP
echo "Downloading IP2Location GeoIP database."
if command -v curl >/dev/null 2>&1; then
curl -s -L -O https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP || echo "Warning: Download failed"
else
wget -cq https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP || echo "Warning: Download failed"
fi
fi

View File

@@ -3,8 +3,6 @@
precision mediump float;
#endif
in vec4 vColor;
uniform sampler2D Texture0;
uniform sampler2D Texture1;
uniform sampler2D Texture2;
@@ -18,6 +16,27 @@ uniform bool EnableDepthPreview;
uniform float DepthTextureScale;
uniform float AntialiasPixelsPerTexel;
#if __VERSION__ == 120
varying vec4 vTexCoord;
varying vec2 vTexMetadata;
varying vec4 vChannelMask;
varying vec4 vDepthMask;
varying vec2 vTexSampler;
varying vec4 vColorFraction;
varying vec4 vRGBAFraction;
varying vec4 vPalettedFraction;
uniform vec2 Texture0Size;
uniform vec2 Texture1Size;
uniform vec2 Texture2Size;
uniform vec2 Texture3Size;
uniform vec2 Texture4Size;
uniform vec2 Texture5Size;
uniform vec2 Texture6Size;
#else
in vec4 vColor;
in vec4 vTexCoord;
in vec2 vTexMetadata;
in vec4 vChannelMask;
@@ -29,6 +48,7 @@ in vec4 vRGBAFraction;
in vec4 vPalettedFraction;
out vec4 fragColor;
#endif
float jet_r(float x)
{
@@ -45,6 +65,43 @@ float jet_b(float x)
return x < 0.3 ? 4.0 * x + 0.5 : -4.0 * x + 2.5;
}
#if __VERSION__ == 120
vec2 Size(float samplerIndex)
{
if (samplerIndex < 0.5)
return Texture0Size;
else if (samplerIndex < 1.5)
return Texture1Size;
else if (samplerIndex < 2.5)
return Texture2Size;
else if (samplerIndex < 3.5)
return Texture3Size;
else if (samplerIndex < 4.5)
return Texture4Size;
else if (samplerIndex < 5.5)
return Texture5Size;
return Texture6Size;
}
vec4 Sample(float samplerIndex, vec2 pos)
{
if (samplerIndex < 0.5)
return texture2D(Texture0, pos);
else if (samplerIndex < 1.5)
return texture2D(Texture1, pos);
else if (samplerIndex < 2.5)
return texture2D(Texture2, pos);
else if (samplerIndex < 3.5)
return texture2D(Texture3, pos);
else if (samplerIndex < 4.5)
return texture2D(Texture4, pos);
else if (samplerIndex < 5.5)
return texture2D(Texture5, pos);
return texture2D(Texture6, pos);
}
#else
ivec2 Size(float samplerIndex)
{
if (samplerIndex < 0.5)
@@ -80,6 +137,7 @@ vec4 Sample(float samplerIndex, vec2 pos)
return texture(Texture6, pos);
}
#endif
vec4 SamplePalettedBilinear(float samplerIndex, vec2 coords, vec2 textureSize)
{
@@ -93,10 +151,17 @@ vec4 SamplePalettedBilinear(float samplerIndex, vec2 coords, vec2 textureSize)
vec4 x3 = Sample(samplerIndex, tl + vec2(0., px.y));
vec4 x4 = Sample(samplerIndex, tl + px);
#if __VERSION__ == 120
vec4 c1 = texture2D(Palette, vec2(dot(x1, vChannelMask), vTexMetadata.s));
vec4 c2 = texture2D(Palette, vec2(dot(x2, vChannelMask), vTexMetadata.s));
vec4 c3 = texture2D(Palette, vec2(dot(x3, vChannelMask), vTexMetadata.s));
vec4 c4 = texture2D(Palette, vec2(dot(x4, vChannelMask), vTexMetadata.s));
#else
vec4 c1 = texture(Palette, vec2(dot(x1, vChannelMask), vTexMetadata.s));
vec4 c2 = texture(Palette, vec2(dot(x2, vChannelMask), vTexMetadata.s));
vec4 c3 = texture(Palette, vec2(dot(x3, vChannelMask), vTexMetadata.s));
vec4 c4 = texture(Palette, vec2(dot(x4, vChannelMask), vTexMetadata.s));
#endif
return mix(mix(c1, c2, interp.x), mix(c3, c4, interp.x), interp.y);
}
@@ -127,7 +192,11 @@ void main()
{
vec4 x = Sample(vTexSampler.s, coords);
vec2 p = vec2(dot(x, vChannelMask), vTexMetadata.s);
#if __VERSION__ == 120
c = vPalettedFraction * texture2D(Palette, p) + vRGBAFraction * x + vColorFraction * vTexCoord;
#else
c = vPalettedFraction * texture(Palette, p) + vRGBAFraction * x + vColorFraction * vTexCoord;
#endif
}
// Discard any transparent fragments (both color and depth)
@@ -150,8 +219,16 @@ void main()
float r = clamp(jet_r(x), 0.0, 1.0);
float g = clamp(jet_g(x), 0.0, 1.0);
float b = clamp(jet_b(x), 0.0, 1.0);
#if __VERSION__ == 120
gl_FragColor = vec4(r, g, b, 1.0);
#else
fragColor = vec4(r, g, b, 1.0);
#endif
}
else
#if __VERSION__ == 120
gl_FragColor = c;
#else
fragColor = c;
#endif
}

View File

@@ -3,6 +3,21 @@
uniform vec3 Scroll;
uniform vec3 r1, r2;
#if __VERSION__ == 120
attribute vec4 aVertexPosition;
attribute vec4 aVertexTexCoord;
attribute vec2 aVertexTexMetadata;
varying vec4 vTexCoord;
varying vec2 vTexMetadata;
varying vec4 vChannelMask;
varying vec4 vDepthMask;
varying vec2 vTexSampler;
varying vec4 vColorFraction;
varying vec4 vRGBAFraction;
varying vec4 vPalettedFraction;
#else
in vec4 aVertexPosition;
in vec4 aVertexTexCoord;
in vec2 aVertexTexMetadata;
@@ -16,6 +31,7 @@ out vec2 vTexSampler;
out vec4 vColorFraction;
out vec4 vRGBAFraction;
out vec4 vPalettedFraction;
#endif
vec4 UnpackChannelAttributes(float x)
{

View File

@@ -9,13 +9,30 @@ uniform vec2 PaletteRows;
uniform vec4 LightDirection;
uniform vec3 AmbientLight, DiffuseLight;
#if __VERSION__ == 120
varying vec4 vTexCoord;
varying vec4 vChannelMask;
varying vec4 vNormalsMask;
#else
in vec4 vTexCoord;
in vec4 vChannelMask;
in vec4 vNormalsMask;
out vec4 fragColor;
#endif
void main()
{
#if __VERSION__ == 120
vec4 x = texture2D(DiffuseTexture, vTexCoord.st);
vec4 color = texture2D(Palette, vec2(dot(x, vChannelMask), PaletteRows.x));
if (color.a < 0.01)
discard;
vec4 y = texture2D(DiffuseTexture, vTexCoord.pq);
vec4 normal = (2.0 * texture2D(Palette, vec2(dot(y, vNormalsMask), PaletteRows.y)) - 1.0);
vec3 intensity = AmbientLight + DiffuseLight * max(dot(normal, LightDirection), 0.0);
gl_FragColor = vec4(intensity * color.rgb, color.a);
#else
vec4 x = texture(DiffuseTexture, vTexCoord.st);
vec4 color = texture(Palette, vec2(dot(x, vChannelMask), PaletteRows.x));
if (color.a < 0.01)
@@ -25,4 +42,5 @@ void main()
vec4 normal = (2.0 * texture(Palette, vec2(dot(y, vNormalsMask), PaletteRows.y)) - 1.0);
vec3 intensity = AmbientLight + DiffuseLight * max(dot(normal, LightDirection), 0.0);
fragColor = vec4(intensity * color.rgb, color.a);
#endif
}

View File

@@ -3,12 +3,21 @@
uniform mat4 View;
uniform mat4 TransformMatrix;
#if __VERSION__ == 120
attribute vec4 aVertexPosition;
attribute vec4 aVertexTexCoord;
attribute vec2 aVertexTexMetadata;
varying vec4 vTexCoord;
varying vec4 vChannelMask;
varying vec4 vNormalsMask;
#else
in vec4 aVertexPosition;
in vec4 aVertexTexCoord;
in vec2 aVertexTexMetadata;
out vec4 vTexCoord;
out vec4 vChannelMask;
out vec4 vNormalsMask;
#endif
vec4 DecodeMask(float x)
{

View File

@@ -8,20 +8,19 @@ set ListenPort=1234
set AdvertiseOnline=True
set Password=""
set GeoIPDatabase=""
set RequireAuthentication=False
set ProfileIDBlacklist=""
set ProfileIDWhitelist=""
set EnableSingleplayer=False
set EnableSyncReports=False
set EnableGeoIP=True
set ShareAnonymizedIPs=True
set SupportDir=""
:loop
OpenRA.Server.exe Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.GeoIPDatabase=%GeoIPDatabase% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Engine.SupportDir=%SupportDir%
OpenRA.Server.exe Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.EnableGeoIP=%EnableGeoIP% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Engine.SupportDir=%SupportDir%
goto loop

View File

@@ -12,14 +12,13 @@ ListenPort="${ListenPort:-"1234"}"
AdvertiseOnline="${AdvertiseOnline:-"True"}"
Password="${Password:-""}"
GeoIPDatabase="${GeoIPDatabase:-""}"
RequireAuthentication="${RequireAuthentication:-"False"}"
ProfileIDBlacklist="${ProfileIDBlacklist:-""}"
ProfileIDWhitelist="${ProfileIDWhitelist:-""}"
EnableSingleplayer="${EnableSingleplayer:-"False"}"
EnableSyncReports="${EnableSyncReports:-"False"}"
EnableGeoIP="${EnableGeoIP:-"True"}"
ShareAnonymizedIPs="${ShareAnonymizedIPs:-"True"}"
SupportDir="${SupportDir:-""}"
@@ -36,6 +35,7 @@ while true; do
Server.ProfileIDBlacklist="$ProfileIDBlacklist" \
Server.ProfileIDWhitelist="$ProfileIDWhitelist" \
Server.EnableSyncReports="$EnableSyncReports" \
Server.EnableGeoIP="$EnableGeoIP" \
Server.ShareAnonymizedIPs="$ShareAnonymizedIPs" \
Engine.SupportDir="$SupportDir"
done

View File

@@ -21,6 +21,13 @@ function All-Command
{
Write-Host "Build succeeded." -ForegroundColor Green
}
if (!(Test-Path "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP") -Or (((get-date) - (get-item "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP").LastWriteTime) -gt (new-timespan -days 30)))
{
echo "Downloading IP2Location GeoIP database."
$target = Join-Path $pwd.ToString() "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP"
(New-Object System.Net.WebClient).DownloadFile("https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP", $target)
}
}
function Clean-Command

View File

@@ -388,7 +388,7 @@ sidebar-bits:
production-tooltip-time: 904, 51, 16, 16
production-tooltip-power: 870, 51, 16, 16
production-tooltip-cost: 836, 51, 16, 16
indicator-muted: 918, 221, 24, 24
indicator-muted: 918, 221, 26, 24
vertical-bars:
Inherits: ^Chrome

View File

@@ -182,8 +182,10 @@ Container@SAVE_MAP_PANEL:
Font: Bold
ScrollPanel@MAP_SAVE_VISIBILITY_PANEL:
TopBottomSpacing: 5
ItemSpacing: 5
Width: 220
Height: 64
Height: 55
Children:
Checkbox@VISIBILITY_TEMPLATE:
X: 5
@@ -642,29 +644,30 @@ Container@EDITOR_WORLD_ROOT:
Contrast: true
ScrollPanel@CATEGORY_FILTER_PANEL:
Width: 190
Width: 230
TopBottomSpacing: 5
ItemSpacing: 5
Children:
Container@SELECT_CATEGORIES_BUTTONS:
Width: PARENT_RIGHT
Height: 30
Height: 25
Children:
Button@SELECT_ALL:
X: 10
Y: 2
Width: 60
Y: 0 - 5
Width: 88
Height: 25
Text: All
Button@SELECT_NONE:
X: PARENT_RIGHT - WIDTH - 34
Y: 2
Width: 60
X: 10 + 88 + 10
Y: 0 - 5
Width: 88
Height: 25
Text: None
Checkbox@CATEGORY_TEMPLATE:
X: 5
Y: 5
Width: PARENT_RIGHT - 29
Height: 22
Height: 20
Visible: false
ScrollPanel@COPY_FILTER_PANEL:

View File

@@ -148,7 +148,7 @@ Container@LOBBY_PLAYER_BIN:
Height: 25
Font: Regular
Image@STATUS_IMAGE:
X: 521
X: 529
Y: 4
Width: 20
Height: 20
@@ -198,7 +198,6 @@ Container@LOBBY_PLAYER_BIN:
Template: ANONYMOUS_PLAYER_TOOLTIP
Label@NAME:
X: 39
Y: 0 - 1
Width: 161
Height: 25
DropDownButton@PLAYER_ACTION:
@@ -233,8 +232,8 @@ Container@LOBBY_PLAYER_BIN:
Height: 25
Children:
Image@FACTIONFLAG:
X: 5
Y: 5
X: 4
Y: 4
Width: 30
Height: 15
Label@FACTIONNAME:
@@ -265,7 +264,7 @@ Container@LOBBY_PLAYER_BIN:
Font: Regular
Visible: false
Image@STATUS_IMAGE:
X: 527
X: 529
Y: 4
Width: 20
Height: 20
@@ -287,7 +286,6 @@ Container@LOBBY_PLAYER_BIN:
Visible: false
Label@NAME:
X: 20
Y: 0 - 1
Width: 195
Height: 25
Visible: false
@@ -348,7 +346,7 @@ Container@LOBBY_PLAYER_BIN:
Align: Center
Font: Bold
Image@STATUS_IMAGE:
X: 521
X: 529
Y: 4
Width: 20
Height: 20
@@ -398,7 +396,6 @@ Container@LOBBY_PLAYER_BIN:
Template: ANONYMOUS_PLAYER_TOOLTIP
Label@NAME:
X: 39
Y: 0 - 1
Width: 161
Height: 25
DropDownButton@PLAYER_ACTION:

View File

@@ -190,7 +190,13 @@ Container@MAINMENU_INTRODUCTION_PROMPT:
Height: 16
Font: Small
Text: - Zoom the battlefield using {MODIFIER + Scroll Wheel}
LabelWithHighlight@DESC_SCROLL:
LabelWithHighlight@DESC_SCROLL_RIGHT:
X: 265
Y: 34
Height: 16
Font: Small
Text: - Pan the battlefield using the {Right} mouse button
LabelWithHighlight@DESC_SCROLL_MIDDLE:
X: 265
Y: 34
Height: 16

View File

@@ -228,13 +228,6 @@ Container@SETTINGS_PANEL:
Height: 25
MaxLength: 5
Type: Integer
Label@WINDOW_CHANGES_DESC:
X: 60
Y: 27
Width: 200
Height: 15
Font: Tiny
Text: Video mode and window size changes require restart
Container@DISPLAY_SELECTION:
Y: 240
Children:
@@ -249,13 +242,6 @@ Container@SETTINGS_PANEL:
Width: 160
Height: 25
Font: Regular
Label@MODE_CHANGES_DESC:
X: 60
Y: 27
Width: 200
Height: 15
Font: Tiny
Text: Video mode and display changes require restart
Checkbox@VSYNC_CHECKBOX:
X: 310
Y: 210
@@ -278,6 +264,27 @@ Container@SETTINGS_PANEL:
Ticks: 20
MinimumValue: 50
MaximumValue: 240
Label@GL_PROFILE:
X: 15
Y: 270
Width: 120
Height: 25
Align: Right
Text: OpenGL Profile:
DropDownButton@GL_PROFILE_DROPDOWN:
X: 140
Y: 270
Width: 160
Height: 25
Font: Regular
Label@RESTART_REQUIRED_DESC:
X: 300
Y: PARENT_BOTTOM + 10
Width: PARENT_RIGHT - 300
Height: 15
Font: TinyBold
Text: Display and OpenGL changes require restart
Align: Center
Container@AUDIO_PANEL:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
@@ -484,7 +491,12 @@ Container@SETTINGS_PANEL:
Height: 16
Font: Small
Text: - Zoom the battlefield using {MODIFIER + Scroll Wheel}
LabelWithHighlight@DESC_SCROLL:
LabelWithHighlight@DESC_SCROLL_RIGHT:
Y: 85
Height: 16
Font: Small
Text: - Pan the battlefield using the {Right} mouse button
LabelWithHighlight@DESC_SCROLL_MIDDLE:
Y: 85
Height: 16
Font: Small
@@ -537,13 +549,13 @@ Container@SETTINGS_PANEL:
Height: 20
Font: Regular
Text: Screen Edge Panning
Checkbox@CLASSIC_MOUSE_MIDDLE_SCROLL_CHECKBOX:
Checkbox@ALTERNATE_SCROLL_CHECKBOX:
X: 360
Y: 133
Width: 180
Height: 20
Font: Regular
Text: Middle Mouse Panning
Text: Alternate Mouse Panning
Label@SCROLL_SPEED_LABEL:
X: 310
Y: 210
@@ -634,7 +646,7 @@ Container@SETTINGS_PANEL:
Width: 80
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
Container@THREE_COLUMN:
Width: 173
Height: 25
@@ -650,7 +662,7 @@ Container@SETTINGS_PANEL:
Width: 80
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
Background@HOTKEY_DIALOG_ROOT:
X: 15
Y: 230
@@ -700,7 +712,7 @@ Container@SETTINGS_PANEL:
Width: 25
Height: 25
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image:
@@ -715,7 +727,7 @@ Container@SETTINGS_PANEL:
Width: 25
Height: 25
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Children:
Image@IMAGE_RELOAD:
@@ -860,4 +872,4 @@ Container@SETTINGS_PANEL:
Width: 140
Height: 35
Text: Reset
TooltipContainer@TOOLTIP_CONTAINER:
TooltipContainer@SETTINGS_TOOLTIP_CONTAINER:

View File

@@ -405,6 +405,9 @@ Actors:
Owner: Nod
Facing: 192
SubCell: 3
Actor101: moneycrate
Location: 56,23
Owner: Neutral
HandOfNod: hand
Location: 30,53
Owner: Nod

View File

@@ -165,10 +165,8 @@ OBLI:
Prerequisites: ~disabled
MoneyCrate:
Inherits: ^Crate
GiveCashCrateAction:
Amount: 500
UseCashTick: true
airstrike.proxy:
AirstrikePower:

View File

@@ -483,6 +483,9 @@ Actors:
Owner: Nod
Facing: 160
SubCell: 4
Actor167: moneycrate
Location: 26,42
Owner: Neutral
waypoint27: waypoint
Location: 32,41
Owner: Neutral

View File

@@ -0,0 +1,194 @@
--[[
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.
]]
AttackPaths = { WaypointGroup3, WaypointGroup4, WaypointGroup5 }
NodBase = { handofnod, nodairfield, nodrefinery, NodYard, nodpower1, nodpower2, nodpower3, nodpower4, gun1, gun2, nodsilo1, nodsilo2, nodsilo3, nodsilo4}
PatrolProductionQueue = { }
InfantryAttackGroup = { }
InfantryGroupSize = 5
InfantryProductionCooldown = DateTime.Minutes(3)
InfantryProductionTypes = { "e1", "e1", "e1", "e3", "e3", "e4" }
HarvesterProductionType = { "harv" }
VehicleAttackGroup = { }
VehicleGroupSize = 5
VehicleProductionCooldown = DateTime.Minutes(3)
VehicleProductionTypes = { "bggy", "bggy", "bike", "ltnk", "ltnk" }
StartingCash = 14000
BaseRefinery = { type = "proc", pos = CPos.New(24, 16), cost = 1500 }
BaseGun1 = { type = "gun", pos = CPos.New( 21, 19), cost = 600 }
BaseGun2 = { type = "gun", pos = CPos.New( 26, 21), cost = 600 }
BaseNuke1 = { type = "nuke", pos = CPos.New( 23, 14), cost = 500 }
BaseNuke2 = { type = "nuke", pos = CPos.New( 10, 9), cost = 500 }
BaseNuke3 = { type = "nuke", pos = CPos.New( 6, 8), cost = 500 }
BaseNuke4 = { type = "nuke", pos = CPos.New( 8, 8), cost = 500 }
InfantryProduction = { type = "hand", pos = CPos.New(27, 17), cost = 500 }
VehicleProduction = { type = "afld", pos = CPos.New(27, 14), cost = 2000 }
NodGuards = { Actor154, Actor155, Actor218, Actor219 }
BaseBuildings = { BaseRefinery, BaseNuke1, BaseNuke2, BaseNuke3, BaseNuke4, InfantryProduction, VehicleProduction, BaseGun1, BaseGun2 }
BuildBuilding = function(building, cyard)
if CyardIsBuilding or Nod.Cash < building.cost then
Trigger.AfterDelay(DateTime.Seconds(10), function() BuildBuilding(building, cyard) end)
return
end
CyardIsBuilding = true
Nod.Cash = Nod.Cash - building.cost
Trigger.AfterDelay(Actor.BuildTime(building.type), function()
CyardIsBuilding = false
if cyard.IsDead or cyard.Owner ~= Nod then
Nod.Cash = Nod.Cash + building.cost
return
end
local actor = Actor.Create(building.type, true, { Owner = Nod, Location = building.pos })
if actor.Type == 'hand' or actor.Type == 'pyle' then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceInfantry(actor) end)
elseif actor.Type == 'afld' or actor.Type == 'weap' then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceVehicle(actor) end)
end
Trigger.OnKilled(actor, function()
BuildBuilding(building, cyard)
end)
RepairBuilding(GDI, actor, 0.75)
end)
end
CheckForHarvester = function()
local harv = Nod.GetActorsByType("harv")
return #harv > 0
end
GuardBase = function()
Utils.Do(NodBase, function(building)
Trigger.OnDamaged(building, function()
Utils.Do(NodGuards, function(guard)
if not guard.IsDead and not building.IsDead then
guard.Stop()
guard.Guard(building)
end
end)
end)
end)
end
ProduceHarvester = function(building)
if not buildingHarvester then
buildingHarvester = true
building.Build(HarvesterProductionType, function()
buildingHarvester = false
end)
end
end
ProduceInfantry = function(building)
if building.IsDead or building.Owner ~= Nod then
return
elseif not CheckForHarvester() then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceInfantry(building) end)
return
end
local delay = Utils.RandomInteger(DateTime.Seconds(3), DateTime.Seconds(9))
local toBuild = { Utils.Random(InfantryProductionTypes) }
local Path = Utils.Random(AttackPaths)
building.Build(toBuild, function(unit)
InfantryAttackGroup[#InfantryAttackGroup + 1] = unit[1]
if #InfantryAttackGroup >= InfantryGroupSize then
MoveAndHunt(InfantryAttackGroup, Path)
InfantryAttackGroup = { }
Trigger.AfterDelay(InfantryProductionCooldown, function() ProduceInfantry(building) end)
else
Trigger.AfterDelay(delay, function() ProduceInfantry(building) end)
end
end)
end
ProduceVehicle = function(building)
if building.IsDead or building.Owner ~= Nod then
return
elseif not CheckForHarvester() then
ProduceHarvester(building)
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceVehicle(building) end)
return
end
local delay = Utils.RandomInteger(DateTime.Seconds(12), DateTime.Seconds(17))
local toBuild = { Utils.Random(VehicleProductionTypes) }
local Path = Utils.Random(AttackPaths)
building.Build(toBuild, function(unit)
VehicleAttackGroup[#VehicleAttackGroup + 1] = unit[1]
if #VehicleAttackGroup >= VehicleGroupSize then
MoveAndHunt(VehicleAttackGroup, Path)
VehicleAttackGroup = { }
Trigger.AfterDelay(VehicleProductionCooldown, function() ProduceVehicle(building) end)
else
Trigger.AfterDelay(delay, function() ProduceVehicle(building) end)
end
end)
end
StartAI = function()
Nod.Cash = StartingCash
GuardBase()
end
Trigger.OnAllKilledOrCaptured(NodBase, function()
Utils.Do(Nod.GetGroundAttackers(), IdleHunt)
end)
Trigger.OnKilled(nodrefinery, function()
BuildBuilding(BaseRefinery, NodYard)
end)
Trigger.OnKilled(nodpower1, function()
BuildBuilding(BaseNuke1, NodYard)
end)
Trigger.OnKilled(nodpower2, function()
BuildBuilding(BaseNuke2, NodYard)
end)
Trigger.OnKilled(nodpower3, function()
BuildBuilding(BaseNuke3, NodYard)
end)
Trigger.OnKilled(nodpower4, function()
BuildBuilding(BaseNuke4, NodYard)
end)
Trigger.OnKilled(gun1, function()
BuildBuilding(BaseGun1, NodYard)
end)
Trigger.OnKilled(gun2, function()
BuildBuilding(BaseGun2, NodYard)
end)
Trigger.OnKilled(handofnod, function()
BuildBuilding(InfantryProduction, NodYard)
end)
Trigger.OnKilled(nodairfield, function()
BuildBuilding(VehicleProduction, NodYard)
end)

View File

@@ -0,0 +1,152 @@
--[[
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.
]]
SamSites = { sam1, sam2, sam3 }
WaypointGroup1 = { waypoint4, waypoint5, waypoint6, waypoint7, waypoint10, waypoint11, waypoint12 }
WaypointGroup2 = { waypoint4, waypoint5, waypoint13, waypoint16 }
WaypointGroup3 = { waypoint4, waypoint5, waypoint6, waypoint8 }
WaypointGroup4 = { waypoint4, waypoint5, waypoint6, waypoint7, waypoint9 }
WaypointGroup5 = { waypoint4, waypoint5, waypoint6 }
WaypointGroup6 = { waypoint4, waypoint5, waypoint6, waypoint7, waypoint14, waypoint15 }
WaypointGroup7 = { waypoint4, waypoint5 }
WaypointGroupCiv = { waypoint0, waypoint1, waypoint2, waypoint3 }
Atk1 = { units = { ['ltnk'] = 1 }, waypoints = WaypointGroup1, delay = 0 }
Atk2 = { units = { ['ltnk'] = 1 }, waypoints = WaypointGroup2, delay = 0 }
Civ1 = { units = { ['c3'] = 1 }, waypoints = WaypointGroupCiv, delay = 0 }
Nod1 = { units = { ['e1'] = 2, ['e2'] = 2, ['e4'] = 2 }, waypoints = WaypointGroup3, delay = 90 }
Nod2 = { units = { ['e3'] = 2, ['e4'] = 2 }, waypoints = WaypointGroup3, delay = 130 }
Nod3 = { units = { ['e1'] = 2, ['e3'] = 3 }, waypoints = WaypointGroup3, delay = 50 }
Nod4 = { units = { ['bggy'] = 2 }, waypoints = WaypointGroup3, delay = 200 }
Nod5 = { units = { ['e4'] = 2, ['ltnk'] = 1 }, waypoints = WaypointGroup1, delay = 250 }
Nod6 = { units = { ['arty'] = 1 }, waypoints = WaypointGroup4, delay = 40 }
Nod7 = { units = { ['e3'] = 2, ['e4'] = 2 }, waypoints = WaypointGroup4, delay = 40 }
Nod8 = { units = { ['ltnk'] = 1, ['bggy'] = 1 }, waypoints = WaypointGroup3, delay = 170 }
Auto1 = { units = { ['e1'] = 2, ['e2'] = 2 }, waypoints = WaypointGroup5, delay = 50 }
Auto2 = { units = { ['e3'] = 3, ['e4'] = 2 }, waypoints = WaypointGroup3, delay = 50 }
Auto3 = { units = { ['ltnk'] = 1, ['bggy'] = 1 }, waypoints = WaypointGroup3, delay = 50 }
Auto4 = { units = { ['bggy'] = 2 }, waypoints = WaypointGroup7, delay = 50 }
Auto5 = { units = { ['ltnk'] = 1 }, waypoints = WaypointGroup6, delay = 50 }
Auto6 = { units = { ['arty'] = 1 }, waypoints = WaypointGroup4, delay = 50 }
Auto7 = { units = { ['e3'] = 3, ['e4'] = 2 }, waypoints = WaypointGroup6, delay = 50 }
AutoAttackWaves = { Atk1, Atk2, Nod1, Nod2, Nod3, Nod4, Nod5, Nod6, Nod7, Nod8, Auto1, Auto2, Auto3, Auto4, Auto5, Auto6, Auto7 }
StationaryGuardUnits = { Actor181, Actor182, Actor183, Actor184, Actor198, Actor199, Actor157, Actor175, Actor176, Actor173, Actor174, Actor158, Actor200, Actor159, Actor179, Actor180, Actor184, Actor185, Actor216, Actor217, Actor153, Actor215, Actor214, Actor213}
DamagedGDIAssets = { Actor126, Actor127, Actor128, Actor129, Actor130,Actor131, Actor132, Actor133, Actor134, Actor135, Actor136, Actor137, Actor138, Actor160, Actor161, Actor162, Actor163, Actor164, Actor165, Actor166, Actor168, Actor169, Actor170}
StartStationaryGuards = function(StationaryGuards)
Utils.Do(StationaryGuards, function(unit)
if not unit.IsDead then
unit.Patrol( { unit.Location } , true, 20)
end
end)
end
SendWaves = function(counter, Waves)
if counter <= #Waves then
local team = Waves[counter]
for type, amount in pairs(team.units) do
MoveAndHunt(Utils.Take(amount, Nod.GetActorsByType(type)), team.waypoints)
end
Trigger.AfterDelay(DateTime.Seconds(team.delay), function() SendWaves(counter + 1, Waves) end)
end
end
SendAttackWave = function(team)
for type, amount in pairs(team.units) do
count = 0
local actors = Nod.GetActorsByType(type)
Utils.Do(actors, function(actor)
if actor.IsIdle and count < amount then
SetAttackWaypoints(actor, team.waypoints)
IdleHunt(actor)
count = count + 1
end
end)
end
end
SetAttackWaypoints = function(actor, waypoints)
if not actor.IsDead then
Utils.Do(waypoints, function(waypoint)
actor.AttackMove(waypoint.Location)
end)
end
end
CeckRepairGDIAssetsObjective = function()
local failed = false
local repaired = true
Utils.Do(DamagedGDIAssets, function(actor)
if actor.IsDead then
failed = true
elseif actor.Health < actor.MaxHealth then
repaired = false
end
end)
if failed then
GDI.MarkFailedObjective(RepairAssets)
return
elseif repaired then
GDI.MarkCompletedObjective(RepairAssets)
return
end
Trigger.AfterDelay(DateTime.Seconds(3), function() CeckRepairGDIAssetsObjective() end)
end
WorldLoaded = function()
GDI = Player.GetPlayer("GDI")
Nod = Player.GetPlayer("Nod")
Camera.Position = DefaultCameraPosition.CenterPosition
StartStationaryGuards(StationaryGuardUnits)
StartAI()
InitObjectives(GDI)
SecureArea = GDI.AddObjective("Destroy the Nod strike force.")
KillGDI = Nod.AddObjective("Kill all enemies!")
RepairAssets = GDI.AddObjective("Repair GDI base and vehicles.", "Secondary", false)
Trigger.AfterDelay(DateTime.Seconds(5), function() CeckRepairGDIAssetsObjective() end)
AirSupport = GDI.AddObjective("Destroy the SAM sites to receive air support.", "Secondary", false)
Trigger.OnAllKilled(SamSites, function()
GDI.MarkCompletedObjective(AirSupport)
Actor.Create("airstrike.proxy", true, { Owner = GDI })
end)
local InitialArrivingUnits = { Actor166 }
Utils.Do(InitialArrivingUnits, function(unit)
unit.Move(unit.Location + CVec.New(1, 1), 0)
end)
Trigger.AfterDelay(DateTime.Minutes(1), function() SendWaves(1, AutoAttackWaves) end)
Trigger.AfterDelay(DateTime.Minutes(2), function() ProduceInfantry(handofnod) end)
Trigger.AfterDelay(DateTime.Minutes(2), function() ProduceVehicle(nodairfield) end)
end
Tick = function()
if DateTime.GameTime > DateTime.Seconds(5) then
if GDI.HasNoRequiredUnits() then
Nod.MarkCompletedObjective(KillGDI)
end
if Nod.HasNoRequiredUnits() then
GDI.MarkCompletedObjective(SecureArea)
end
end
end

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,889 @@
MapFormat: 11
RequiresMod: cnc
Title: 08a: Restore GDI Presence Near Salzburg
Author: Westwood Studios
Tileset: WINTER
MapSize: 64,64
Bounds: 3,7,51,50
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: gdi
PlayerReference@Nod:
Name: Nod
Faction: nod
Color: FF1400
Allies: Nod
Enemies: GDI
Bot: campaign
PlayerReference@GDI:
Name: GDI
AllowBots: False
Playable: True
Required: True
LockFaction: True
Faction: gdi
LockColor: True
Color: F5D378
LockSpawn: True
LockTeam: True
Allies: GDI
Enemies: Nod
Actors:
Actor0: sbag
Location: 53,56
Owner: Neutral
Actor1: sbag
Location: 52,56
Owner: Neutral
Actor2: sbag
Location: 51,56
Owner: Neutral
Actor3: sbag
Location: 50,56
Owner: Neutral
Actor4: sbag
Location: 49,56
Owner: Neutral
Actor5: sbag
Location: 48,56
Owner: Neutral
Actor6: sbag
Location: 47,56
Owner: Neutral
Actor7: sbag
Location: 46,56
Owner: Neutral
Actor8: sbag
Location: 45,56
Owner: Neutral
Actor9: sbag
Location: 44,56
Owner: Neutral
Actor10: sbag
Location: 43,56
Owner: Neutral
Actor11: sbag
Location: 41,56
Owner: Neutral
Actor12: sbag
Location: 40,56
Owner: Neutral
Actor13: wood
Location: 5,56
Owner: Neutral
Actor14: wood
Location: 4,56
Owner: Neutral
Actor15: wood
Location: 3,56
Owner: Neutral
Actor16: sbag
Location: 53,55
Owner: Neutral
Actor17: sbag
Location: 40,55
Owner: Neutral
Actor18: v18
Location: 5,55
Owner: Neutral
Actor19: v17
Location: 4,55
Owner: Neutral
Actor20: sbag
Location: 53,54
Owner: Neutral
Actor21: sbag
Location: 40,54
Owner: Neutral
Actor22: wood
Location: 17,54
Owner: Neutral
Actor23: wood
Location: 16,54
Owner: Neutral
Actor24: wood
Location: 15,54
Owner: Neutral
Actor25: wood
Location: 14,54
Owner: Neutral
Actor26: wood
Location: 11,54
Owner: Neutral
Actor27: wood
Location: 10,54
Owner: Neutral
Actor28: v14
Location: 6,54
Owner: Neutral
Actor29: v15
Location: 5,54
Owner: Neutral
Actor30: v16
Location: 4,54
Owner: Neutral
Actor31: sbag
Location: 53,53
Owner: Neutral
Actor32: wood
Location: 6,53
Owner: Neutral
Actor33: wood
Location: 17,52
Owner: Neutral
Actor34: wood
Location: 16,52
Owner: Neutral
Actor35: wood
Location: 15,52
Owner: Neutral
Actor36: wood
Location: 14,52
Owner: Neutral
Actor37: wood
Location: 11,52
Owner: Neutral
Actor38: wood
Location: 10,52
Owner: Neutral
Actor39: sbag
Location: 53,51
Owner: Neutral
Actor40: sbag
Location: 53,50
Owner: Neutral
Actor41: sbag
Location: 40,50
Owner: Neutral
Actor42: sbag
Location: 53,49
Owner: Neutral
Actor43: sbag
Location: 40,49
Owner: Neutral
Actor44: sbag
Location: 53,48
Owner: Neutral
Actor45: sbag
Location: 53,47
Owner: Neutral
Actor46: sbag
Location: 40,47
Owner: Neutral
Actor47: sbag
Location: 53,46
Owner: Neutral
Actor48: sbag
Location: 52,46
Owner: Neutral
Actor49: sbag
Location: 51,46
Owner: Neutral
Actor50: sbag
Location: 50,46
Owner: Neutral
Actor51: sbag
Location: 49,46
Owner: Neutral
Actor52: sbag
Location: 48,46
Owner: Neutral
Actor53: sbag
Location: 42,46
Owner: Neutral
Actor54: sbag
Location: 41,46
Owner: Neutral
Actor55: sbag
Location: 40,46
Owner: Neutral
Actor56: t01
Location: 33,32
Owner: Neutral
Actor57: t01
Location: 33,34
Owner: Neutral
Actor58: tc01
Location: 47,27
Owner: Neutral
Actor59: t01
Location: 42,18
Owner: Neutral
Actor60: t01
Location: 41,16
Owner: Neutral
Actor61: t01
Location: 36,18
Owner: Neutral
Actor62: t01
Location: 15,50
Owner: Neutral
Actor63: t01
Location: 6,54
Owner: Neutral
Actor64: t01
Location: 11,48
Owner: Neutral
Actor65: tc01
Location: 14,33
Owner: Neutral
Actor66: tc04
Location: 17,35
Owner: Neutral
Actor67: tc01
Location: 9,19
Owner: Neutral
Actor68: tc04
Location: 11,20
Owner: Neutral
Actor69: t06
Location: 37,8
Owner: Neutral
Actor70: t03
Location: 33,8
Owner: Neutral
Actor71: t02
Location: 36,9
Owner: Neutral
Actor72: t01
Location: 32,7
Owner: Neutral
Actor73: tc04
Location: 50,9
Owner: Neutral
Actor74: tc01
Location: 45,7
Owner: Neutral
Actor75: tc01
Location: 38,13
Owner: Neutral
Actor76: tc02
Location: 35,22
Owner: Neutral
Actor77: tc05
Location: 32,21
Owner: Neutral
Actor78: t10
Location: 17,47
Owner: Neutral
Actor79: t13
Location: 16,50
Owner: Neutral
Actor80: tc01
Location: 15,48
Owner: Neutral
Actor81: tc02
Location: 6,51
Owner: Neutral
Actor82: tc02
Location: 3,47
Owner: Neutral
Actor83: tc04
Location: 3,51
Owner: Neutral
Actor84: tc04
Location: 5,47
Owner: Neutral
Actor85: tc04
Location: 9,54
Owner: Neutral
Actor86: tc04
Location: 3,40
Owner: Neutral
Actor87: t13
Location: 4,42
Owner: Neutral
Actor88: t11
Location: 10,12
Owner: Neutral
Actor89: t15
Location: 4,10
Owner: Neutral
Actor90: t16
Location: 3,9
Owner: Neutral
Actor91: tc02
Location: 5,12
Owner: Neutral
Actor92: tc01
Location: 4,7
Owner: Neutral
Actor93: tc04
Location: 20,11
Owner: Neutral
Actor94: tc04
Location: 19,47
Owner: Neutral
Actor95: tc05
Location: 20,49
Owner: Neutral
Actor96: tc04
Location: 40,17
Owner: Neutral
Actor97: tc01
Location: 44,36
Owner: Neutral
Actor98: tc04
Location: 39,37
Owner: Neutral
Actor99: tc02
Location: 40,30
Owner: Neutral
Actor100: tc02
Location: 30,32
Owner: Neutral
Actor101: t10
Location: 32,33
Owner: Neutral
Actor102: tc04
Location: 37,54
Owner: Neutral
Actor103: tc02
Location: 34,53
Owner: Neutral
Actor104: t01
Location: 31,53
Owner: Neutral
Actor105: t01
Location: 16,54
Owner: Neutral
Actor106: t02
Location: 18,55
Owner: Neutral
Actor107: t11
Location: 25,32
Owner: Neutral
Actor108: t11
Location: 41,44
Owner: Neutral
Actor109: tc01
Location: 51,44
Owner: Neutral
Actor110: tc04
Location: 27,40
Owner: Neutral
Actor111: tc05
Location: 30,42
Owner: Neutral
Actor112: t07
Location: 31,39
Owner: Neutral
Actor113: t06
Location: 26,42
Owner: Neutral
Actor114: t03
Location: 28,43
Owner: Neutral
sam1: sam
Location: 11,20
Owner: Nod
sam2: sam
Location: 33,18
Owner: Nod
sam3: sam
Location: 16,7
Owner: Nod
Actor118: v05
Location: 17,49
Owner: Neutral
Actor119: v07
Location: 9,49
Owner: Neutral
Actor120: v06
Location: 4,53
Owner: Neutral
Actor121: v05
Location: 6,49
Owner: Neutral
Actor122: v04
Location: 4,50
Owner: Neutral
Actor123: v03
Location: 10,50
Owner: Neutral
Actor124: v01
Location: 3,48
Owner: Neutral
Actor125: v06
Location: 20,49
Owner: Neutral
Actor126: gtwr
Location: 41,54
Owner: GDI
Health: 41
Actor127: gtwr
Location: 41,50
Owner: GDI
Health: 16
Actor128: hq
Location: 51,47
Owner: GDI
Health: 39
Actor129: proc
Location: 44,46
Owner: GDI
Health: 33
FreeActor: False
Actor130: silo
Location: 49,54
Owner: GDI
Health: 53
Actor131: silo
Location: 47,47
Owner: GDI
Health: 20
Actor132: silo
Location: 47,53
Owner: GDI
Health: 13
Actor133: silo
Location: 51,54
Owner: GDI
Health: 28
Actor134: nuke
Location: 49,51
Owner: GDI
Health: 14
Actor135: nuke
Location: 49,48
Owner: GDI
Health: 20
Actor136: nuke
Location: 51,50
Owner: GDI
Health: 35
Actor137: pyle
Location: 47,49
Owner: GDI
Health: 18
Actor138: fix
Location: 44,52
Owner: GDI
Health: 28
gun1: gun
Location: 21,19
Owner: Nod
NodYard: fact
Location: 7,11
Owner: Nod
nodrefinery: proc
Location: 24,16
Owner: Nod
FreeActor: False
nodsilo1: silo
Location: 29,17
Owner: Nod
nodsilo2: silo
Location: 9,7
Owner: Nod
nodsilo3: silo
Location: 12,9
Owner: Nod
nodsilo4: silo
Location: 11,7
Owner: Nod
nodairfield: afld
Location: 27,14
Owner: Nod
nodpower1: nuke
Location: 23,14
Owner: Nod
nodpower2: nuke
Location: 10,9
Owner: Nod
nodpower3: nuke
Location: 6,8
Owner: Nod
nodpower4: nuke
Location: 8,8
Owner: Nod
handofnod: hand
Location: 27,17
Owner: Nod
gun2: gun
Location: 26,21
Owner: Nod
Actor153: arty
Location: 4,12
Owner: Nod
Facing: 127
Actor154: bggy
Location: 20,18
Owner: Nod
Actor155: ltnk
Location: 19,18
Owner: Nod
Actor156: bggy
Location: 10,21
Owner: Nod
Facing: 95
Actor157: ltnk
Location: 40,35
Owner: Nod
Facing: 159
Actor158: ltnk
Location: 45,23
Owner: Nod
Facing: 95
Actor159: ltnk
Location: 41,14
Owner: Nod
Facing: 159
Actor160: mtnk
Location: 42,55
Owner: GDI
Health: 35
Facing: 223
Actor161: mtnk
Location: 33,52
Owner: GDI
Health: 43
Facing: 31
Actor162: apc
Location: 37,50
Owner: GDI
Health: 68
Facing: 127
Actor163: apc
Location: 39,48
Owner: GDI
Health: 45
Facing: 127
Actor164: msam
Location: 42,49
Owner: GDI
Health: 40
Facing: 95
Actor165: msam
Location: 41,51
Owner: GDI
Health: 50
Facing: 159
Actor166: msam
Location: 33,48
Owner: GDI
Health: 65
Facing: 159
Actor167: harv
Location: 23,23
Owner: Nod
Facing: 95
Actor168: harv
Location: 38,53
Owner: GDI
Health: 51
Facing: 31
Actor169: jeep
Location: 36,52
Owner: GDI
Health: 72
Facing: 95
Actor170: jeep
Location: 34,51
Owner: GDI
Health: 31
Facing: 159
Actor171: arty
Location: 19,17
Owner: Nod
Actor172: ltnk
Location: 22,11
Owner: Nod
Facing: 159
Actor173: e1
Location: 45,36
Owner: Nod
SubCell: 3
Actor174: e1
Location: 44,36
Owner: Nod
SubCell: 4
Actor175: e3
Location: 41,36
Owner: Nod
Facing: 159
SubCell: 2
Actor176: e3
Location: 41,36
Owner: Nod
Facing: 159
SubCell: 3
Actor177: e3
Location: 42,17
Owner: Nod
Facing: 159
SubCell: 0
Actor178: e3
Location: 40,17
Owner: Nod
Facing: 159
SubCell: 4
Actor179: e4
Location: 42,15
Owner: Nod
Facing: 159
SubCell: 2
Actor180: e4
Location: 42,15
Owner: Nod
Facing: 159
SubCell: 3
Actor181: e1
Location: 16,53
Owner: Nod
Facing: 191
SubCell: 3
Actor182: e1
Location: 15,53
Owner: Nod
Facing: 191
SubCell: 2
Actor183: e1
Location: 17,53
Owner: Nod
Facing: 191
SubCell: 3
Actor184: e1
Location: 16,53
Owner: Nod
Facing: 191
SubCell: 2
Actor185: c3
Location: 18,50
Owner: Neutral
Facing: 159
SubCell: 1
Actor186: e1
Location: 46,51
Owner: GDI
Facing: 159
SubCell: 1
Actor187: e1
Location: 44,52
Owner: GDI
Health: 87
Facing: 223
SubCell: 2
Actor188: e1
Location: 43,53
Owner: GDI
Health: 31
Facing: 31
SubCell: 4
Actor189: e1
Location: 42,54
Owner: GDI
Health: 75
SubCell: 2
Actor190: e1
Location: 39,52
Owner: GDI
Health: 77
Facing: 159
SubCell: 4
Actor191: e1
Location: 42,53
Owner: GDI
Health: 69
Facing: 159
SubCell: 0
Actor192: e2
Location: 40,53
Owner: GDI
Health: 75
Facing: 31
SubCell: 2
Actor193: e2
Location: 45,51
Owner: GDI
Facing: 127
SubCell: 1
Actor194: e2
Location: 38,50
Owner: GDI
Health: 27
Facing: 223
SubCell: 0
Actor195: e2
Location: 46,52
Owner: GDI
Facing: 31
SubCell: 1
Actor196: e2
Location: 36,53
Owner: GDI
Health: 57
Facing: 159
SubCell: 0
Actor197: e2
Location: 37,54
Owner: GDI
Facing: 31
SubCell: 1
Actor198: e3
Location: 27,40
Owner: Nod
SubCell: 4
Actor199: e3
Location: 29,40
Owner: Nod
SubCell: 3
Actor200: e3
Location: 48,27
Owner: Nod
SubCell: 3
Actor201: e1
Location: 27,21
Owner: Nod
SubCell: 3
Actor202: e1
Location: 27,21
Owner: Nod
SubCell: 4
Actor203: e1
Location: 27,21
Owner: Nod
SubCell: 2
Actor204: e1
Location: 27,21
Owner: Nod
SubCell: 1
Actor205: e3
Location: 28,21
Owner: Nod
SubCell: 4
Actor206: e3
Location: 28,21
Owner: Nod
SubCell: 2
Actor207: e3
Location: 29,21
Owner: Nod
SubCell: 1
Actor208: e3
Location: 29,21
Owner: Nod
SubCell: 3
Actor209: e4
Location: 30,21
Owner: Nod
SubCell: 1
Actor210: e4
Location: 30,21
Owner: Nod
SubCell: 3
Actor211: e4
Location: 30,21
Owner: Nod
SubCell: 2
Actor212: e4
Location: 30,21
Owner: Nod
SubCell: 4
Actor213: e1
Location: 13,12
Owner: Nod
SubCell: 3
Actor214: e1
Location: 14,12
Owner: Nod
SubCell: 1
Actor215: e1
Location: 15,11
Owner: Nod
SubCell: 0
Actor216: e3
Location: 5,7
Owner: Nod
SubCell: 3
Actor217: e3
Location: 7,7
Owner: Nod
SubCell: 3
Actor218: e3
Location: 19,15
Owner: Nod
SubCell: 1
Actor219: e3
Location: 20,16
Owner: Nod
SubCell: 1
DefaultChinookTarget: waypoint
Location: 41,56
Owner: Neutral
DefaultCameraPosition: waypoint
Location: 41,49
Owner: Neutral
waypoint16: waypoint
Location: 10,22
Owner: Neutral
waypoint15: waypoint
Location: 53,45
Owner: Neutral
waypoint14: waypoint
Location: 53,40
Owner: Neutral
waypoint13: waypoint
Location: 13,26
Owner: Neutral
waypoint12: waypoint
Location: 52,25
Owner: Neutral
waypoint11: waypoint
Location: 36,27
Owner: Neutral
waypoint10: waypoint
Location: 38,32
Owner: Neutral
waypoint9: waypoint
Location: 47,43
Owner: Neutral
waypoint8: waypoint
Location: 24,55
Owner: Neutral
waypoint7: waypoint
Location: 43,40
Owner: Neutral
waypoint6: waypoint
Location: 24,38
Owner: Neutral
waypoint5: waypoint
Location: 20,29
Owner: Neutral
waypoint4: waypoint
Location: 23,24
Owner: Neutral
waypoint3: waypoint
Location: 5,43
Owner: Neutral
waypoint2: waypoint
Location: 8,47
Owner: Neutral
waypoint1: waypoint
Location: 8,53
Owner: Neutral
waypoint0: waypoint
Location: 19,53
Owner: Neutral
Actor239: moneycrate
Owner: Neutral
Location: 5,42
Rules: cnc|rules/campaign-maprules.yaml, cnc|rules/campaign-tooltips.yaml, cnc|rules/campaign-palettes.yaml, rules.yaml

View File

@@ -0,0 +1,154 @@
World:
LuaScript:
Scripts: campaign-global.lua, gdi08a.lua, gdi08a-AI.lua
MusicPlaylist:
StartingMusic: march
VictoryMusic: gdi_win1
MissionData:
Briefing: U.N. Sanction has cut funding to the Global Defense Initiative. Field Units are helpless.\n\nUse the repair facility to keep your units in the field long enough to destroy the Nod base in this region.\n\nAll Nod units and structures must be destroyed.
BriefingVideo: gdi8a.vqa
BackgroundVideo: tbrinfo1.vqa
WinVideo: paratrop.vqa
LossVideo: gameover.vqa
SmudgeLayer@SCORCH:
InitialSmudges:
42,56: sc1,0
41,52: sc6,0
38,51: sc2,0
43,50: sc3,0
40,48: sc4,0
47,46: sc5,0
ATWR:
Buildable:
Prerequisites: ~disabled
NUK2:
Buildable:
Prerequisites: ~disabled
HPAD:
Buildable:
Prerequisites: ~disabled
BRIK:
Buildable:
Prerequisites: ~disabled
EYE:
Buildable:
Prerequisites: ~disabled
OBLI:
Buildable:
Prerequisites: ~disabled
TMPL:
Buildable:
Prerequisites: ~disabled
HTNK:
Buildable:
Prerequisites: ~disabled
TRAN:
Buildable:
Prerequisites: ~disabled
ORCA:
Buildable:
Prerequisites: ~disabled
RMBO:
Buildable:
Prerequisites: ~disabled
MSAM:
Buildable:
Prerequisites: ~disabled
MCV:
Buildable:
Prerequisites: ~disabled
BOAT:
Buildable:
Prerequisites: ~disabled
FTNK:
Buildable:
Prerequisites: ~disabled
STNK:
Buildable:
Prerequisites: ~disabled
HELI:
Buildable:
Prerequisites: ~disabled
LTNK:
Buildable:
Prerequisites: ~afld
ARTY:
Buildable:
Prerequisites: ~disabled
E4:
Buildable:
Prerequisites: barracks
E5:
Buildable:
Prerequisites: ~disabled
MLRS:
Buildable:
Prerequisites: ~disabled
CYCL:
Buildable:
Prerequisites: ~disabled
GTWR:
Buildable:
Prerequisites: ~disabled
SBAG:
Buildable:
Queue: Defence.GDI, Defence.Nod
GUN:
Buildable:
Queue: Defence.GDI, Defence.Nod
C3:
Tooltip:
Name: Farmer Mike
SAM:
Buildable:
Prerequisites: ~disabled
^Bridge:
DamageMultiplier@INVULNERABLE:
Modifier: 0
BRIDGEHUT:
-Targetable:
airstrike.proxy:
AirstrikePower:
SquadSize: 2
SquadOffset: -1536, 1024, 0
HQ:
Tooltip:
-AirstrikePower:
Buildable:
Description: Provides an overview of the battlefield.\n Requires power to operate.
AFLD:
RallyPoint:
Path: -5,2

View File

@@ -0,0 +1,204 @@
--[[
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.
]]
AttackPaths = { { waypoint7 }, { waypoint8 } }
NodBase = { handofnod, nodairfield, nodrefinery, NodCYard, nodpower1, nodpower2, nodpower3, nodpower4, nodpower5, gun5, gun6, gun7, gun8, nodsilo1, nodsilo2, nodsilo3, nodsilo4, nodobelisk }
PatrolProductionQueue = { }
InfantryAttackGroup = { }
InfantryGroupSize = 5
InfantryProductionCooldown = DateTime.Minutes(3)
InfantryProductionTypes = { "e1", "e1", "e1", "e3", "e3", "e4" }
HarvesterProductionType = { "harv" }
VehicleAttackGroup = { }
VehicleGroupSize = 5
VehicleProductionCooldown = DateTime.Minutes(3)
VehicleProductionTypes = { "bggy", "bggy", "bggy", "ltnk", "ltnk", "arty" }
StartingCash = 14000
BaseRefinery = { type = "proc", pos = CPos.New(12, 25) }
BaseNuke1 = { type = "nuke", pos = CPos.New(5, 24) }
BaseNuke2 = { type = "nuke", pos = CPos.New(3, 24) }
BaseNuke3 = { type = "nuke", pos = CPos.New(16, 30) }
BaseNuke4 = { type = "nuke", pos = CPos.New(14, 30) }
BaseNuke5 = { type = "nuke", pos = CPos.New(12, 30) }
InfantryProduction = { type = "hand", pos = CPos.New(15, 24) }
VehicleProduction = { type = "afld", pos = CPos.New(3, 27) }
NodGuards = { Actor168, Actor169, Actor170, Actor171, Actor172, Actor181, Actor177, Actor188, Actor189, Actor190 }
BaseBuildings = { BaseRefinery, BaseNuke1, BaseNuke2, BaseNuke3, BaseNuke4, InfantryProduction, VehicleProduction }
BuildBuilding = function(building, cyard)
local buildingCost = Actor.Cost(building.type)
if CyardIsBuilding or Nod.Cash < buildingCost then
Trigger.AfterDelay(DateTime.Seconds(10), function() BuildBuilding(building, cyard) end)
return
end
CyardIsBuilding = true
Nod.Cash = Nod.Cash - buildingCost
Trigger.AfterDelay(Actor.BuildTime(building.type), function()
CyardIsBuilding = false
if cyard.IsDead or cyard.Owner ~= Nod then
Nod.Cash = Nod.Cash + buildingCost
return
end
local actor = Actor.Create(building.type, true, { Owner = Nod, Location = building.pos })
if actor.Type == 'hand' or actor.Type == 'pyle' then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceInfantry(actor) end)
elseif actor.Type == 'afld' or actor.Type == 'weap' then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceVehicle(actor) end)
end
Trigger.OnKilled(actor, function()
BuildBuilding(building, cyard)
end)
RepairBuilding(Nod, actor, 0.75)
end)
end
HasHarvester = function()
local harv = Nod.GetActorsByType("harv")
return #harv > 0
end
GuardBase = function()
Utils.Do(NodBase, function(building)
Trigger.OnDamaged(building, function()
if not building.IsDead then
Utils.Do(NodGuards, function(guard)
if not guard.IsDead then
guard.Stop()
guard.Guard(building)
end
end)
end
end)
end)
end
ProduceHarvester = function(building)
if not buildingHarvester then
buildingHarvester = true
building.Build(HarvesterProductionType, function()
buildingHarvester = false
end)
end
end
ProduceInfantry = function(building)
if building.IsDead or building.Owner ~= Nod then
return
elseif not HasHarvester() then
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceInfantry(building) end)
return
end
if #PatrolProductionQueue >= 1 then
local inQueue = PatrolProductionQueue[1]
local toBuild = { inQueue.unit[1] }
local patrolPath = inQueue.waypoints
building.Build(toBuild, function(unit)
ReplenishPatrolUnit(unit[1], handofnod, patrolPath, 40)
table.remove(PatrolProductionQueue, 1)
end)
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceInfantry(building) end)
return
end
local delay = Utils.RandomInteger(DateTime.Seconds(3), DateTime.Seconds(9))
local toBuild = { Utils.Random(InfantryProductionTypes) }
local Path = Utils.Random(AttackPaths)
building.Build(toBuild, function(unit)
InfantryAttackGroup[#InfantryAttackGroup + 1] = unit[1]
if #InfantryAttackGroup >= InfantryGroupSize then
MoveAndHunt(InfantryAttackGroup, Path)
InfantryAttackGroup = { }
Trigger.AfterDelay(InfantryProductionCooldown, function() ProduceInfantry(building) end)
else
Trigger.AfterDelay(delay, function() ProduceInfantry(building) end)
end
end)
end
ProduceVehicle = function(building)
if building.IsDead or building.Owner ~= Nod then
return
elseif not HasHarvester() then
ProduceHarvester(building)
Trigger.AfterDelay(DateTime.Seconds(10), function() ProduceVehicle(building) end)
return
end
local delay = Utils.RandomInteger(DateTime.Seconds(12), DateTime.Seconds(17))
local toBuild = { Utils.Random(VehicleProductionTypes) }
local Path = Utils.Random(AttackPaths)
building.Build(toBuild, function(unit)
VehicleAttackGroup[#VehicleAttackGroup + 1] = unit[1]
if #VehicleAttackGroup >= VehicleGroupSize then
MoveAndHunt(VehicleAttackGroup, Path)
VehicleAttackGroup = { }
Trigger.AfterDelay(VehicleProductionCooldown, function() ProduceVehicle(building) end)
else
Trigger.AfterDelay(delay, function() ProduceVehicle(building) end)
end
end)
end
StartAI = function()
RepairNamedActors(Nod, 0.75)
Nod.Cash = StartingCash
GuardBase()
end
Trigger.OnAllKilledOrCaptured(NodBase, function()
Utils.Do(Nod.GetGroundAttackers(), IdleHunt)
end)
Trigger.OnKilled(nodrefinery, function(building)
BuildBuilding(BaseRefinery, NodCYard)
end)
Trigger.OnKilled(nodpower1, function(building)
BuildBuilding(BaseNuke1, NodCYard)
end)
Trigger.OnKilled(nodpower2, function(building)
BuildBuilding(BaseNuke2, NodCYard)
end)
Trigger.OnKilled(nodpower3, function(building)
BuildBuilding(BaseNuke3, NodCYard)
end)
Trigger.OnKilled(nodpower4, function(building)
BuildBuilding(BaseNuke4, NodCYard)
end)
Trigger.OnKilled(nodpower5, function(building)
BuildBuilding(BaseNuke5, NodCYard)
end)
Trigger.OnKilled(handofnod, function(building)
BuildBuilding(InfantryProduction, NodCYard)
end)
Trigger.OnKilled(nodairfield, function(building)
BuildBuilding(VehicleProduction, NodCYard)
end)

View File

@@ -0,0 +1,170 @@
--[[
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.
]]
InsertionHelicopterType = "tran.insertion"
GDIHeliReinfUnits = { "e2", "e2", "e2", "e3", "e3" }
SamSites = { sam1, sam2, sam3, sam4 }
NodBunkersNorth = { gun3, gun4 }
NodBunkersSouth = { gun1, gun2 }
BoatEscapeTrigger = { CPos.New(2,37) }
WaypointGroup1 = { waypoint1, waypoint2, waypoint8 }
WaypointGroup2 = { waypoint1, waypoint2, waypoint3, waypoint9 }
WaypointGroup3 = { waypoint1, waypoint2, waypoint3, waypoint10, waypoint11, waypoint12, waypoint6, waypoint13 }
WaypointGroup4 = { waypoint1, waypoint2, waypoint3, waypoint4 }
Patrol1Waypoints = { waypoint11.Location, waypoint10.Location }
Patrol2Waypoints = { waypoint1.Location, waypoint2.Location, waypoint3.Location, waypoint4.Location, waypoint5.Location, waypoint4.Location, waypoint3.Location, waypoint2.Location, waypoint1.Location, waypoint6.Location }
Nod1 = { units = { ['e1'] = 2, ['e3'] = 2 }, waypoints = WaypointGroup1, delay = 40 }
Nod2 = { units = { ['e3'] = 2, ['e4'] = 2 }, waypoints = WaypointGroup2, delay = 50 }
Nod3 = { units = { ['e1'] = 2, ['e3'] = 3, ['e4'] = 2 }, waypoints = WaypointGroup1, delay = 50 }
Nod4 = { units = { ['bggy'] = 2 }, waypoints = WaypointGroup2, delay = 50 }
Nod5 = { units = { ['e4'] = 2, ['ltnk'] = 1 }, waypoints = WaypointGroup1, delay = 50 }
Auto1 = { units = { ['e4'] = 2, ['arty'] = 1 }, waypoints = WaypointGroup1, delay = 50 }
Auto2 = { units = { ['e1'] = 2, ['e3'] = 2 }, waypoints = WaypointGroup2, delay = 50 }
Auto3 = { units = { ['e3'] = 2, ['e4'] = 2 }, waypoints = WaypointGroup1, delay = 50 }
Auto4 = { units = { ['e1'] = 3, ['e4'] = 1 }, waypoints = WaypointGroup1, delay = 50 }
Auto5 = { units = { ['ltnk'] = 1, ['bggy'] = 1 }, waypoints = WaypointGroup1, delay = 60 }
Auto6 = { units = { ['bggy'] = 1 }, waypoints = WaypointGroup2, delay = 50 }
Auto7 = { units = { ['ltnk'] = 1 }, waypoints = WaypointGroup2, delay = 50 }
Auto8 = { units = { ['e4'] = 2, ['bggy'] = 1 }, waypoints = WaypointGroup4, delay = 0 }
Patrols = {
grd1 = { units = { ['e3'] = 3 }, waypoints = Patrol1Waypoints, wait = 40, initialWaypointPlacement = { 1 } },
grd2 = { units = { ['e1'] = 2, ['e3'] = 2, ['e4'] = 2 }, waypoints = Patrol2Waypoints, wait = 20, initialWaypointPlacement = { 4, 10, 1 } }
}
AutoAttackWaves = { Nod1, Nod2, Nod3, Nod4, Nod5, Auto1, Auto2, Auto3, Auto4, Auto5, Auto6, Auto7, Auto8 }
StationaryGuards = { Actor174, Actor173, Actor182, Actor183, Actor184, Actor185, Actor186, Actor187 , Actor199, Actor200, Actor201, Actor202, Actor203, Actor204 }
StartStationaryGuards = function()
Utils.Do(StationaryGuards, function(unit)
if not unit.IsDead then
unit.Patrol( { unit.Location } , true, 20)
end
end)
end
SendWaves = function(counter, Waves)
if counter <= #Waves then
local team = Waves[counter]
for type, amount in pairs(team.units) do
MoveAndHunt(Utils.Take(amount, Nod.GetActorsByType(type)), team.waypoints)
end
Trigger.AfterDelay(DateTime.Seconds(team.delay), function() SendWaves(counter + 1, Waves) end)
end
end
StartPatrols = function()
for k, team in pairs(Patrols) do
local group = 1
for type, amount in pairs(team.units) do
for i = 1, amount do
Reinforcements.Reinforce(Nod, { type }, { team.waypoints[team.initialWaypointPlacement[group]] }, 0, function(unit)
ReplenishPatrolUnit(unit, handofnod, team.waypoints, team.wait)
end)
end
group = group + 1
end
end
Patrols = nil
end
ReplenishPatrolUnit = function(unit, building, waypoints, waitatwaypoint)
unit.Patrol(waypoints, true, waitatwaypoint)
Trigger.OnKilled(unit, function()
local queueUnit = { unit = { unit.Type }, atbuilding = { building }, waypoints = waypoints }
PatrolProductionQueue[#PatrolProductionQueue + 1] = queueUnit
end)
end
SendGDIReinforcements = function()
Media.PlaySpeechNotification(GDI, "Reinforce")
Reinforcements.ReinforceWithTransport(GDI, InsertionHelicopterType, GDIHeliReinfUnits, { GDIHeliEntryNorth.Location, GDIHeliLZ.Location }, { GDIHeliLZ.Location + CVec.New(20, 0) })
end
SendGDIReinforcementChinook = function()
Reinforcements.ReinforceWithTransport(GDI, 'tran', nil, { GDIHeliEntryNorth.Location, GDIHeliLZ.Location })
end
SpawnGunboat = function()
Media.PlaySpeechNotification(GDI, "Reinforce")
Actor.Create("boat", true, { Owner = GDI, Facing = 0, Location = CPos.New(62,37) })
end
WorldLoaded = function()
GDI = Player.GetPlayer("GDI")
Nod = Player.GetPlayer("Nod")
Camera.Position = DefaultCameraPosition.CenterPosition
DestroyBunkers = GDI.AddObjective("Destroy the Nod bunkers to allow Carter's\nconvoy to pass through safely.")
Trigger.OnAllKilled(NodBunkersNorth, function()
GDI.MarkCompletedObjective(DestroyBunkers)
Trigger.AfterDelay(DateTime.Seconds(1), SpawnGunboat)
end)
Trigger.OnAllKilled(NodBunkersSouth, function()
GDI.MarkCompletedObjective(DestroyBunkers)
SendGDIReinforcementChinook()
Trigger.AfterDelay(DateTime.Seconds(1), SpawnGunboat)
end)
Trigger.OnEnteredFootprint(BoatEscapeTrigger, function(a, id)
if a.Type == "boat" then
a.Destroy()
Media.DisplayMessage("Part of Carter's convoy passed through!")
Media.PlaySoundNotification(GDI, "AlertBleep")
end
end)
SecureArea = GDI.AddObjective("Destroy the Nod strike force.")
KillGDI = Nod.AddObjective("Kill all enemies!")
Trigger.AfterDelay(DateTime.Seconds(5), SendGDIReinforcements)
AirSupport = GDI.AddObjective("Destroy the SAM sites to receive air support.", "Secondary", false)
Trigger.OnAllKilled(SamSites, function()
GDI.MarkCompletedObjective(AirSupport)
Actor.Create("airstrike.proxy", true, { Owner = GDI })
end)
Actor.Create("flare", true, { Owner = GDI, Location = DefaultFlareLocation.Location })
StartStationaryGuards()
StartAI()
StartPatrols()
InitObjectives(GDI)
Trigger.AfterDelay(DateTime.Minutes(1), function() SendWaves(1, AutoAttackWaves) end)
Trigger.AfterDelay(DateTime.Minutes(3), function() ProduceInfantry(handofnod) end)
Trigger.AfterDelay(DateTime.Minutes(3), function() ProduceVehicle(nodairfield) end)
local initialArrivingUnits = { Actor175, Actor191, Actor192, Actor193, Actor194, Actor195, Actor196, Actor197, Actor198 }
Utils.Do(initialArrivingUnits, function(unit)
unit.Move(unit.Location + CVec.New(0, 1), 0)
end)
end
Tick = function()
if DateTime.GameTime > DateTime.Seconds(5) then
if GDI.HasNoRequiredUnits() then
Nod.MarkCompletedObjective(KillGDI)
end
if Nod.HasNoRequiredUnits() then
GDI.MarkCompletedObjective(SecureArea)
end
end
end

BIN
mods/cnc/maps/gdi09/map.bin Normal file

Binary file not shown.

BIN
mods/cnc/maps/gdi09/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,785 @@
MapFormat: 11
RequiresMod: cnc
Title: 09: Secure the Danube
Author: Westwood Studios
Tileset: WINTER
MapSize: 64,64
Bounds: 2,3,60,48
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: gdi
PlayerReference@Nod:
Name: Nod
Faction: nod
Color: FF1400
Allies: Nod
Enemies: GDI
Bot: campaign
PlayerReference@GDI:
Name: GDI
AllowBots: False
Playable: True
Required: True
LockFaction: True
Faction: gdi
LockColor: True
Color: F5D378
LockSpawn: True
LockTeam: True
Allies: GDI
Enemies: Nod
Actors:
Actor0: sbag
Location: 30,43
Owner: Neutral
Actor1: sbag
Location: 29,43
Owner: Neutral
Actor2: sbag
Location: 28,43
Owner: Neutral
Actor3: sbag
Location: 27,43
Owner: Neutral
Actor4: sbag
Location: 26,43
Owner: Neutral
Actor5: sbag
Location: 30,42
Owner: Neutral
Actor6: sbag
Location: 28,42
Owner: Neutral
Actor7: sbag
Location: 26,42
Owner: Neutral
Actor8: sbag
Location: 35,33
Owner: Neutral
Actor9: sbag
Location: 33,33
Owner: Neutral
Actor10: sbag
Location: 31,33
Owner: Neutral
Actor11: brik
Location: 21,33
Owner: Neutral
Actor12: brik
Location: 20,33
Owner: Neutral
Actor13: brik
Location: 19,33
Owner: Neutral
Actor14: brik
Location: 18,33
Owner: Neutral
Actor15: brik
Location: 17,33
Owner: Neutral
Actor16: brik
Location: 16,33
Owner: Neutral
Actor17: brik
Location: 15,33
Owner: Neutral
Actor18: brik
Location: 14,33
Owner: Neutral
Actor19: brik
Location: 13,33
Owner: Neutral
Actor20: brik
Location: 12,33
Owner: Neutral
Actor21: brik
Location: 11,33
Owner: Neutral
Actor22: brik
Location: 10,33
Owner: Neutral
Actor23: brik
Location: 9,33
Owner: Neutral
Actor24: brik
Location: 8,33
Owner: Neutral
Actor25: brik
Location: 7,33
Owner: Neutral
Actor26: brik
Location: 6,33
Owner: Neutral
Actor27: brik
Location: 5,33
Owner: Neutral
Actor28: brik
Location: 4,33
Owner: Neutral
Actor29: brik
Location: 3,33
Owner: Neutral
Actor30: brik
Location: 2,33
Owner: Neutral
Actor31: sbag
Location: 35,32
Owner: Neutral
Actor32: sbag
Location: 34,32
Owner: Neutral
Actor33: sbag
Location: 33,32
Owner: Neutral
Actor34: sbag
Location: 32,32
Owner: Neutral
Actor35: sbag
Location: 31,32
Owner: Neutral
Actor36: brik
Location: 21,32
Owner: Neutral
Actor37: brik
Location: 2,32
Owner: Neutral
Actor38: brik
Location: 21,31
Owner: Neutral
Actor39: brik
Location: 20,31
Owner: Neutral
Actor40: brik
Location: 2,31
Owner: Neutral
Actor41: brik
Location: 21,30
Owner: Neutral
Actor42: brik
Location: 20,30
Owner: Neutral
Actor43: brik
Location: 2,30
Owner: Neutral
Actor44: wood
Location: 31,29
Owner: Neutral
Actor45: brik
Location: 2,29
Owner: Neutral
Actor46: wood
Location: 31,28
Owner: Neutral
Actor47: brik
Location: 2,28
Owner: Neutral
Actor48: wood
Location: 42,27
Owner: Neutral
Actor49: wood
Location: 41,27
Owner: Neutral
Actor50: wood
Location: 40,27
Owner: Neutral
Actor51: wood
Location: 39,27
Owner: Neutral
Actor52: brik
Location: 2,27
Owner: Neutral
Actor53: wood
Location: 42,26
Owner: Neutral
Actor54: v16
Location: 41,26
Owner: Neutral
Actor55: v18
Location: 40,26
Owner: Neutral
Actor56: wood
Location: 39,26
Owner: Neutral
Actor57: brik
Location: 21,26
Owner: Neutral
Actor58: brik
Location: 20,26
Owner: Neutral
Actor59: brik
Location: 2,26
Owner: Neutral
Actor60: wood
Location: 42,25
Owner: Neutral
Actor61: v14
Location: 41,25
Owner: Neutral
Actor62: v15
Location: 40,25
Owner: Neutral
Actor63: wood
Location: 39,25
Owner: Neutral
Actor64: brik
Location: 21,25
Owner: Neutral
Actor65: brik
Location: 20,25
Owner: Neutral
Actor66: brik
Location: 2,25
Owner: Neutral
Actor67: wood
Location: 42,24
Owner: Neutral
Actor68: wood
Location: 39,24
Owner: Neutral
Actor69: brik
Location: 21,24
Owner: Neutral
Actor70: brik
Location: 14,24
Owner: Neutral
Actor71: brik
Location: 13,24
Owner: Neutral
Actor72: brik
Location: 8,24
Owner: Neutral
Actor73: brik
Location: 7,24
Owner: Neutral
Actor74: brik
Location: 2,24
Owner: Neutral
Actor75: brik
Location: 21,23
Owner: Neutral
Actor76: brik
Location: 20,23
Owner: Neutral
Actor77: brik
Location: 19,23
Owner: Neutral
Actor78: brik
Location: 18,23
Owner: Neutral
Actor79: brik
Location: 17,23
Owner: Neutral
Actor80: brik
Location: 16,23
Owner: Neutral
Actor81: brik
Location: 15,23
Owner: Neutral
Actor82: brik
Location: 14,23
Owner: Neutral
Actor83: brik
Location: 13,23
Owner: Neutral
Actor84: brik
Location: 8,23
Owner: Neutral
Actor85: brik
Location: 7,23
Owner: Neutral
Actor86: brik
Location: 6,23
Owner: Neutral
Actor87: brik
Location: 5,23
Owner: Neutral
Actor88: brik
Location: 4,23
Owner: Neutral
Actor89: brik
Location: 3,23
Owner: Neutral
Actor90: brik
Location: 2,23
Owner: Neutral
Actor91: t02
Location: 46,27
Owner: Neutral
Actor92: t02
Location: 14,19
Owner: Neutral
Actor93: t01
Location: 18,19
Owner: Neutral
Actor94: t14
Location: 44,27
Owner: Neutral
Actor95: tc01
Location: 43,24
Owner: Neutral
Actor96: tc02
Location: 42,27
Owner: Neutral
Actor97: tc04
Location: 45,24
Owner: Neutral
Actor98: split2
Location: 55,23
Owner: Neutral
Actor99: split2
Location: 60,28
Owner: Neutral
Actor100: tc04
Location: 34,26
Owner: Neutral
Actor101: tc02
Location: 37,25
Owner: Neutral
Actor102: t10
Location: 32,27
Owner: Neutral
Actor103: t01
Location: 34,25
Owner: Neutral
Actor104: t17
Location: 35,24
Owner: Neutral
Actor105: tc01
Location: 31,26
Owner: Neutral
Actor106: tc04
Location: 51,15
Owner: Neutral
Actor107: tc04
Location: 37,5
Owner: Neutral
Actor108: tc04
Location: 39,14
Owner: Neutral
Actor109: tc04
Location: 30,6
Owner: Neutral
Actor110: tc02
Location: 29,14
Owner: Neutral
Actor111: tc01
Location: 44,12
Owner: Neutral
Actor112: tc01
Location: 32,9
Owner: Neutral
Actor113: t03
Location: 34,17
Owner: Neutral
Actor114: t02
Location: 44,20
Owner: Neutral
Actor115: t01
Location: 42,19
Owner: Neutral
Actor116: t01
Location: 43,10
Owner: Neutral
Actor117: t01
Location: 35,15
Owner: Neutral
Actor118: t01
Location: 31,10
Owner: Neutral
Actor119: tc02
Location: 22,42
Owner: Neutral
Actor120: tc04
Location: 2,47
Owner: Neutral
Actor121: tc05
Location: 15,52
Owner: Neutral
Actor122: tc02
Location: 27,53
Owner: Neutral
Actor123: t01
Location: 30,50
Owner: Neutral
Actor124: t02
Location: 23,52
Owner: Neutral
Actor125: t03
Location: 33,45
Owner: Neutral
Actor126: tc01
Location: 22,22
Owner: Neutral
Actor127: t07
Location: 5,5
Owner: Neutral
Actor128: tc04
Location: 8,4
Owner: Neutral
Actor129: tc01
Location: 3,4
Owner: Neutral
Actor130: t07
Location: 38,23
Owner: Neutral
Actor131: t11
Location: 30,43
Owner: Neutral
Actor132: tc04
Location: 24,43
Owner: Neutral
Actor133: tc04
Location: 25,29
Owner: Neutral
Actor134: tc05
Location: 52,22
Owner: Neutral
gun1: gun
Location: 27,42
Owner: Nod
gun2: gun
Location: 29,42
Owner: Nod
gun3: gun
Location: 34,33
Owner: Nod
Facing: 159
gun4: gun
Location: 32,33
Owner: Nod
Facing: 159
Actor139: v04
Location: 45,26
Owner: Neutral
Actor140: v07
Location: 51,24
Owner: Neutral
Actor141: v06
Location: 40,24
Owner: Neutral
Actor142: v05
Location: 43,26
Owner: Neutral
Actor143: v03
Location: 35,25
Owner: Neutral
Actor144: v01
Location: 37,26
Owner: Neutral
sam1: sam
Location: 3,4
Owner: Nod
sam2: sam
Location: 19,24
Owner: Nod
sam3: sam
Location: 2,22
Owner: Nod
sam4: sam
Location: 19,32
Owner: Nod
nodpower1: nuke
Location: 5,24
Owner: Nod
nodpower2: nuke
Location: 3,24
Owner: Nod
gun5: gun
Location: 13,22
Owner: Nod
gun6: gun
Location: 8,22
Owner: Nod
NodCYard: fact
Location: 7,30
Owner: Nod
nodobelisk: obli
Location: 18,32
Owner: Nod
nodpower3: nuke
Location: 16,30
Owner: Nod
nodpower4: nuke
Location: 14,30
Owner: Nod
nodsilo1: silo
Location: 5,31
Owner: Nod
nodsilo2: silo
Location: 10,31
Owner: Nod
nodsilo3: silo
Location: 3,31
Owner: Nod
nodrefinery: proc
Location: 12,25
Owner: Nod
FreeActor: False
nodairfield: afld
Location: 3,27
Owner: Nod
handofnod: hand
Location: 15,25
Owner: Nod
nodsilo4: silo
Location: 17,24
Owner: Nod
nodpower5: nuke
Location: 12,30
Owner: Nod
gun7: gun
Location: 22,25
Owner: Nod
Facing: 191
gun8: gun
Location: 22,31
Owner: Nod
Facing: 191
Actor167: harv
Location: 20,28
Owner: Nod
Facing: 191
Actor168: ltnk
Location: 19,26
Owner: Nod
Facing: 127
Actor169: ltnk
Location: 18,26
Owner: Nod
Facing: 127
Actor170: ltnk
Location: 8,28
Owner: Nod
Facing: 127
Actor171: bggy
Location: 18,31
Owner: Nod
Actor172: bggy
Location: 19,31
Owner: Nod
Actor173: bggy
Location: 40,14
Owner: Nod
Actor174: arty
Location: 8,15
Owner: Nod
Facing: 31
Actor175: mcv
Location: 54,4
Owner: GDI
Facing: 127
Actor176: e3
Location: 19,25
Owner: Nod
SubCell: 1
Actor177: e3
Location: 17,26
Owner: Nod
SubCell: 3
Actor178: e3
Location: 8,25
Owner: Nod
SubCell: 1
Actor179: e3
Location: 10,29
Owner: Nod
SubCell: 0
Actor180: e1
Location: 5,30
Owner: Nod
SubCell: 0
Actor181: e1
Location: 8,26
Owner: Nod
SubCell: 4
Actor182: e4
Location: 7,14
Owner: Nod
Facing: 31
SubCell: 3
Actor183: e4
Location: 7,14
Owner: Nod
Facing: 31
SubCell: 2
Actor184: e4
Location: 25,6
Owner: Nod
Facing: 191
SubCell: 1
Actor185: e4
Location: 25,4
Owner: Nod
Facing: 191
SubCell: 3
Actor186: e3
Location: 33,9
Owner: Nod
SubCell: 0
Actor187: e3
Location: 31,6
Owner: Nod
SubCell: 3
Actor188: e3
Location: 17,27
Owner: Nod
SubCell: 2
Actor189: e3
Location: 17,26
Owner: Nod
SubCell: 2
Actor190: e3
Location: 17,27
Owner: Nod
SubCell: 0
Actor191: e2
Location: 53,6
Owner: GDI
Facing: 127
SubCell: 4
Actor192: e2
Location: 54,6
Owner: GDI
Facing: 127
SubCell: 3
Actor193: e2
Location: 53,7
Owner: GDI
Facing: 127
SubCell: 2
Actor194: e3
Location: 54,6
Owner: GDI
Facing: 127
SubCell: 4
Actor195: e3
Location: 55,6
Owner: GDI
Facing: 127
SubCell: 3
Actor196: e3
Location: 55,7
Owner: GDI
Facing: 127
SubCell: 1
Actor197: e3
Location: 54,7
Owner: GDI
Facing: 127
SubCell: 2
Actor198: e2
Location: 54,7
Owner: GDI
Facing: 127
SubCell: 1
Actor199: e3
Location: 25,34
Owner: Nod
Facing: 127
SubCell: 4
Actor200: e3
Location: 25,34
Owner: Nod
Facing: 127
SubCell: 3
Actor201: e3
Location: 24,34
Owner: Nod
Facing: 127
SubCell: 4
Actor202: e3
Location: 25,34
Owner: Nod
Facing: 127
SubCell: 1
Actor203: e3
Location: 24,34
Owner: Nod
Facing: 127
SubCell: 2
Actor204: e3
Location: 25,34
Owner: Nod
Facing: 127
SubCell: 2
GDIHeliEntryNorth: waypoint
Location: 57,6
Owner: Neutral
DefaultCameraPosition: waypoint
Location: 48,3
Owner: Neutral
DefaultFlareLocation: waypoint
Location: 33,31
Owner: Neutral
waypoint23: waypoint
Location: 53,37
Owner: Neutral
waypoint19: waypoint
Location: 52,42
Owner: Neutral
waypoint18: waypoint
Location: 52,19
Owner: Neutral
waypoint17: waypoint
Location: 9,30
Owner: Neutral
waypoint11: waypoint
Location: 23,24
Owner: Neutral
waypoint10: waypoint
Location: 23,32
Owner: Neutral
waypoint9: waypoint
Location: 43,5
Owner: Neutral
waypoint8: waypoint
Location: 60,18
Owner: Neutral
waypoint7: waypoint
Location: 52,6
Owner: Neutral
waypoint6: waypoint
Location: 9,16
Owner: Neutral
waypoint5: waypoint
Location: 4,10
Owner: Neutral
waypoint4: waypoint
Location: 12,5
Owner: Neutral
waypoint3: waypoint
Location: 28,5
Owner: Neutral
waypoint2: waypoint
Location: 30,20
Owner: Neutral
waypoint1: waypoint
Location: 23,28
Owner: Neutral
GDIHeliLZ: waypoint
Location: 41,44
Owner: Neutral
Actor224: moneycrate
Owner: Neutral
Location: 5,48
Rules: cnc|rules/campaign-maprules.yaml, cnc|rules/campaign-tooltips.yaml, cnc|rules/campaign-palettes.yaml, rules.yaml
Weapons: weapons.yaml

View File

@@ -0,0 +1,146 @@
World:
LuaScript:
Scripts: campaign-global.lua, gdi09.lua, gdi09-AI.lua
MusicPlaylist:
StartingMusic: march
VictoryMusic: gdi_win1
MissionData:
Briefing: Take out Nod turrets along shore so Gunboats can move in safely on the Nod base.\n\nThe Nod base must be destroyed.\n\nIf gunboats can get in, they should be able to destroy the base with no difficulty.\n\nKeep an eye out for the new weapon Nod is rumored to be working on.
BriefingVideo: gdi9.vqa
WinVideo: gunboat.vqa
LossVideo: gameover.vqa
ATWR:
Buildable:
Prerequisites: ~disabled
NUK2:
Buildable:
Prerequisites: ~disabled
HPAD:
Buildable:
Prerequisites: ~disabled
BRIK:
Buildable:
Prerequisites: ~disabled
EYE:
Buildable:
Prerequisites: ~disabled
GUN:
Buildable:
Prerequisites: ~disabled
OBLI:
Buildable:
Prerequisites: ~disabled
TMPL:
Buildable:
Prerequisites: ~disabled
HTNK:
Buildable:
Prerequisites: ~disabled
TRAN:
Buildable:
Prerequisites: ~disabled
ORCA:
Buildable:
Prerequisites: ~disabled
RMBO:
Buildable:
Prerequisites: ~disabled
MSAM:
Buildable:
Prerequisites: ~disabled
MCV:
Buildable:
Prerequisites: ~disabled
BOAT:
Buildable:
Prerequisites: ~disabled
FTNK:
Buildable:
Prerequisites: ~disabled
STNK:
Buildable:
Prerequisites: ~disabled
HELI:
Buildable:
Prerequisites: ~disabled
LTNK:
Buildable:
Prerequisites: ~afld
ARTY:
Buildable:
Prerequisites: ~afld
E4:
Buildable:
Prerequisites: barracks
E5:
Buildable:
Prerequisites: ~disabled
MLRS:
Buildable:
Prerequisites: ~disabled
SAM:
Buildable:
Prerequisites: ~disabled
^Bridge:
DamageMultiplier@INVULNERABLE:
Modifier: 0
BRIDGEHUT:
-Targetable:
TRAN.Extraction:
Inherits: TRAN
RevealsShroud:
Range: 0c0
RejectsOrders:
-Selectable:
RenderSprites:
Image: tran
Interactable:
TRAN.Insertion:
Inherits: TRAN.Extraction
Cargo:
MaxWeight: 0
HQ:
Tooltip:
-AirstrikePower:
Buildable:
Description: Provides an overview of the battlefield.\n Requires power to operate.
airstrike.proxy:
AirstrikePower:
SquadSize: 2
SquadOffset: -1536, 1024, 0
BOAT:
AutoTarget:
InitialStance: AttackAnything
RejectsOrders:
Except: Attack

View File

@@ -0,0 +1,13 @@
BoatMissile:
Warhead@Bonus: SpreadDamage
Spread: 128
Damage: 2000
Versus:
None: 50
Wood: 100
Light: 100
Heavy: 100
DamageTypes: Prone50Percent, TriggerProne, ExplosionDeath
TurretGun:
ReloadDelay: 40

View File

@@ -105,13 +105,11 @@ SendReinforcementsWave = function(team)
transport.UnloadPassengers()
Trigger.OnPassengerExited(transport, function(_, passenger)
Utils.Do(passengers, function(actor)
if actor.Type == "e6" then
CaptureStructures(actor)
else
IdleHunt(actor)
end
end)
if passenger.Type == "e6" then
Trigger.OnIdle(passenger, CaptureStructures)
else
IdleHunt(passenger)
end
if not transport.HasPassengers then
IdleHunt(transport)
@@ -177,6 +175,10 @@ WorldLoaded = function()
EliminateGDI = Nod.AddObjective("Eliminate all GDI forces in the area.")
BuildSAMs = Nod.AddObjective("Build 3 SAMs to fend off the GDI bombers.", "Secondary", false)
GDIObjective = GDI.AddObjective("Eliminate all Nod forces in the area.")
Trigger.OnKilled(GDIProc, function()
Actor.Create("moneycrate", true, { Owner = GDI, Location = CPos.New(24, 54) })
end)
end
Tick = function()

View File

@@ -12,7 +12,7 @@ WaypointGroup2 = { waypoint0, waypoint1, waypoint2, waypoint3, waypoint4, waypoi
WaypointGroup3 = { waypoint0, waypoint1, waypoint2, waypoint3, waypoint9, waypoint10, waypoint11, waypoint6, waypoint7 }
WaypointGroup4 = { waypoint9, waypoint10, waypoint11, waypoint6, waypoint7, waypoint14 }
GDI1 = { units = { "e2", "e2", "e6" }, waypoints = WaypointGroup4, delay = 40 }
GDI1 = { units = { "e2", "e2", "e6", "e6", "e6" }, waypoints = WaypointGroup4, delay = 40 }
GDI2 = { units = { "e1", "e2" }, waypoints = WaypointGroup3, delay = 40 }
GDI3 = { units = { "e2", "e3", "jeep" }, waypoints = WaypointGroup2, delay = 40 }
GDI4 = { units = { "mtnk" }, waypoints = WaypointGroup3, delay = 40 }
@@ -100,17 +100,18 @@ SendWaves = function(counter, Waves)
end
SendReinforcementsWave = function(team)
Reinforcements.ReinforceWithTransport(GDI, "apc", GetCargo(team), { ReinforcementsGDISpawn.Location, waypoint12.Location}, nil, function(transport, passengers)
MoveAndHunt(transport, team.waypoints)
Reinforcements.ReinforceWithTransport(GDI, "apc", team.units, { ReinforcementsGDISpawn.Location, waypoint12.Location}, nil, function(transport, passengers)
Utils.Do(team.waypoints, function(waypoint)
transport.Move(waypoint.Location)
end)
transport.UnloadPassengers()
Trigger.OnPassengerExited(transport, function(_, passenger)
Utils.Do(passengers, function(actor)
if actor.Type == "e6" then
CaptureStructures(actor)
else
IdleHunt(actor)
end
end)
if passenger.Type == "e6" then
Trigger.OnIdle(passenger, CaptureStructures)
else
IdleHunt(passenger)
end
if not transport.HasPassengers then
IdleHunt(transport)

View File

@@ -156,12 +156,6 @@ FACTOUT.IN:
Capturable:
Types: building
MoneyCrate:
Inherits: ^Crate
GiveCashCrateAction:
Amount: 2000
UseCashTick: true
NUKEOUT.IN:
Inherits: NUKE
RenderSprites:

View File

@@ -189,12 +189,6 @@ FACTOUT.IN:
Capturable:
Types: building
MoneyCrate:
Inherits: ^Crate
GiveCashCrateAction:
Amount: 2000
UseCashTick: true
NUKEOUT.IN:
Inherits: NUKE
RenderSprites:

View File

@@ -696,6 +696,9 @@ Actors:
Location: 54,6
Facing: 92
TurretFacing: 92
Actor245: moneycrate
Location: 5,6
Owner: Neutral
waypoint27: waypoint
Location: 32,32
Owner: Neutral

View File

@@ -9,6 +9,8 @@ GDI Campaign:
gdi05b
gdi06
gdi07
gdi08a
gdi09
Nod Campaign:
nod01

View File

@@ -255,7 +255,7 @@ GameSpeeds:
OrderLatency: 6
ColorValidator:
TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, b6f706, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 380135, 1d06f7, 328dff, 78dbf8, cef6b1, 000000
TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, 94b319, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 502048, 1d06f7, 328dff, 78dbf8, cef6b1, 391d1d
ModContent:
InstallPromptMessage: Tiberian Dawn requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2007 C&C Gold freeware release.\n\nAdvanced Install includes options for downloading the music and for\ncopying the videos and other content from an original game disc.

View File

@@ -63,6 +63,17 @@ airstrike.proxy:
CircleSequence: circles
SupportPowerPaletteOrder: 10
MoneyCrate:
Inherits: ^Crate
Tooltip:
Name: Money Crate
GiveCashCrateAction:
Amount: 2000
Sequence: dollar
UseCashTick: true
RenderSprites:
Image: wcrate
TRUCK:
Buildable:
Prerequisites: ~disabled

View File

@@ -425,7 +425,7 @@ AFLD:
Queue: Building.Nod
Description: Provides a dropzone\nfor vehicle reinforcements
Building:
Footprint: XXXX xxxx ====
Footprint: XXX+ xxx+ ====
Dimensions: 4,3
LocalCenterOffset: 0,-512,0
Health:

View File

@@ -175,6 +175,14 @@ crate:
idle:
ZOffset: -511
scrate:
idle:
ZOffset: -511
wcrate:
idle:
ZOffset: -511
xcratea:
idle: xcrate
ZOffset: -511

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 991 KiB

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -173,8 +173,10 @@ Background@SAVE_MAP_PANEL:
Key: escape
ScrollPanel@MAP_SAVE_VISIBILITY_PANEL:
TopBottomSpacing: 5
ItemSpacing: 5
Width: 220
Height: 64
Height: 55
Children:
Checkbox@VISIBILITY_TEMPLATE:
X: 5
@@ -618,29 +620,30 @@ Container@EDITOR_WORLD_ROOT:
Contrast: true
ScrollPanel@CATEGORY_FILTER_PANEL:
Width: 170
Width: 240
TopBottomSpacing: 5
ItemSpacing: 5
Children:
Container@SELECT_CATEGORIES_BUTTONS:
Width: PARENT_RIGHT
Height: 29
Height: 25
Children:
Button@SELECT_ALL:
X: 10
Y: 2
Width: 60
Y: 0 - 5
Width: 93
Height: 25
Text: All
Font: Bold
Button@SELECT_NONE:
X: PARENT_RIGHT - WIDTH - 34
Y: 2
Width: 60
X: 10 + 93 + 10
Y: 0 - 5
Width: 93
Height: 25
Text: None
Font: Bold
Checkbox@CATEGORY_TEMPLATE:
X: 5
Y: 5
Width: PARENT_RIGHT - 29
Height: 20
Visible: false

View File

@@ -185,7 +185,13 @@ Background@MAINMENU_INTRODUCTION_PROMPT:
Height: 16
Font: Small
Text: - Zoom the battlefield using {MODIFIER + Scroll Wheel}
LabelWithHighlight@DESC_SCROLL:
LabelWithHighlight@DESC_SCROLL_RIGHT:
X: 265
Y: 34
Height: 16
Font: Small
Text: - Pan the battlefield using the {Right} mouse button
LabelWithHighlight@DESC_SCROLL_MIDDLE:
X: 265
Y: 34
Height: 16

View File

@@ -91,7 +91,7 @@ Background@SETTINGS_PANEL:
X: 5
Y: 50
Width: PARENT_RIGHT - 10
Height: PARENT_BOTTOM
Height: PARENT_BOTTOM - 50
Children:
Label@PLAYER:
Text: Player Name:
@@ -249,13 +249,6 @@ Background@SETTINGS_PANEL:
Height: 25
MaxLength: 5
Type: Integer
Label@WINDOW_CHANGES_DESC:
X: 60
Y: 27
Width: 200
Height: 15
Font: Tiny
Text: Video mode and window size changes require restart
Container@DISPLAY_SELECTION:
Y: 240
Children:
@@ -271,13 +264,6 @@ Background@SETTINGS_PANEL:
Height: 25
Font: Regular
Text: Standard
Label@MODE_CHANGES_DESC:
X: 60
Y: 27
Width: 200
Height: 15
Font: Tiny
Text: Video mode and display changes require restart
Checkbox@FRAME_LIMIT_CHECKBOX:
X: 310
Y: 243
@@ -293,6 +279,26 @@ Background@SETTINGS_PANEL:
Ticks: 20
MinimumValue: 50
MaximumValue: 240
Label@GL_PROFILE:
X: 15
Y: 270
Width: 120
Height: 25
Align: Right
Text: OpenGL Profile:
DropDownButton@GL_PROFILE_DROPDOWN:
X: 140
Y: 270
Width: 160
Height: 25
Font: Regular
Label@RESTART_REQUIRED_DESC:
Y: PARENT_BOTTOM - 40
Width: PARENT_RIGHT
Height: 15
Font: Tiny
Text: Display and OpenGL changes require restart
Align: Center
Container@AUDIO_PANEL:
X: 5
Y: 50
@@ -491,7 +497,12 @@ Background@SETTINGS_PANEL:
Height: 16
Font: Small
Text: - Zoom the battlefield using {MODIFIER + Scroll Wheel}
LabelWithHighlight@DESC_SCROLL:
LabelWithHighlight@DESC_SCROLL_RIGHT:
Y: 85
Height: 16
Font: Small
Text: - Pan the battlefield using the {Right} mouse button
LabelWithHighlight@DESC_SCROLL_MIDDLE:
Y: 85
Height: 16
Font: Small
@@ -544,13 +555,13 @@ Background@SETTINGS_PANEL:
Height: 20
Font: Regular
Text: Screen Edge Panning
Checkbox@CLASSIC_MOUSE_MIDDLE_SCROLL_CHECKBOX:
Checkbox@ALTERNATE_SCROLL_CHECKBOX:
X: 360
Y: 133
Width: 180
Height: 20
Font: Regular
Text: Middle Mouse Panning
Text: Alternate Mouse Panning
Label@SCROLL_SPEED_LABEL:
X: 305
Y: 210
@@ -648,7 +659,7 @@ Background@SETTINGS_PANEL:
Width: 80
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
Container@THREE_COLUMN:
Width: 173
Height: 25
@@ -664,7 +675,7 @@ Background@SETTINGS_PANEL:
Width: 80
Height: 25
Align: Left
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
Background@HOTKEY_DIALOG_ROOT:
X: 15
Y: 230
@@ -716,7 +727,7 @@ Background@SETTINGS_PANEL:
Text: Clear
Font: Bold
TooltipText: Unbind the hotkey
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Button@RESET_HOTKEY_BUTTON:
X: PARENT_RIGHT - WIDTH - 20
@@ -726,7 +737,7 @@ Background@SETTINGS_PANEL:
Text: Reset
Font: Bold
TooltipText: Reset to default
TooltipContainer: TOOLTIP_CONTAINER
TooltipContainer: SETTINGS_TOOLTIP_CONTAINER
TooltipTemplate: SIMPLE_TOOLTIP
Container@ADVANCED_PANEL:
X: 5
@@ -846,4 +857,4 @@ Background@SETTINGS_PANEL:
Height: 20
Font: Regular
Text: Enable Debug Commands in Replays
TooltipContainer@TOOLTIP_CONTAINER:
TooltipContainer@SETTINGS_TOOLTIP_CONTAINER:

View File

@@ -39,7 +39,7 @@ InitObjectives = function(player)
end)
end
SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pathFunction, unitTypes, customCondition, customHuntFunction)
SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pathFunction, unitTypes, customCondition, customHuntFunction, announcementFunction)
Trigger.AfterDelay(delay, function()
if customCondition and customCondition() then
return
@@ -50,6 +50,10 @@ SendCarryallReinforcements = function(player, currentWave, totalWaves, delay, pa
return
end
if announcementFunction then
announcementFunction(currentWave)
end
local path = pathFunction()
local units = Reinforcements.ReinforceWithTransport(player, "carryall.reinforce", unitTypes[currentWave], path, { path[1] })[2]
@@ -78,6 +82,10 @@ TriggerCarryallReinforcements = function(triggeringPlayer, reinforcingPlayer, ar
end)
end
DestroyCarryalls = function(player)
Utils.Do(player.GetActorsByType("carryall"), function(actor) actor.Kill() end)
end
-- Used for the AI:
IdlingUnits = { }

View File

@@ -48,7 +48,7 @@ sidebar-bits:
production-tooltip-time: 136, 51, 16, 16
production-tooltip-power: 102, 51, 16, 16
production-tooltip-cost: 68, 51, 16, 16
indicator-muted: 0, 119, 24, 24
indicator-muted: 0, 119, 26, 24
commandbar:
Inherits: ^Chrome

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

View File

@@ -0,0 +1,578 @@
MapFormat: 11
RequiresMod: d2k
Title: Ordos 06a
Author: Westwood Studios
Tileset: ARRAKIS
MapSize: 84,84
Bounds: 2,2,80,80
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: Random
PlayerReference@Creeps:
Name: Creeps
NonCombatant: True
Faction: Random
Enemies: Atreides, Ordos, Harkonnen
PlayerReference@Ordos:
Name: Ordos
Playable: True
LockFaction: True
Faction: ordos
LockColor: True
Color: B3EAA5
Enemies: Atreides, Harkonnen, Creeps
PlayerReference@Atreides:
Name: Atreides
LockFaction: True
Faction: atreides
LockColor: True
Color: 9191FF
Allies: Harkonnen
Enemies: Ordos, Creeps
PlayerReference@Harkonnen:
Name: Harkonnen
LockFaction: True
Faction: harkonnen
LockColor: True
Color: FE0000
Allies: Atreides
Enemies: Ordos, Creeps
Actors:
Actor0: wall
Location: 65,2
Owner: Ordos
AHeavyFactory: heavy_factory
Location: 3,3
Owner: Atreides
AConyard: construction_yard
Location: 8,3
Owner: Atreides
APower1: wind_trap
Location: 11,3
Owner: Atreides
APower2: wind_trap
Location: 13,3
Owner: Atreides
ALightFactory: light_factory
Location: 16,3
Owner: Atreides
Actor6: wall
Location: 65,3
Owner: Ordos
OOutpost: outpost
Location: 66,3
Owner: Ordos
OPower1: wind_trap
Location: 72,3
Owner: Ordos
OPower2: wind_trap
Location: 74,3
Owner: Ordos
OConyard: construction_yard
Location: 76,3
Owner: Ordos
OBarracks: barracks
Location: 79,3
Owner: Ordos
Actor12: wall
Location: 65,4
Owner: Ordos
ARock1: large_gun_turret
Location: 20,5
Facing: 160
Owner: Atreides
Actor14: wall
Location: 65,5
Owner: Ordos
Actor15: combat_tank_a
Location: 13,6
Owner: Atreides
Actor16: wall
Location: 65,6
Owner: Ordos
APower3: wind_trap
Location: 7,7
Owner: Atreides
APower4: wind_trap
Location: 9,7
Owner: Atreides
APower5: wind_trap
Location: 11,7
Owner: Atreides
APower6: wind_trap
Location: 13,7
Owner: Atreides
APower7: wind_trap
Location: 15,7
Owner: Atreides
Actor22: wall
Location: 65,7
Owner: Ordos
Actor23: trooper
Location: 4,8
Owner: Atreides
Actor24: wall
Location: 65,8
Owner: Ordos
Actor25: wall
Location: 66,8
Owner: Ordos
Actor26: wall
Location: 67,8
Owner: Ordos
Actor27: wall
Location: 68,8
Owner: Ordos
Actor28: wall
Location: 69,8
Owner: Ordos
Actor29: wall
Location: 70,8
Owner: Ordos
ABarracks: barracks
Location: 2,9
Owner: Atreides
Actor31: wall
Location: 70,9
Owner: Ordos
Actor32: wall
Location: 79,9
Owner: Ordos
Actor33: wall
Location: 80,9
Owner: Ordos
Actor34: wall
Location: 81,9
Owner: Ordos
APower8: wind_trap
Location: 10,10
Owner: Atreides
Actor36: wall
Location: 70,10
Owner: Ordos
Actor37: wall
Location: 79,10
Owner: Ordos
AResearch: research_centre
Location: 5,11
Owner: Atreides
Actor39: wall
Location: 70,11
Owner: Ordos
Actor40: wall
Location: 79,11
Owner: Ordos
APower9: wind_trap
Location: 2,12
Owner: Atreides
Actor42: combat_tank_a
Location: 8,12
Owner: Atreides
ARepair: repair_pad
Location: 13,12
Owner: Atreides
ARock2: large_gun_turret
Location: 16,12
Facing: 160
Owner: Atreides
Actor45: wall
Location: 70,12
Owner: Ordos
OGunt1: medium_gun_turret
Location: 71,12
Owner: Ordos
OGunt2: medium_gun_turret
Location: 78,12
Owner: Ordos
Actor48: wall
Location: 79,12
Owner: Ordos
APower10: wind_trap
Location: 2,15
Owner: Atreides
APower11: wind_trap
Location: 4,15
Owner: Atreides
APower12: wind_trap
Location: 6,15
Owner: Atreides
Actor52: combat_tank_a
Location: 11,17
Owner: Atreides
Actor53: combat_tank_a
Location: 8,18
Owner: Atreides
Actor54: wall
Location: 10,19
Owner: Atreides
Actor55: wall
Location: 11,19
Owner: Atreides
ARefinery: refinery
Location: 2,20
Owner: Atreides
Actor57: wall
Location: 10,20
Owner: Atreides
Actor58: combat_tank_a
Location: 6,21
Owner: Atreides
Actor59: wall
Location: 10,21
Owner: Atreides
Actor60: spicebloom.spawnpoint
Location: 38,21
Owner: Neutral
Actor61: wall
Location: 10,22
Owner: Atreides
Actor62: carryall
Location: 4,23
Owner: Atreides
Actor63: harvester
Location: 5,23
Owner: Atreides
ARock3: large_gun_turret
Location: 10,23
Facing: 192
Owner: Atreides
Actor65: wall
Location: 11,23
Owner: Atreides
AGunt1: medium_gun_turret
Location: 12,23
Facing: 192
Owner: Atreides
Actor67: wormspawner
Location: 26,23
Owner: Creeps
Actor68: siege_tank
Location: 5,24
Owner: Atreides
Actor69: spicebloom.spawnpoint
Location: 67,24
Owner: Neutral
Actor70: wall
Location: 2,25
Owner: Atreides
Actor71: wall
Location: 3,25
Owner: Atreides
Actor72: wall
Location: 4,25
Owner: Atreides
Actor73: wall
Location: 5,25
Owner: Atreides
Actor74: wall
Location: 6,25
Owner: Atreides
ARock4: large_gun_turret
Location: 7,25
Owner: Atreides
Actor76: wall
Location: 7,26
Owner: Atreides
AGunt2: medium_gun_turret
Location: 7,27
Owner: Atreides
Actor78: spicebloom.spawnpoint
Location: 76,30
Owner: Neutral
Actor79: spicebloom.spawnpoint
Location: 32,33
Owner: Neutral
Actor80: spicebloom.spawnpoint
Location: 56,33
Owner: Neutral
Actor81: spicebloom.spawnpoint
Location: 7,37
Owner: Neutral
Actor82: spicebloom.spawnpoint
Location: 44,48
Owner: Neutral
Actor83: spicebloom.spawnpoint
Location: 77,48
Owner: Neutral
Actor84: trooper
Location: 78,50
Owner: Ordos
Actor85: trooper
Location: 76,53
Owner: Ordos
Actor86: trooper
Location: 77,53
Owner: Ordos
OStarport: starport
Location: 79,53
Owner: Ordos
Actor88: trooper
Location: 77,55
Owner: Ordos
Actor89: raider
Location: 78,56
Owner: Ordos
HGunt1: medium_gun_turret
Location: 30,57
Facing: 0
Owner: Harkonnen
HGunt2: medium_gun_turret
Location: 34,57
Facing: 0
Owner: Harkonnen
Actor92: wall
Location: 30,58
Owner: Harkonnen
Actor93: combat_tank_h
Location: 32,58
Owner: Harkonnen
Actor94: wall
Location: 34,58
Owner: Harkonnen
Actor95: wall
Location: 30,59
Owner: Harkonnen
Actor96: trooper
Location: 33,59
Owner: Harkonnen
Actor97: wall
Location: 34,59
Owner: Harkonnen
Actor98: wall
Location: 30,60
Owner: Harkonnen
Actor99: trooper
Location: 31,60
Owner: Harkonnen
Actor100: wall
Location: 34,60
Owner: Harkonnen
Actor101: wall
Location: 29,61
Owner: Harkonnen
Actor102: wall
Location: 30,61
Owner: Harkonnen
Actor103: wall
Location: 34,61
Owner: Harkonnen
Actor104: wall
Location: 35,61
Owner: Harkonnen
Actor105: wall
Location: 29,62
Owner: Harkonnen
Actor106: wall
Location: 35,62
Owner: Harkonnen
Actor107: wall
Location: 29,63
Owner: Harkonnen
Actor108: wall
Location: 35,63
Owner: Harkonnen
Actor109: wall
Location: 36,63
Owner: Harkonnen
Actor110: wall
Location: 37,63
Owner: Harkonnen
Actor111: harvester
Location: 31,65
Owner: Harkonnen
HSilo1: silo
Location: 35,65
Owner: Harkonnen
Actor113: carryall
Location: 32,66
Owner: Harkonnen
HSilo2: silo
Location: 35,66
Owner: Harkonnen
HRefinery: refinery
Location: 30,67
Owner: Harkonnen
HSilo3: silo
Location: 35,67
Owner: Harkonnen
Actor117: spicebloom.spawnpoint
Location: 22,68
Owner: Neutral
HOutpost: outpost
Location: 35,70
Owner: Harkonnen
HPower1: wind_trap
Location: 31,71
Owner: Harkonnen
HPower2: wind_trap
Location: 34,73
Owner: Harkonnen
HPower3: wind_trap
Location: 36,73
Owner: Harkonnen
HPower4: wind_trap
Location: 38,73
Owner: Harkonnen
Actor123: wall
Location: 24,74
Owner: Harkonnen
HPower5: wind_trap
Location: 26,74
Owner: Harkonnen
HPower6: wind_trap
Location: 28,74
Owner: Harkonnen
HPower7: wind_trap
Location: 30,74
Owner: Harkonnen
Actor127: wall
Location: 24,75
Owner: Harkonnen
Actor128: spicebloom.spawnpoint
Location: 58,75
Owner: Neutral
HGunt3: medium_gun_turret
Location: 19,76
Facing: 32
Owner: Harkonnen
Actor130: wall
Location: 20,76
Owner: Harkonnen
Actor131: wall
Location: 21,76
Owner: Harkonnen
Actor132: wall
Location: 22,76
Owner: Harkonnen
Actor133: wall
Location: 23,76
Owner: Harkonnen
Actor134: wall
Location: 24,76
Owner: Harkonnen
Actor135: wall
Location: 19,77
Owner: Harkonnen
HHeavyFactory: heavy_factory
Location: 33,77
Owner: Harkonnen
HRock: large_gun_turret
Location: 38,77
Facing: 192
Owner: Harkonnen
HPower8: wind_trap
Location: 23,78
Owner: Harkonnen
HConyard: construction_yard
Location: 25,78
Owner: Harkonnen
HPower9: wind_trap
Location: 28,78
Owner: Harkonnen
HPower10: wind_trap
Location: 30,78
Owner: Harkonnen
Actor142: combat_tank_h
Location: 20,79
Owner: Harkonnen
HBarracks: barracks
Location: 36,79
Owner: Harkonnen
HGunt4: medium_gun_turret
Location: 19,80
Facing: 64
Owner: Harkonnen
Actor145: wall
Location: 39,80
Owner: Harkonnen
Actor146: wall
Location: 19,81
Owner: Harkonnen
Actor147: wall
Location: 39,81
Owner: Harkonnen
AtreidesRally1: waypoint
Owner: Neutral
Location: 8,22
AtreidesEntry1: waypoint
Owner: Neutral
Location: 2,22
HarkonnenRally1: waypoint
Owner: Neutral
Location: 32,64
HarkonnenEntry1: waypoint
Owner: Neutral
Location: 32,81
AtreidesRally2: waypoint
Owner: Neutral
Location: 77,76
AtreidesEntry2: waypoint
Owner: Neutral
Location: 77,81
AtreidesRally3: waypoint
Owner: Neutral
Location: 36,34
AtreidesEntry3: waypoint
Owner: Neutral
Location: 36,2
AtreidesRally4: waypoint
Owner: Neutral
Location: 68,41
AtreidesEntry4: waypoint
Owner: Neutral
Location: 81,41
HarkonnenRally2: waypoint
Owner: Neutral
Location: 44,59
HarkonnenEntry2: waypoint
Owner: Neutral
Location: 44,81
HarkonnenRally3: waypoint
Owner: Neutral
Location: 77,77
HarkonnenEntry3: waypoint
Owner: Neutral
Location: 81,77
HarkonnenRally4: waypoint
Owner: Neutral
Location: 56,50
HarkonnenEntry4: waypoint
Owner: Neutral
Location: 56,81
HarkonnenRally5: waypoint
Owner: Neutral
Location: 54,41
HarkonnenEntry5: waypoint
Owner: Neutral
Location: 81,41
HarkonnenRally6: waypoint
Owner: Neutral
Location: 52,33
HarkonnenEntry6: waypoint
Owner: Neutral
Location: 81,33
HarkonnenRally7: waypoint
Owner: Neutral
Location: 34,26
HarkonnenEntry7: waypoint
Owner: Neutral
Location: 34,2
Rules: d2k|rules/campaign-rules.yaml, d2k|rules/campaign-tooltips.yaml, d2k|rules/campaign-palettes.yaml, rules.yaml

View File

@@ -0,0 +1,54 @@
--[[
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.
]]
AttackGroupSize =
{
easy = 6,
normal = 8,
hard = 10
}
ProductionDelays =
{
easy = { DateTime.Seconds(16), DateTime.Seconds(30) },
normal = { DateTime.Seconds(2), DateTime.Seconds(5) },
hard = { DateTime.Seconds(1), DateTime.Seconds(3) }
}
EnemyInfantryTypes = { "light_inf", "light_inf", "light_inf", "trooper", "trooper" }
EnemyVehicleTypes = { "trike", "trike", "quad" }
AtreidesTankTypes = { "combat_tank_a", "siege_tank", "missile_tank" }
HarkonnenTankTypes = { "combat_tank_h", "siege_tank" }
InitAIUnits = function(house)
LastHarvesterEaten[house] = true
IdlingUnits[house] = Reinforcements.Reinforce(house, InitialReinforcements[house.InternalName], InitialReinforcementsPaths[house.InternalName])
DefendAndRepairBase(house, Base[house.InternalName], 0.75, AttackGroupSize[Difficulty])
end
ActivateAI = function()
InitAIUnits(atreides)
InitAIUnits(harkonnen)
local delay = function() return Utils.RandomInteger(ProductionDelays[Difficulty][1], ProductionDelays[Difficulty][2] + 1) end
local infantryToBuild = function() return { Utils.Random(EnemyInfantryTypes) } end
local vehiclesToBuild = function() return { Utils.Random(EnemyVehicleTypes) } end
local tanksToBuildAtreides = function() return { Utils.Random(AtreidesTankTypes) } end
local tanksToBuildHarkonnen = function() return { Utils.Random(HarkonnenTankTypes) } end
local attackTresholdSize = AttackGroupSize[Difficulty] * 2.5
ProduceUnits(atreides, ABarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize)
ProduceUnits(atreides, ALightFactory, delay, vehiclesToBuild, AttackGroupSize[Difficulty], attackTresholdSize)
ProduceUnits(atreides, AHeavyFactory, delay, tanksToBuildAtreides, AttackGroupSize[Difficulty], attackTresholdSize)
ProduceUnits(harkonnen, HBarracks, delay, infantryToBuild, AttackGroupSize[Difficulty], attackTresholdSize)
ProduceUnits(harkonnen, HHeavyFactory, delay, tanksToBuildHarkonnen, AttackGroupSize[Difficulty], attackTresholdSize)
end

View File

@@ -0,0 +1,275 @@
--[[
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.
]]
Base =
{
Atreides = { AConyard, APower1, APower2, APower3, APower4, APower5, APower6, APower7, APower8, APower9, APower10, APower11, APower12, ABarracks, ARefinery, ALightFactory, AHeavyFactory, ARepair, AResearch, AGunt1, AGunt2, ARock1, ARock2, ARock3, ARock4 },
Harkonnen = { HConyard, HPower1, HPower2, HPower3, HPower4, HPower5, HPower6, HPower7, HPower8, HPower9, HPower10, HBarracks, HRefinery, HOutpost, HHeavyFactory, HGunt1, HGunt2, HGunt3, HGunt4, HRock, HSilo1, HSilo2, HSilo3 }
}
AtreidesReinforcements =
{
easy =
{
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper" },
{ "quad", "quad", "combat_tank_a" },
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" }
},
normal =
{
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad" },
{ "quad", "quad", "combat_tank_a", "combat_tank_a" },
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad", "quad" },
{ "combat_tank_a", "combat_tank_a", "combat_tank_a" }
},
hard =
{
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper", "trooper", "quad", "quad" },
{ "quad", "quad", "quad", "combat_tank_a", "combat_tank_a" },
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper", "quad", "quad", "quad", "quad" },
{ "combat_tank_a", "combat_tank_a", "combat_tank_a", "quad" },
{ "combat_tank_a", "combat_tank_a", "missile_tank", "siege_tank" }
}
}
HarkonnenReinforcements =
{
easy =
{
{ "quad", "trike", "trike" },
{ "light_inf", "light_inf", "light_inf", "trooper", "trooper" },
{ "combat_tank_h", "quad" },
{ "trooper", "trooper", "trooper", "trooper", "trooper" }
},
normal =
{
{ "combat_tank_h", "combat_tank_h", "trike", "trike" },
{ "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper" },
{ "combat_tank_h", "quad", "quad" },
{ "trooper", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper" },
{ "trike", "trike", "quad", "siege_tank" }
},
hard =
{
{ "combat_tank_h", "combat_tank_h", "trike", "trike", "trike" },
{ "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "light_inf", "trooper", "trooper" },
{ "combat_tank_h", "combat_tank_h", "quad", "quad" },
{ "trooper", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper", "trooper" },
{ "trike", "trike", "quad", "quad", "siege_tank" },
{ "missile_tank", "missile_tank", "missile_tank", "missile_tank" }
}
}
IxianReinforcements =
{
easy = { "deviator", "deviator", "missile_tank", "missile_tank", "missile_tank", "siege_tank", "siege_tank", "combat_tank_o", "combat_tank_o" },
normal = { "deviator", "deviator", "missile_tank", "missile_tank", "missile_tank", "siege_tank", "siege_tank", "combat_tank_o" },
hard = { "deviator", "deviator", "missile_tank", "missile_tank", "siege_tank", "siege_tank", "combat_tank_o" }
}
EnemyAttackDelay =
{
easy = DateTime.Minutes(5) + DateTime.Seconds(15),
normal = DateTime.Minutes(3) + DateTime.Seconds(15),
hard = DateTime.Minutes(1) + DateTime.Seconds(30)
}
AtreidesPaths =
{
{ AtreidesEntry2.Location, AtreidesRally2.Location },
{ AtreidesEntry3.Location, AtreidesRally3.Location },
{ AtreidesEntry4.Location, AtreidesRally4.Location }
}
HarkonnenPaths =
{
{ HarkonnenEntry2.Location, HarkonnenRally2.Location },
{ HarkonnenEntry3.Location, HarkonnenRally3.Location },
{ HarkonnenEntry4.Location, HarkonnenRally4.Location },
{ HarkonnenEntry5.Location, HarkonnenRally5.Location },
{ HarkonnenEntry6.Location, HarkonnenRally6.Location },
{ HarkonnenEntry7.Location, HarkonnenRally7.Location }
}
AtreidesAttackWaves =
{
easy = 3,
normal = 4,
hard = 5
}
HarkonnenAttackWaves =
{
easy = 4,
normal = 5,
hard = 6
}
InitialReinforcements =
{
Atreides = { "combat_tank_a", "quad", "quad", "trike", "trike" },
Harkonnen = { "trooper", "trooper", "trooper", "trooper", "trooper", "combat_tank_h" }
}
InitialReinforcementsPaths =
{
Atreides = { AtreidesEntry1.Location, AtreidesRally1.Location },
Harkonnen = { HarkonnenEntry1.Location, HarkonnenRally1.Location }
}
InitialContrabandTimes =
{
easy = DateTime.Minutes(10),
normal = DateTime.Minutes(15),
hard = DateTime.Minutes(20)
}
ContrabandTimes =
{
easy = DateTime.Minutes(4),
normal = DateTime.Minutes(6),
hard = DateTime.Minutes(7)
}
SendContraband = function()
Media.PlaySpeechNotification(player, "Reinforce")
for i = 0, 6 do
local c = player.Color
if i % 2 == 0 then
c = HSLColor.White
end
Trigger.AfterDelay(DateTime.Seconds(i), function() UserInterface.SetMissionText("Ixian reinforcements have arrived!", c) end)
end
Trigger.AfterDelay(DateTime.Seconds(6), function()
TimerTicks = ContrabandTimes[Difficulty]
end)
local entryPath = { CPos.New(82, OStarport.Location.Y + 1), OStarport.Location + CVec.New(1, 1) }
local exitPath = { CPos.New(2, OStarport.Location.Y + 1) }
Reinforcements.ReinforceWithTransport(player, "frigate", IxianReinforcements[Difficulty], entryPath, exitPath)
end
Hunt = function(house)
Trigger.OnAllKilledOrCaptured(Base[house.InternalName], function()
Utils.Do(house.GetGroundAttackers(), IdleHunt)
end)
end
CheckHarvester = function(house)
if DateTime.GameTime % DateTime.Seconds(10) == 0 and LastHarvesterEaten[house] then
local units = house.GetActorsByType("harvester")
if #units > 0 then
LastHarvesterEaten[house] = false
ProtectHarvester(units[1], house, AttackGroupSize[Difficulty])
end
end
end
Tick = function()
if not player.IsObjectiveCompleted(KillAtreides) and atreides.HasNoRequiredUnits() then
Media.DisplayMessage("The Atreides have been annihilated!", "Mentat")
player.MarkCompletedObjective(KillAtreides)
DestroyCarryalls(atreides)
if player.IsObjectiveCompleted(KillHarkonnen) then
player.MarkCompletedObjective(GuardStarport)
end
end
if not player.IsObjectiveCompleted(KillHarkonnen) and harkonnen.HasNoRequiredUnits() then
Media.DisplayMessage("The Harkonnen have been annihilated!", "Mentat")
player.MarkCompletedObjective(KillHarkonnen)
DestroyCarryalls(harkonnen)
if player.IsObjectiveCompleted(KillAtreides) then
player.MarkCompletedObjective(GuardStarport)
end
end
if TimerTicks and TimerTicks > 0 then
TimerTicks = TimerTicks - 1
if TimerTicks == 0 then
if not FirstIxiansArrived then
Media.DisplayMessage("Deliveries beginning to arrive. Massive reinforcements expected!", "Mentat")
end
FirstIxiansArrived = true
SendContraband()
else
local text = "Initial"
if FirstIxiansArrived then
text = "Additional"
end
UserInterface.SetMissionText(text .. " reinforcements will arrive in " .. Utils.FormatTime(TimerTicks), player.Color)
end
end
CheckHarvester(atreides)
CheckHarvester(harkonnen)
end
WorldLoaded = function()
atreides = Player.GetPlayer("Atreides")
harkonnen = Player.GetPlayer("Harkonnen")
player = Player.GetPlayer("Ordos")
InitObjectives(player)
GuardStarport = player.AddObjective("Defend the Starport.")
KillAtreides = player.AddObjective("Destroy the Atreides.")
KillHarkonnen = player.AddObjective("Destroy the Harkonnen.")
Camera.Position = OConyard.CenterPosition
EnemyAttackLocations = { OConyard.Location, OStarport.Location }
Trigger.OnRemovedFromWorld(OStarport, function()
player.MarkFailedObjective(GuardStarport)
end)
Trigger.AfterDelay(DateTime.Seconds(2), function()
TimerTicks = InitialContrabandTimes[Difficulty]
Media.DisplayMessage("The first batch of Ixian reinforcements will arrive in " .. Utils.FormatTime(TimerTicks) .. ".", "Mentat")
end)
Hunt(atreides)
Hunt(harkonnen)
local atreidesPath = function() return Utils.Random(AtreidesPaths) end
local harkonnenPath = function() return Utils.Random(HarkonnenPaths) end
local atreidesCondition = function() return player.IsObjectiveCompleted(KillAtreides) end
local harkonnenCondition = function() return player.IsObjectiveCompleted(KillHarkonnen) end
local huntFunction = function(unit)
unit.AttackMove(Utils.Random(EnemyAttackLocations))
IdleHunt(unit)
end
local announcementFunction = function()
Media.DisplayMessage("Enemy reinforcements have arrived.", "Mentat")
end
SendCarryallReinforcements(atreides, 0, AtreidesAttackWaves[Difficulty], EnemyAttackDelay[Difficulty], atreidesPath, AtreidesReinforcements[Difficulty], atreidesCondition, huntFunction, announcementFunction)
Trigger.AfterDelay(Utils.RandomInteger(DateTime.Seconds(45), DateTime.Minutes(1) + DateTime.Seconds(15)), function()
SendCarryallReinforcements(harkonnen, 0, HarkonnenAttackWaves[Difficulty], EnemyAttackDelay[Difficulty], harkonnenPath, HarkonnenReinforcements[Difficulty], harkonnenCondition, huntFunction, announcementFunction)
end)
Actor.Create("upgrade.barracks", true, { Owner = atreides })
Actor.Create("upgrade.light", true, { Owner = atreides })
Actor.Create("upgrade.heavy", true, { Owner = atreides })
Actor.Create("upgrade.barracks", true, { Owner = harkonnen })
Actor.Create("upgrade.heavy", true, { Owner = harkonnen })
Trigger.AfterDelay(0, ActivateAI)
end

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