Compare commits
156 Commits
devtest-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e82c30fbab | ||
|
|
5583cbca83 | ||
|
|
4ec7edf3a3 | ||
|
|
7b0a0ded48 | ||
|
|
7e6dcb1f90 | ||
|
|
9cf3d2a647 | ||
|
|
9095211159 | ||
|
|
5370750d36 | ||
|
|
eaf12ffb04 | ||
|
|
9f4e1b04b4 | ||
|
|
53bb1449c0 | ||
|
|
04e56be8c7 | ||
|
|
017c89cb57 | ||
|
|
aaf55210e5 | ||
|
|
c66a01ad26 | ||
|
|
1b3d8dfc8d | ||
|
|
c60ee03a58 | ||
|
|
d71b116969 | ||
|
|
16e18c1cb2 | ||
|
|
00a553ca41 | ||
|
|
7d9179c5b3 | ||
|
|
f5e4c1d952 | ||
|
|
e31bcd3f02 | ||
|
|
a5b0b047ea | ||
|
|
844da265bd | ||
|
|
373bfdbd26 | ||
|
|
846df4f1b5 | ||
|
|
73caeb36a4 | ||
|
|
26c3ea50fa | ||
|
|
4e9cf96d6d | ||
|
|
db72971717 | ||
|
|
1852a8739b | ||
|
|
3812de6953 | ||
|
|
398406294a | ||
|
|
97c8dca131 | ||
|
|
91772847d2 | ||
|
|
1130ccf0bc | ||
|
|
6366ca8c4a | ||
|
|
324d2bae92 | ||
|
|
6606f517cf | ||
|
|
af9c2972b1 | ||
|
|
99bc783eb8 | ||
|
|
93e4083640 | ||
|
|
609dadc51b | ||
|
|
b2f2b81629 | ||
|
|
b867f3250d | ||
|
|
916599d313 | ||
|
|
8f983453c7 | ||
|
|
16fc540226 | ||
|
|
7a74a5059c | ||
|
|
3ec995258d | ||
|
|
2b4bd76a99 | ||
|
|
bc5c084250 | ||
|
|
75fe91b921 | ||
|
|
a47969b31b | ||
|
|
33b1e74296 | ||
|
|
059b8502c0 | ||
|
|
fdfc368db4 | ||
|
|
c42b5d9e07 | ||
|
|
5e24183108 | ||
|
|
a94856df0c | ||
|
|
1c36b38c1d | ||
|
|
6c8b3e17de | ||
|
|
adbc368b14 | ||
|
|
3deaf6c7bb | ||
|
|
8bbf43f45d | ||
|
|
5cd674445c | ||
|
|
27f1c37eca | ||
|
|
258259af4a | ||
|
|
276b1a9509 | ||
|
|
5b19dbe20c | ||
|
|
40424814a4 | ||
|
|
c0ea92850b | ||
|
|
dc1f11f412 | ||
|
|
e4602f8db2 | ||
|
|
b4b4412664 | ||
|
|
a23634897a | ||
|
|
082efe1b08 | ||
|
|
c6c05802d2 | ||
|
|
c091d7dc67 | ||
|
|
ac1d95c863 | ||
|
|
f15bb98f4a | ||
|
|
1353ad38a0 | ||
|
|
508f74822c | ||
|
|
4b5ae12536 | ||
|
|
4ad4e143b7 | ||
|
|
94aa310612 | ||
|
|
06d5826a0b | ||
|
|
a64f0eb0f5 | ||
|
|
814878126b | ||
|
|
3438dd3f74 | ||
|
|
e6b9bc07e9 | ||
|
|
cee511f59d | ||
|
|
992559d317 | ||
|
|
f27440d67e | ||
|
|
6b1aac4d98 | ||
|
|
96b95c8980 | ||
|
|
088288fee1 | ||
|
|
a93fbc3219 | ||
|
|
58273c532c | ||
|
|
4498f52723 | ||
|
|
28ede7ad8f | ||
|
|
fd663cc3f3 | ||
|
|
75d6495003 | ||
|
|
eb4ba7e793 | ||
|
|
4532cf9b55 | ||
|
|
2fba47151c | ||
|
|
d4f1ea54e7 | ||
|
|
4a6efc99ee | ||
|
|
c150ed373c | ||
|
|
d99b22db51 | ||
|
|
51ad7e6b59 | ||
|
|
493294c61e | ||
|
|
ea7d00df8d | ||
|
|
3218249a2d | ||
|
|
2be9200ce7 | ||
|
|
90ca7cedc2 | ||
|
|
4f884f9835 | ||
|
|
1f8aa67593 | ||
|
|
44af88bb49 | ||
|
|
522ee22845 | ||
|
|
9d98276c7d | ||
|
|
7ecc653183 | ||
|
|
de330daf85 | ||
|
|
e2a74f21b7 | ||
|
|
7bb594e11b | ||
|
|
4055952be3 | ||
|
|
77daea61bb | ||
|
|
fec1e244f3 | ||
|
|
1e26672b70 | ||
|
|
aead03d9b3 | ||
|
|
a9d44d4e44 | ||
|
|
7047ba3a31 | ||
|
|
e169368cf4 | ||
|
|
90f3788187 | ||
|
|
96b65aa969 | ||
|
|
0771a42749 | ||
|
|
8afa1d8dde | ||
|
|
79f6a51deb | ||
|
|
4bb4897ba3 | ||
|
|
f82aa58d2b | ||
|
|
30ee546fb3 | ||
|
|
2dad293328 | ||
|
|
14c4e2978f | ||
|
|
cb75a85341 | ||
|
|
dbbbb2c782 | ||
|
|
bf432df830 | ||
|
|
c42e2db542 | ||
|
|
bc55717b6b | ||
|
|
19551063a8 | ||
|
|
6d0f3a0008 | ||
|
|
88d11b4c66 | ||
|
|
9f7c3d6bac | ||
|
|
55ba6b9e26 | ||
|
|
79627d036e | ||
|
|
2438ee487a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,7 +26,6 @@ mods/*/*.pdb
|
||||
/*.exe
|
||||
/*.exe.config
|
||||
thirdparty/download/*
|
||||
*.mmdb.gz
|
||||
|
||||
# backup files by various editors
|
||||
*~
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
dist: xenial
|
||||
language: csharp
|
||||
mono: 5.20.1
|
||||
mono: 6.4.0
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
BIN
GeoLite2-Country.mmdb.gz
Normal file
BIN
GeoLite2-Country.mmdb.gz
Normal file
Binary file not shown.
16
Makefile
16
Makefile
@@ -151,7 +151,7 @@ test: core
|
||||
all: dependencies core
|
||||
|
||||
core:
|
||||
@command -v $(MSBUILD) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
|
||||
@command -v $(firstword $(MSBUILD)) >/dev/null || (echo "OpenRA requires the '$(MSBUILD)' tool provided by Mono >= 5.4."; exit 1)
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@$(MSBUILD) -t:build -p:Configuration="Release-x86"
|
||||
else
|
||||
@@ -173,30 +173,26 @@ cli-dependencies:
|
||||
@ $(CP_R) thirdparty/download/*.dll.config .
|
||||
@ test -f OpenRA.Game/obj/project.assets.json || $(MSBUILD) -t:restore
|
||||
|
||||
linux-dependencies: cli-dependencies geoip-dependencies linux-native-dependencies
|
||||
linux-dependencies: cli-dependencies linux-native-dependencies
|
||||
|
||||
linux-native-dependencies:
|
||||
@./thirdparty/configure-native-deps.sh
|
||||
|
||||
windows-dependencies: cli-dependencies geoip-dependencies
|
||||
windows-dependencies: cli-dependencies
|
||||
ifeq ($(WIN32), $(filter $(WIN32),true yes y on 1))
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x86
|
||||
else
|
||||
@./thirdparty/fetch-thirdparty-deps-windows.sh x64
|
||||
endif
|
||||
|
||||
osx-dependencies: cli-dependencies geoip-dependencies
|
||||
osx-dependencies: cli-dependencies
|
||||
@./thirdparty/fetch-thirdparty-deps-osx.sh
|
||||
@ $(CP_R) thirdparty/download/osx/*.dylib .
|
||||
@ $(CP_R) thirdparty/download/osx/*.dll.config .
|
||||
|
||||
geoip-dependencies:
|
||||
@./thirdparty/fetch-geoip-db.sh
|
||||
@ $(CP) thirdparty/download/GeoLite2-Country.mmdb.gz .
|
||||
|
||||
dependencies: $(os-dependencies)
|
||||
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies geoip-dependencies
|
||||
all-dependencies: cli-dependencies windows-dependencies osx-dependencies
|
||||
|
||||
version: VERSION mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/ts/mod.yaml mods/modcontent/mod.yaml mods/all/mod.yaml
|
||||
@echo "$(VERSION)" > VERSION
|
||||
@@ -387,4 +383,4 @@ help:
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies geoip-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
|
||||
.PHONY: check-scripts check nunit test all core clean distclean cli-dependencies linux-dependencies linux-native-dependencies windows-dependencies osx-dependencies dependencies all-dependencies version install install-linux-shortcuts install-engine install-common-mod-files install-default-mods install-core install-linux-icons install-linux-desktop install-linux-mime install-linux-appdata install-man-page install-linux-scripts uninstall help
|
||||
|
||||
@@ -49,7 +49,31 @@ namespace OpenRA.Activities
|
||||
public ActivityState State { get; private set; }
|
||||
|
||||
protected Activity ChildActivity { get; private set; }
|
||||
public Activity NextActivity { get; private set; }
|
||||
|
||||
Activity nextActivity;
|
||||
public Activity NextActivity
|
||||
{
|
||||
get
|
||||
{
|
||||
// If Activity.NextActivity.Cancel() was called, NextActivity will be in the Canceling
|
||||
// state rather than Queued (the activity system guarantees that it cannot be Active or Done).
|
||||
// An unknown number of ticks may have elapsed between the Activity.NextActivity.Cancel() call
|
||||
// and now, so we cannot make any assumptions on the value of Activity.NextActivity.NextActivity.
|
||||
// We must not return nextActivity (ticking it would be bogus), but returning null would potentially
|
||||
// drop valid activities queued after it. Walk the queue until we find a valid activity or
|
||||
// (more likely) run out of activities.
|
||||
var next = nextActivity;
|
||||
while (next != null && next.State == ActivityState.Canceling)
|
||||
next = next.NextActivity;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
nextActivity = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInterruptible { get; protected set; }
|
||||
public bool ChildHasPriority { get; protected set; }
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace OpenRA
|
||||
readonly INotifyIdle[] tickIdles;
|
||||
readonly ITargetablePositions[] targetablePositions;
|
||||
WPos[] staticTargetablePositions;
|
||||
bool created;
|
||||
|
||||
internal Actor(World world, string name, TypeDictionary initDict)
|
||||
{
|
||||
@@ -138,6 +139,35 @@ namespace OpenRA
|
||||
SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
|
||||
}
|
||||
|
||||
internal void Created()
|
||||
{
|
||||
created = true;
|
||||
|
||||
foreach (var t in TraitsImplementing<INotifyCreated>())
|
||||
t.Created(this);
|
||||
|
||||
// The initial activity should run before any activities queued by INotifyCreated.Created
|
||||
// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
|
||||
ICreationActivity creationActivity = null;
|
||||
foreach (var ica in TraitsImplementing<ICreationActivity>())
|
||||
{
|
||||
if (!ica.IsTraitEnabled())
|
||||
continue;
|
||||
|
||||
if (creationActivity != null)
|
||||
throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
|
||||
|
||||
var activity = ica.GetCreationActivity();
|
||||
if (activity == null)
|
||||
continue;
|
||||
|
||||
creationActivity = ica;
|
||||
|
||||
activity.Queue(CurrentActivity);
|
||||
CurrentActivity = activity;
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var wasIdle = IsIdle;
|
||||
@@ -214,11 +244,15 @@ namespace OpenRA
|
||||
{
|
||||
if (!queued)
|
||||
CancelActivity();
|
||||
|
||||
QueueActivity(nextActivity);
|
||||
}
|
||||
|
||||
public void QueueActivity(Activity nextActivity)
|
||||
{
|
||||
if (!created)
|
||||
throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
|
||||
|
||||
if (CurrentActivity == null)
|
||||
CurrentActivity = nextActivity;
|
||||
else
|
||||
|
||||
@@ -45,10 +45,15 @@ namespace OpenRA
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 256);
|
||||
|
||||
// If the player has defined a local support directory (in the game directory)
|
||||
// then this will override both the regular and system support dirs
|
||||
var sources = new[] { Platform.SystemSupportDir, Platform.SupportDir };
|
||||
foreach (var source in sources.Distinct())
|
||||
// Several types of support directory types are available, depending on
|
||||
// how the player has installed and launched the game.
|
||||
// Read registration metadata from all of them
|
||||
var sources = Enum.GetValues(typeof(SupportDirType))
|
||||
.Cast<SupportDirType>()
|
||||
.Select(t => Platform.GetSupportDir(t))
|
||||
.Distinct();
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var metadataPath = Path.Combine(source, "ModMetadata");
|
||||
if (!Directory.Exists(metadataPath))
|
||||
@@ -112,10 +117,18 @@ namespace OpenRA
|
||||
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
{
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
|
||||
// If using the modern support dir we must also write the registration
|
||||
// to the legacy support dir for older engine versions, but ONLY if it exists
|
||||
var legacyPath = Platform.GetSupportDir(SupportDirType.LegacyUser);
|
||||
if (Directory.Exists(legacyPath))
|
||||
sources.Add(legacyPath);
|
||||
}
|
||||
|
||||
// Make sure the mod is available for this session, even if saving it fails
|
||||
LoadMod(yaml.First().Value, forceRegistration: true);
|
||||
@@ -148,10 +161,16 @@ namespace OpenRA
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var activeModKey = ExternalMod.MakeKey(activeMod);
|
||||
foreach (var source in sources.Distinct())
|
||||
@@ -207,10 +226,16 @@ namespace OpenRA
|
||||
{
|
||||
var sources = new List<string>();
|
||||
if (registration.HasFlag(ModRegistration.System))
|
||||
sources.Add(Platform.SystemSupportDir);
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.System));
|
||||
|
||||
if (registration.HasFlag(ModRegistration.User))
|
||||
sources.Add(Platform.SupportDir);
|
||||
{
|
||||
// User support dir may be using the modern or legacy value, or overridden by the user
|
||||
// Add all the possibilities and let the .Distinct() below ignore the duplicates
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.User));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.ModernUser));
|
||||
sources.Add(Platform.GetSupportDir(SupportDirType.LegacyUser));
|
||||
}
|
||||
|
||||
var key = ExternalMod.MakeKey(mod);
|
||||
mods.Remove(key);
|
||||
|
||||
@@ -22,6 +22,7 @@ using System.Threading.Tasks;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Server;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
@@ -41,6 +42,7 @@ namespace OpenRA
|
||||
public static ICursor Cursor;
|
||||
public static bool HideCursor;
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
|
||||
internal static OrderManager OrderManager;
|
||||
static Server.Server server;
|
||||
@@ -351,6 +353,8 @@ namespace OpenRA
|
||||
foreach (var mod in Mods)
|
||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Metadata.Title, mod.Value.Metadata.Version);
|
||||
|
||||
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
|
||||
|
||||
ExternalMods = new ExternalMods();
|
||||
|
||||
Manifest currentMod;
|
||||
@@ -509,8 +513,15 @@ namespace OpenRA
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = mod.LaunchPath;
|
||||
var args = launchArguments != null ? mod.LaunchArgs.Append(launchArguments) : mod.LaunchArgs;
|
||||
var p = Process.Start(mod.LaunchPath, args.Select(a => "\"" + a + "\"").JoinWith(" "));
|
||||
if (modLaunchWrapper != null)
|
||||
{
|
||||
path = modLaunchWrapper;
|
||||
args = new[] { mod.LaunchPath }.Concat(args);
|
||||
}
|
||||
|
||||
var p = Process.Start(path, args.Select(a => "\"" + a + "\"").JoinWith(" "));
|
||||
if (p == null || p.HasExited)
|
||||
onFailed();
|
||||
else
|
||||
@@ -886,7 +897,7 @@ namespace OpenRA
|
||||
|
||||
public static void CreateServer(ServerSettings settings)
|
||||
{
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, false);
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, ModData, ServerType.Multiplayer);
|
||||
}
|
||||
|
||||
public static int CreateLocalServer(string map)
|
||||
@@ -898,7 +909,7 @@ namespace OpenRA
|
||||
AdvertiseOnline = false
|
||||
};
|
||||
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, false);
|
||||
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0), settings, ModData, ServerType.Local);
|
||||
|
||||
return server.Port;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
@@ -38,19 +37,46 @@ namespace OpenRA.Graphics
|
||||
hardwarePalette.AddPalette(p.Key, p.Value, false);
|
||||
|
||||
hardwarePalette.Initialize();
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = cursorProvider.Palettes[kv.Value.Palette];
|
||||
var hc = kv.Value.Frames
|
||||
.Select(f => CreateCursor(f, palette, kv.Key, kv.Value))
|
||||
.ToArray();
|
||||
|
||||
hardwareCursors.Add(kv.Key, hc);
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
var sequenceBounds = Rectangle.FromLTRB(0, 0, 1, 1);
|
||||
var frameHotspots = new int2[frames.Length];
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
// Hotspot relative to the center of the frame
|
||||
frameHotspots[i] = kv.Value.Hotspot - frames[i].Offset.ToInt2() + new int2(frames[i].Size) / 2;
|
||||
|
||||
var s = kv.Value.Frames.Select(a => sheetBuilder.Add(a)).ToArray();
|
||||
sprites.Add(kv.Key, s);
|
||||
// Bounds relative to the hotspot
|
||||
sequenceBounds = Rectangle.Union(sequenceBounds, new Rectangle(-frameHotspots[i], frames[i].Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
var paddedSize = 8 * new int2((sequenceBounds.Width + 7) / 8, (sequenceBounds.Height + 7) / 8);
|
||||
|
||||
var cursors = new IHardwareCursor[frames.Length];
|
||||
var frameSprites = new Sprite[frames.Length];
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
// Software rendering is used when the cursor is locked
|
||||
frameSprites[i] = sheetBuilder.Add(frames[i].Data, frames[i].Size, 0, frames[i].Offset);
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(sequenceBounds.Location + frameHotspots[i]);
|
||||
var paddingBR = paddedSize - new int2(frames[i].Size) - paddingTL;
|
||||
cursors[i] = CreateCursor(kv.Key, frames[i], palette, paddingTL, paddingBR, -sequenceBounds.Location);
|
||||
}
|
||||
|
||||
hardwareCursors.Add(kv.Key, cursors);
|
||||
sprites.Add(kv.Key, frameSprites);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
@@ -64,48 +90,24 @@ namespace OpenRA.Graphics
|
||||
return new PaletteReference(name, hardwarePalette.GetPaletteIndex(name), pal, hardwarePalette);
|
||||
}
|
||||
|
||||
IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence)
|
||||
IHardwareCursor CreateCursor(string name, ISpriteFrame frame, ImmutablePalette palette, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2;
|
||||
|
||||
// Expand the frame if required to include the hotspot
|
||||
var frameWidth = f.Size.Width;
|
||||
var dataWidth = f.Size.Width;
|
||||
var dataX = 0;
|
||||
if (hotspot.X < 0)
|
||||
// Pad the cursor and convert to RBGA
|
||||
var newWidth = paddingTL.X + frame.Size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + frame.Size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
for (var j = 0; j < frame.Size.Height; j++)
|
||||
{
|
||||
dataX = -hotspot.X;
|
||||
dataWidth += dataX;
|
||||
hotspot = hotspot.WithX(0);
|
||||
}
|
||||
else if (hotspot.X >= frameWidth)
|
||||
dataWidth = hotspot.X + 1;
|
||||
|
||||
var frameHeight = f.Size.Height;
|
||||
var dataHeight = f.Size.Height;
|
||||
var dataY = 0;
|
||||
if (hotspot.Y < 0)
|
||||
{
|
||||
dataY = -hotspot.Y;
|
||||
dataHeight += dataY;
|
||||
hotspot = hotspot.WithY(0);
|
||||
}
|
||||
else if (hotspot.Y >= frameHeight)
|
||||
dataHeight = hotspot.Y + 1;
|
||||
|
||||
var data = new byte[4 * dataWidth * dataHeight];
|
||||
for (var j = 0; j < frameHeight; j++)
|
||||
{
|
||||
for (var i = 0; i < frameWidth; i++)
|
||||
for (var i = 0; i < frame.Size.Width; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]);
|
||||
var start = 4 * ((j + dataY) * dataWidth + dataX + i);
|
||||
var bytes = BitConverter.GetBytes(palette[frame.Data[j * frame.Size.Width + i]]);
|
||||
var o = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
for (var k = 0; k < 4; k++)
|
||||
data[start + k] = bytes[k];
|
||||
rgbaData[o + k] = bytes[k];
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, hotspot);
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot);
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace OpenRA
|
||||
public readonly Hotkey Default = Hotkey.Invalid;
|
||||
public readonly string Description = "";
|
||||
public readonly HashSet<string> Types = new HashSet<string>();
|
||||
public bool HasDuplicates { get; internal set; }
|
||||
|
||||
public HotkeyDefinition(string name, MiniYaml node)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace OpenRA
|
||||
if (definitions.ContainsKey(kv.Key))
|
||||
keys[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
foreach (var hd in definitions)
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
|
||||
}
|
||||
|
||||
internal Func<Hotkey> GetHotkeyReference(string name)
|
||||
@@ -65,6 +68,20 @@ namespace OpenRA
|
||||
settings[name] = value;
|
||||
else
|
||||
settings.Remove(name);
|
||||
|
||||
var hadDuplicates = definition.HasDuplicates;
|
||||
definition.HasDuplicates = GetFirstDuplicate(definition.Name, this[definition.Name].GetValue(), definition) != null;
|
||||
|
||||
if (hadDuplicates || definition.HasDuplicates)
|
||||
{
|
||||
foreach (var hd in definitions)
|
||||
{
|
||||
if (hd.Value == definition)
|
||||
continue;
|
||||
|
||||
hd.Value.HasDuplicates = GetFirstDuplicate(hd.Value.Name, this[hd.Value.Name].GetValue(), hd.Value) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)
|
||||
|
||||
@@ -88,14 +88,12 @@ namespace OpenRA.Network
|
||||
break;
|
||||
}
|
||||
|
||||
if (orderManager.LocalClient == null)
|
||||
break;
|
||||
|
||||
var player = world.FindPlayerByClient(client);
|
||||
var localClientIsObserver = orderManager.LocalClient.IsObserver || (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||
var localClientIsObserver = world.IsReplay || (orderManager.LocalClient != null && orderManager.LocalClient.IsObserver)
|
||||
|| (world.LocalPlayer != null && world.LocalPlayer.WinState != WinState.Undefined);
|
||||
|
||||
// ExtraData gives us the team number, uint.MaxValue means Spectators
|
||||
if (order.ExtraData == uint.MaxValue && (localClientIsObserver || world.IsReplay))
|
||||
if (order.ExtraData == uint.MaxValue && localClientIsObserver)
|
||||
{
|
||||
// Validate before adding the line
|
||||
if (client.IsObserver || (player != null && player.WinState != WinState.Undefined))
|
||||
@@ -105,8 +103,8 @@ namespace OpenRA.Network
|
||||
}
|
||||
|
||||
var valid = client.Team == order.ExtraData && player != null && player.WinState == WinState.Undefined;
|
||||
var isSameTeam = order.ExtraData == orderManager.LocalClient.Team && world.LocalPlayer != null
|
||||
&& world.LocalPlayer.WinState == WinState.Undefined;
|
||||
var isSameTeam = orderManager.LocalClient != null && order.ExtraData == orderManager.LocalClient.Team
|
||||
&& world.LocalPlayer != null && world.LocalPlayer.WinState == WinState.Undefined;
|
||||
|
||||
if (valid && (isSameTeam || world.IsReplay))
|
||||
Game.AddChatLine("[Team" + (world.IsReplay ? " " + order.ExtraData : "") + "] " + client.Name, client.Color, message);
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace OpenRA.Orders
|
||||
return world.Map.Contains(cell) ? Cursor : "generic-blocked";
|
||||
}
|
||||
|
||||
public override bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
|
||||
public override bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
|
||||
{
|
||||
// Custom order generators always override selection
|
||||
return true;
|
||||
|
||||
@@ -67,21 +67,27 @@ namespace OpenRA.Orders
|
||||
|
||||
public virtual string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
var useSelect = false;
|
||||
var target = TargetForInput(world, cell, worldPixel, mi);
|
||||
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
|
||||
|
||||
if (target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
|
||||
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any()))
|
||||
useSelect = true;
|
||||
bool useSelect;
|
||||
if (Game.Settings.Game.UseClassicMouseStyle && !InputOverridesSelection(world, worldPixel, mi))
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>();
|
||||
else
|
||||
{
|
||||
var ordersWithCursor = world.Selection.Actors
|
||||
.Select(a => OrderForUnit(a, target, actorsAt, cell, mi))
|
||||
.Where(o => o != null && o.Cursor != null);
|
||||
|
||||
var ordersWithCursor = world.Selection.Actors
|
||||
.Select(a => OrderForUnit(a, target, actorsAt, cell, mi))
|
||||
.Where(o => o != null && o.Cursor != null);
|
||||
var cursorOrder = ordersWithCursor.MaxByOrDefault(o => o.Order.OrderPriority);
|
||||
if (cursorOrder != null)
|
||||
return cursorOrder.Cursor;
|
||||
|
||||
var cursorOrder = ordersWithCursor.MaxByOrDefault(o => o.Order.OrderPriority);
|
||||
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<SelectableInfo>() &&
|
||||
(mi.Modifiers.HasModifier(Modifiers.Shift) || !world.Selection.Actors.Any());
|
||||
}
|
||||
|
||||
return cursorOrder != null ? cursorOrder.Cursor : (useSelect ? "select" : "default");
|
||||
return useSelect ? "select" : "default";
|
||||
}
|
||||
|
||||
public void Deactivate() { }
|
||||
@@ -89,7 +95,7 @@ namespace OpenRA.Orders
|
||||
bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; }
|
||||
|
||||
// Used for classic mouse orders, determines whether or not action at xy is move or select
|
||||
public virtual bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
|
||||
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
|
||||
{
|
||||
var actor = world.ScreenMap.ActorsAtMouse(xy)
|
||||
.Where(a => !a.Actor.IsDead)
|
||||
@@ -101,22 +107,19 @@ namespace OpenRA.Orders
|
||||
var target = Target.FromActor(actor);
|
||||
var cell = world.Map.CellContaining(target.CenterPosition);
|
||||
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
|
||||
var underCursor = world.Selection.Actors
|
||||
.Select(a => new ActorBoundsPair(a, a.MouseBounds(wr)))
|
||||
.WithHighestSelectionPriority(xy, mi.Modifiers);
|
||||
|
||||
var o = OrderForUnit(underCursor, target, actorsAt, cell, mi);
|
||||
if (o != null)
|
||||
var modifiers = TargetModifiers.None;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
|
||||
modifiers |= TargetModifiers.ForceAttack;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Shift))
|
||||
modifiers |= TargetModifiers.ForceQueue;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Alt))
|
||||
modifiers |= TargetModifiers.ForceMove;
|
||||
|
||||
foreach (var a in world.Selection.Actors)
|
||||
{
|
||||
var modifiers = TargetModifiers.None;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
|
||||
modifiers |= TargetModifiers.ForceAttack;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Shift))
|
||||
modifiers |= TargetModifiers.ForceQueue;
|
||||
if (mi.Modifiers.HasModifier(Modifiers.Alt))
|
||||
modifiers |= TargetModifiers.ForceMove;
|
||||
|
||||
if (o.Order.TargetOverridesSelection(modifiers))
|
||||
var o = OrderForUnit(a, target, actorsAt, cell, mi);
|
||||
if (o != null && o.Order.TargetOverridesSelection(a, target, actorsAt, cell, modifiers))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace OpenRA
|
||||
{
|
||||
public enum PlatformType { Unknown, Windows, OSX, Linux }
|
||||
|
||||
public enum SupportDirType { System, ModernUser, LegacyUser, User }
|
||||
|
||||
public static class Platform
|
||||
{
|
||||
public const string SupportDirPrefix = "^";
|
||||
@@ -26,6 +28,12 @@ namespace OpenRA
|
||||
|
||||
static Lazy<PlatformType> currentPlatform = Exts.Lazy(GetCurrentPlatform);
|
||||
|
||||
static bool supportDirInitialized;
|
||||
static string systemSupportPath;
|
||||
static string legacyUserSupportPath;
|
||||
static string modernUserSupportPath;
|
||||
static string userSupportPath;
|
||||
|
||||
static PlatformType GetCurrentPlatform()
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
@@ -66,18 +74,91 @@ namespace OpenRA
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing user-specific support files (settings, maps, replays, game data, etc).
|
||||
/// The directory will automatically be created if it does not exist when this is queried.
|
||||
/// </summary>
|
||||
public static string SupportDir { get { return supportDir.Value; } }
|
||||
static Lazy<string> supportDir = Exts.Lazy(GetSupportDir);
|
||||
static string supportDirOverride;
|
||||
public static string SupportDir { get { return GetSupportDir(SupportDirType.User); } }
|
||||
|
||||
public static string GetSupportDir(SupportDirType type)
|
||||
{
|
||||
if (!supportDirInitialized)
|
||||
InitializeSupportDir();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SupportDirType.System: return systemSupportPath;
|
||||
case SupportDirType.LegacyUser: return legacyUserSupportPath;
|
||||
case SupportDirType.ModernUser: return modernUserSupportPath;
|
||||
default: return userSupportPath;
|
||||
}
|
||||
}
|
||||
|
||||
static void InitializeSupportDir()
|
||||
{
|
||||
// The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions
|
||||
switch (CurrentPlatform)
|
||||
{
|
||||
case PlatformType.Windows:
|
||||
{
|
||||
modernUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA");
|
||||
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA");
|
||||
systemSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformType.OSX:
|
||||
{
|
||||
modernUserSupportPath = legacyUserSupportPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library", "Application Support", "OpenRA");
|
||||
|
||||
systemSupportPath = "/Library/Application Support/OpenRA/";
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformType.Linux:
|
||||
{
|
||||
legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
|
||||
|
||||
var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
|
||||
if (string.IsNullOrEmpty(xdgConfigHome))
|
||||
xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config");
|
||||
|
||||
modernUserSupportPath = Path.Combine(xdgConfigHome, "openra");
|
||||
systemSupportPath = "/var/games/openra/";
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
modernUserSupportPath = legacyUserSupportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
|
||||
systemSupportPath = "/var/games/openra/";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
userSupportPath = systemSupportPath = localSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
// Use the fallback directory if it exists and the preferred one does not
|
||||
else if (!Directory.Exists(modernUserSupportPath) && Directory.Exists(legacyUserSupportPath))
|
||||
userSupportPath = legacyUserSupportPath + Path.DirectorySeparatorChar;
|
||||
else
|
||||
userSupportPath = modernUserSupportPath + Path.DirectorySeparatorChar;
|
||||
|
||||
supportDirInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom support directory that already exists on the filesystem.
|
||||
/// MUST be called before Platform.SupportDir is first accessed.
|
||||
/// Cannot be called after Platform.SupportDir / GetSupportDir have been accessed.
|
||||
/// </summary>
|
||||
public static void OverrideSupportDir(string path)
|
||||
{
|
||||
if (supportDirInitialized)
|
||||
throw new InvalidOperationException("Attempted to override user support directory after it has already been accessed.");
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
throw new DirectoryNotFoundException(path);
|
||||
|
||||
@@ -85,92 +166,8 @@ namespace OpenRA
|
||||
!path.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
path += Path.DirectorySeparatorChar;
|
||||
|
||||
supportDirOverride = path;
|
||||
}
|
||||
|
||||
static string GetSupportDir()
|
||||
{
|
||||
// Use the custom override if it has been defined
|
||||
if (supportDirOverride != null)
|
||||
return supportDirOverride;
|
||||
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
return localSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
// The preferred support dir location for Windows and Linux was changed in mid 2019 to match modern platform conventions
|
||||
string preferredSupportDir;
|
||||
string fallbackSupportDir;
|
||||
switch (CurrentPlatform)
|
||||
{
|
||||
case PlatformType.Windows:
|
||||
{
|
||||
preferredSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenRA");
|
||||
fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "OpenRA");
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformType.OSX:
|
||||
{
|
||||
preferredSupportDir = fallbackSupportDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library", "Application Support", "OpenRA");
|
||||
break;
|
||||
}
|
||||
|
||||
case PlatformType.Linux:
|
||||
{
|
||||
fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
|
||||
|
||||
var xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
|
||||
if (string.IsNullOrEmpty(xdgConfigHome))
|
||||
xdgConfigHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".config");
|
||||
|
||||
preferredSupportDir = Path.Combine(xdgConfigHome, "openra");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
preferredSupportDir = fallbackSupportDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".openra");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the fallback directory if it exists and the preferred one does not
|
||||
if (!Directory.Exists(preferredSupportDir) && Directory.Exists(fallbackSupportDir))
|
||||
return fallbackSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
return preferredSupportDir + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing system-wide support files (mod metadata).
|
||||
/// This directory is not guaranteed to exist or be writable.
|
||||
/// Consumers are expected to check the validity of the returned value, and
|
||||
/// fall back to the user support directory if necessary.
|
||||
/// </summary>
|
||||
public static string SystemSupportDir { get { return systemSupportDir.Value; } }
|
||||
static Lazy<string> systemSupportDir = Exts.Lazy(GetSystemSupportDir);
|
||||
|
||||
static string GetSystemSupportDir()
|
||||
{
|
||||
// Use a local directory in the game root if it exists (shared with the system support dir)
|
||||
var localSupportDir = Path.Combine(GameDir, "Support");
|
||||
if (Directory.Exists(localSupportDir))
|
||||
return localSupportDir + Path.DirectorySeparatorChar;
|
||||
|
||||
switch (CurrentPlatform)
|
||||
{
|
||||
case PlatformType.Windows:
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "OpenRA") + Path.DirectorySeparatorChar;
|
||||
case PlatformType.OSX:
|
||||
return "/Library/Application Support/OpenRA/";
|
||||
default:
|
||||
return "/var/games/openra/";
|
||||
}
|
||||
InitializeSupportDir();
|
||||
userSupportPath = path;
|
||||
}
|
||||
|
||||
public static string GameDir
|
||||
|
||||
@@ -32,6 +32,13 @@ namespace OpenRA.Server
|
||||
ShuttingDown = 3
|
||||
}
|
||||
|
||||
public enum ServerType
|
||||
{
|
||||
Local = 0,
|
||||
Multiplayer = 1,
|
||||
Dedicated = 2
|
||||
}
|
||||
|
||||
public class Server
|
||||
{
|
||||
public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match.";
|
||||
@@ -39,7 +46,7 @@ namespace OpenRA.Server
|
||||
public readonly IPAddress Ip;
|
||||
public readonly int Port;
|
||||
public readonly MersenneTwister Random = new MersenneTwister();
|
||||
public readonly bool Dedicated;
|
||||
public readonly ServerType Type;
|
||||
|
||||
// Valid player connections
|
||||
public List<Connection> Conns = new List<Connection>();
|
||||
@@ -122,7 +129,7 @@ namespace OpenRA.Server
|
||||
t.GameEnded(this);
|
||||
}
|
||||
|
||||
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, bool dedicated)
|
||||
public Server(IPEndPoint endpoint, ServerSettings settings, ModData modData, ServerType type)
|
||||
{
|
||||
Log.AddChannel("server", "server.log", true);
|
||||
|
||||
@@ -131,7 +138,7 @@ namespace OpenRA.Server
|
||||
var localEndpoint = (IPEndPoint)listener.LocalEndpoint;
|
||||
Ip = localEndpoint.Address;
|
||||
Port = localEndpoint.Port;
|
||||
Dedicated = dedicated;
|
||||
Type = type;
|
||||
Settings = settings;
|
||||
|
||||
Settings.Name = OpenRA.Settings.SanitizedServerName(Settings.Name);
|
||||
@@ -157,10 +164,10 @@ namespace OpenRA.Server
|
||||
RandomSeed = randomSeed,
|
||||
Map = settings.Map,
|
||||
ServerName = settings.Name,
|
||||
EnableSingleplayer = settings.EnableSingleplayer || !dedicated,
|
||||
EnableSingleplayer = settings.EnableSingleplayer || Type != ServerType.Dedicated,
|
||||
EnableSyncReports = settings.EnableSyncReports,
|
||||
GameUid = Guid.NewGuid().ToString(),
|
||||
Dedicated = dedicated
|
||||
Dedicated = Type == ServerType.Dedicated
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,7 +223,7 @@ namespace OpenRA.Server
|
||||
delayedActions.PerformActions(0);
|
||||
|
||||
// PERF: Dedicated servers need to drain the action queue to remove references blocking the GC from cleaning up disposed objects.
|
||||
if (dedicated)
|
||||
if (Type == ServerType.Dedicated)
|
||||
Game.PerformDelayedActions();
|
||||
|
||||
foreach (var t in serverTraits.WithInterface<ITick>())
|
||||
@@ -434,7 +441,7 @@ namespace OpenRA.Server
|
||||
// Send initial ping
|
||||
SendOrderTo(newConn, "Ping", Game.RunTime.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (Dedicated)
|
||||
if (Type == ServerType.Dedicated)
|
||||
{
|
||||
var motdFile = Platform.ResolvePath(Platform.SupportDirPrefix, "motd.txt");
|
||||
if (!File.Exists(motdFile))
|
||||
@@ -454,7 +461,13 @@ namespace OpenRA.Server
|
||||
SendOrderTo(newConn, "Message", "Bots have been disabled on this map.");
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature))
|
||||
if (Type == ServerType.Local)
|
||||
{
|
||||
// Local servers can only be joined by the local client, so we can trust their identity without validation
|
||||
client.Fingerprint = handshake.Fingerprint;
|
||||
completeConnection();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature))
|
||||
{
|
||||
waitingForAuthenticationCallback++;
|
||||
|
||||
@@ -502,9 +515,9 @@ namespace OpenRA.Server
|
||||
|
||||
delayedActions.Add(() =>
|
||||
{
|
||||
var notAuthenticated = Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any());
|
||||
var blacklisted = Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID);
|
||||
var notWhitelisted = Dedicated && Settings.ProfileIDWhitelist.Any() &&
|
||||
var notAuthenticated = Type == ServerType.Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any());
|
||||
var blacklisted = Type == ServerType.Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID);
|
||||
var notWhitelisted = Type == ServerType.Dedicated && Settings.ProfileIDWhitelist.Any() &&
|
||||
(profile == null || !Settings.ProfileIDWhitelist.Contains(profile.ProfileID));
|
||||
|
||||
if (notAuthenticated)
|
||||
@@ -534,7 +547,7 @@ namespace OpenRA.Server
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()))
|
||||
if (Type == ServerType.Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()))
|
||||
{
|
||||
Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint);
|
||||
SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account");
|
||||
@@ -616,7 +629,7 @@ namespace OpenRA.Server
|
||||
{
|
||||
DispatchOrdersToClients(conn, 0, Order.FromTargetString("Message", text, true).Serialize());
|
||||
|
||||
if (Dedicated)
|
||||
if (Type == ServerType.Dedicated)
|
||||
Console.WriteLine("[{0}] {1}".F(DateTime.Now.ToString(Settings.TimestampFormat), text));
|
||||
}
|
||||
|
||||
@@ -731,7 +744,7 @@ namespace OpenRA.Server
|
||||
|
||||
case "LoadGameSave":
|
||||
{
|
||||
if (Dedicated || State >= ServerState.GameStarted)
|
||||
if (Type == ServerType.Dedicated || State >= ServerState.GameStarted)
|
||||
break;
|
||||
|
||||
// Sanitize potentially malicious input
|
||||
@@ -834,7 +847,7 @@ namespace OpenRA.Server
|
||||
|
||||
// Client was the server admin
|
||||
// TODO: Reassign admin for game in progress via an order
|
||||
if (Dedicated && dropClient.IsAdmin && State == ServerState.WaitingPlayers)
|
||||
if (Type == ServerType.Dedicated && dropClient.IsAdmin && State == ServerState.WaitingPlayers)
|
||||
{
|
||||
// Remove any bots controlled by the admin
|
||||
LobbyInfo.Clients.RemoveAll(c => c.Bot != null && c.BotControllerClientIndex == toDrop.PlayerIndex);
|
||||
@@ -856,10 +869,10 @@ namespace OpenRA.Server
|
||||
foreach (var t in serverTraits.WithInterface<INotifyServerEmpty>())
|
||||
t.ServerEmpty(this);
|
||||
|
||||
if (Conns.Any() || Dedicated)
|
||||
if (Conns.Any() || Type == ServerType.Dedicated)
|
||||
SyncLobbyClients();
|
||||
|
||||
if (!Dedicated && dropClient.IsAdmin)
|
||||
if (Type != ServerType.Dedicated && dropClient.IsAdmin)
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
@@ -952,7 +965,7 @@ namespace OpenRA.Server
|
||||
|
||||
// Enable game saves for singleplayer missions only
|
||||
// TODO: Enable for multiplayer (non-dedicated servers only) once the lobby UI has been created
|
||||
LobbyInfo.GlobalSettings.GameSavesEnabled = !Dedicated && LobbyInfo.NonBotClients.Count() == 1;
|
||||
LobbyInfo.GlobalSettings.GameSavesEnabled = Type != ServerType.Dedicated && LobbyInfo.NonBotClients.Count() == 1;
|
||||
|
||||
SyncLobbyInfo();
|
||||
State = ServerState.GameStarted;
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace OpenRA
|
||||
public MouseScrollType MiddleMouseScroll = MouseScrollType.Standard;
|
||||
public MouseScrollType RightMouseScroll = MouseScrollType.Disabled;
|
||||
public MouseButtonPreference MouseButtonPreference = new MouseButtonPreference();
|
||||
public float ViewportEdgeScrollStep = 10f;
|
||||
public float ViewportEdgeScrollStep = 30f;
|
||||
public float UIScrollSpeed = 50f;
|
||||
public int SelectionDeadzone = 24;
|
||||
public int MouseScrollDeadzone = 8;
|
||||
|
||||
@@ -45,10 +45,12 @@ namespace OpenRA
|
||||
ISound video;
|
||||
MusicInfo currentMusic;
|
||||
Dictionary<uint, ISound> currentSounds = new Dictionary<uint, ISound>();
|
||||
public bool DummyEngine { get; private set; }
|
||||
|
||||
public Sound(IPlatform platform, SoundSettings soundSettings)
|
||||
{
|
||||
soundEngine = platform.CreateSound(soundSettings.Device);
|
||||
DummyEngine = soundEngine.Dummy;
|
||||
|
||||
if (soundSettings.Mute)
|
||||
MuteAudio();
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace OpenRA
|
||||
ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate);
|
||||
ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume);
|
||||
ISound Play2DStream(Stream stream, int channels, int sampleBits, int sampleRate, bool loop, bool relative, WPos pos, float volume);
|
||||
bool Dummy { get; }
|
||||
float Volume { get; set; }
|
||||
void PauseSound(ISound sound, bool paused);
|
||||
void StopSound(ISound sound);
|
||||
|
||||
@@ -186,20 +186,22 @@ namespace OpenRA.Traits
|
||||
Hash += 1;
|
||||
}
|
||||
|
||||
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist range, int maxHeightDelta = -1)
|
||||
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist minRange, WDist maxRange, int maxHeightDelta = -1)
|
||||
{
|
||||
// Account for potential extra half-cell from odd-height terrain
|
||||
var r = (range.Length + 1023 + 512) / 1024;
|
||||
var limit = range.LengthSquared;
|
||||
var r = (maxRange.Length + 1023 + 512) / 1024;
|
||||
var minLimit = minRange.LengthSquared;
|
||||
var maxLimit = maxRange.LengthSquared;
|
||||
|
||||
// Project actor position into the shroud plane
|
||||
var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
|
||||
var projectedCell = map.CellContaining(projectedPos);
|
||||
var projectedHeight = pos.Z / 512;
|
||||
|
||||
foreach (var c in map.FindTilesInCircle(projectedCell, r, true))
|
||||
foreach (var c in map.FindTilesInAnnulus(projectedCell, minRange.Length / 1024, r, true))
|
||||
{
|
||||
if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit)
|
||||
var dist = (map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared;
|
||||
if (dist <= maxLimit && (dist == 0 || dist > minLimit))
|
||||
{
|
||||
var puv = (PPos)c.ToMPos(map);
|
||||
if (maxHeightDelta < 0 || map.ProjectedHeight(puv) < projectedHeight + maxHeightDelta)
|
||||
@@ -210,7 +212,7 @@ namespace OpenRA.Traits
|
||||
|
||||
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, CPos cell, WDist range, int maxHeightDelta = -1)
|
||||
{
|
||||
return ProjectedCellsInRange(map, map.CenterOfCell(cell), range, maxHeightDelta);
|
||||
return ProjectedCellsInRange(map, map.CenterOfCell(cell), WDist.Zero, range, maxHeightDelta);
|
||||
}
|
||||
|
||||
public void AddSource(object key, SourceType type, PPos[] projectedCells)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
@@ -172,7 +173,7 @@ namespace OpenRA.Traits
|
||||
int OrderPriority { get; }
|
||||
bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor);
|
||||
bool IsQueued { get; }
|
||||
bool TargetOverridesSelection(TargetModifiers modifiers);
|
||||
bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers);
|
||||
}
|
||||
|
||||
public interface IResolveOrder { void ResolveOrder(Actor self, Order order); }
|
||||
@@ -260,6 +261,7 @@ namespace OpenRA.Traits
|
||||
WDist LargestActorRadius { get; }
|
||||
WDist LargestBlockingActorRadius { get; }
|
||||
|
||||
void UpdateOccupiedCells(IOccupySpace ios);
|
||||
event Action<CPos> CellUpdated;
|
||||
}
|
||||
|
||||
@@ -312,7 +314,7 @@ namespace OpenRA.Traits
|
||||
|
||||
public interface IPositionableInfo : IOccupySpaceInfo
|
||||
{
|
||||
bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true);
|
||||
bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true);
|
||||
}
|
||||
|
||||
public interface IPositionable : IOccupySpace
|
||||
@@ -527,4 +529,7 @@ namespace OpenRA.Traits
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface IUnlocksRenderPlayer { bool RenderPlayerUnlocked { get; } }
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
public interface ICreationActivity { Activity GetCreationActivity(); }
|
||||
}
|
||||
|
||||
@@ -329,10 +329,10 @@ namespace OpenRA
|
||||
public Actor CreateActor(bool addToWorld, string name, TypeDictionary initDict)
|
||||
{
|
||||
var a = new Actor(this, name, initDict);
|
||||
foreach (var t in a.TraitsImplementing<INotifyCreated>())
|
||||
t.Created(a);
|
||||
a.Created();
|
||||
if (addToWorld)
|
||||
Add(a);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,8 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (newStance > oldStance || forceAttack)
|
||||
return;
|
||||
|
||||
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
|
||||
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
target = Target.Invalid;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
leapToken = conditionManager.RevokeCondition(self, leapToken);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
{
|
||||
return new LeapAttack(self, newTarget, allowMove, forceAttack, this, info, targetLineColor);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public AttackTDGunboatTurreted(Actor self, AttackTDGunboatTurretedInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
{
|
||||
return new AttackTDGunboatTurretedActivity(self, newTarget, allowMove, forceAttack, targetLineColor);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
{
|
||||
return new ChargeAttack(this, newTarget, forceAttack, targetLineColor);
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
public string OrderID { get { return "BeginMinefield"; } }
|
||||
public int OrderPriority { get { return 5; } }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public string OrderID { get { return "PortableChronoTeleport"; } }
|
||||
public int OrderPriority { get { return 5; } }
|
||||
public bool IsQueued { get; protected set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
base.Activate(self, order, manager);
|
||||
|
||||
attack.AttackTarget(order.Target, false, false, true);
|
||||
attack.AttackTarget(order.Target, AttackSource.Default, false, false, true);
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
|
||||
@@ -313,8 +313,13 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
bool IsValidTarget(CPos xy)
|
||||
{
|
||||
// Don't teleport if there are no units in range (either all moved out of range, or none yet moved into range)
|
||||
var unitsInRange = power.UnitsInRange(sourceLocation);
|
||||
if (!unitsInRange.Any())
|
||||
return false;
|
||||
|
||||
var canTeleport = false;
|
||||
foreach (var unit in power.UnitsInRange(sourceLocation))
|
||||
foreach (var unit in unitsInRange)
|
||||
{
|
||||
var targetCell = unit.Location + (xy - sourceLocation);
|
||||
if (manager.Self.Owner.Shroud.IsExplored(targetCell) && unit.Trait<Chronoshiftable>().CanChronoshiftTo(unit, targetCell))
|
||||
|
||||
@@ -47,12 +47,9 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
bool IOccupySpaceInfo.SharesCell { get { return false; } }
|
||||
|
||||
// Used to determine if actor can spawn
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = false)
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = false)
|
||||
{
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return world.Map.Contains(cell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +188,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
|
||||
public Activity MoveIntoWorld(Actor self, int delay = 0) { return null; }
|
||||
public Activity ReturnToCell(Actor self) { return null; }
|
||||
public Activity MoveToTarget(Actor self, Target target,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
|
||||
public Activity MoveIntoTarget(Actor self, Target target) { return null; }
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (aircraft.Info.CanHover && !skipHeightAdjustment && dat != aircraft.Info.CruiseAltitude)
|
||||
{
|
||||
if (dat <= aircraft.LandAltitude)
|
||||
QueueChild(new TakeOff(self, target));
|
||||
QueueChild(new TakeOff(self));
|
||||
else
|
||||
VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
else if (dat <= aircraft.LandAltitude)
|
||||
{
|
||||
QueueChild(new TakeOff(self, target));
|
||||
QueueChild(new TakeOff(self));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
var pos = aircraft.GetPosition();
|
||||
var delta = checkTarget.CenterPosition - pos;
|
||||
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
|
||||
|
||||
// Inside the target annulus, so we're done
|
||||
var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(pos, maxRange);
|
||||
@@ -167,12 +166,20 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return true;
|
||||
|
||||
var isSlider = aircraft.Info.CanSlide;
|
||||
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
|
||||
var move = isSlider ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing);
|
||||
|
||||
// Inside the minimum range, so reverse if we CanSlide
|
||||
if (isSlider && insideMinRange)
|
||||
// Inside the minimum range, so reverse if we CanSlide, otherwise face away from the target.
|
||||
if (insideMinRange)
|
||||
{
|
||||
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
|
||||
if (isSlider)
|
||||
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, -move);
|
||||
else
|
||||
{
|
||||
desiredFacing = Util.NormalizeFacing(desiredFacing + 128);
|
||||
FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, move);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly Aircraft aircraft;
|
||||
readonly AttackAircraft attackAircraft;
|
||||
readonly Rearmable rearmable;
|
||||
readonly AttackSource source;
|
||||
readonly bool forceAttack;
|
||||
readonly Color? targetLineColor;
|
||||
readonly WDist strafeDistance;
|
||||
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
@@ -34,10 +36,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
bool useLastVisibleTarget;
|
||||
bool hasTicked;
|
||||
bool returnToBase;
|
||||
int remainingTicksUntilTurn;
|
||||
|
||||
public FlyAttack(Actor self, Target target, bool forceAttack, Color? targetLineColor)
|
||||
public FlyAttack(Actor self, AttackSource source, Target target, bool forceAttack, Color? targetLineColor)
|
||||
{
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.forceAttack = forceAttack;
|
||||
this.targetLineColor = targetLineColor;
|
||||
@@ -46,6 +48,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
attackAircraft = self.Trait<AttackAircraft>();
|
||||
rearmable = self.TraitOrDefault<Rearmable>();
|
||||
|
||||
strafeDistance = attackAircraft.Info.StrafeRunLength;
|
||||
|
||||
// The target may become hidden between the initial order request and the first tick (e.g. if queued)
|
||||
// Moving to any position (even if quite stale) is still better than immediately giving up
|
||||
if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner))
|
||||
@@ -99,6 +103,17 @@ namespace OpenRA.Mods.Common.Activities
|
||||
lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes();
|
||||
}
|
||||
|
||||
// The target may become hidden in the same tick the FlyAttack constructor is called,
|
||||
// causing lastVisible* to remain uninitialized.
|
||||
// Fix the fallback values based on the frozen actor properties
|
||||
else if (target.Type == TargetType.FrozenActor && !lastVisibleTarget.IsValidFor(self))
|
||||
{
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
|
||||
lastVisibleOwner = target.FrozenActor.Owner;
|
||||
lastVisibleTargetTypes = target.FrozenActor.TargetTypes;
|
||||
}
|
||||
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
@@ -109,6 +124,14 @@ namespace OpenRA.Mods.Common.Activities
|
||||
// and resume the activity after reloading if AbortOnResupply is set to 'false'
|
||||
if (rearmable != null && !useLastVisibleTarget && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
|
||||
{
|
||||
// Attack moves never resupply
|
||||
if (source == AttackSource.AttackMove)
|
||||
return true;
|
||||
|
||||
// AbortOnResupply cancels the current activity (after resupplying) plus any queued activities
|
||||
if (attackAircraft.Info.AbortOnResupply && NextActivity != null)
|
||||
NextActivity.Cancel(self);
|
||||
|
||||
QueueChild(new ReturnToBase(self));
|
||||
returnToBase = true;
|
||||
return attackAircraft.Info.AbortOnResupply;
|
||||
@@ -136,23 +159,15 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
var minimumRange = attackAircraft.Info.AttackType == AirAttackType.Strafe ? WDist.Zero : attackAircraft.GetMinimumRangeVersusTarget(target);
|
||||
|
||||
// When strafing we must move forward for a minimum number of ticks after passing the target.
|
||||
if (remainingTicksUntilTurn > 0)
|
||||
{
|
||||
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
|
||||
remainingTicksUntilTurn--;
|
||||
}
|
||||
|
||||
// Move into range of the target.
|
||||
else if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
|
||||
if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange))
|
||||
QueueChild(aircraft.MoveWithinRange(target, minimumRange, lastVisibleMaximumRange, target.CenterPosition, Color.Red));
|
||||
|
||||
// The aircraft must keep moving forward even if it is already in an ideal position.
|
||||
else if (!aircraft.Info.CanHover || attackAircraft.Info.AttackType == AirAttackType.Strafe)
|
||||
{
|
||||
Fly.FlyTick(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
|
||||
remainingTicksUntilTurn = attackAircraft.Info.AttackTurnDelay;
|
||||
}
|
||||
else if (attackAircraft.Info.AttackType == AirAttackType.Strafe)
|
||||
QueueChild(new StrafeAttackRun(self, attackAircraft, target, strafeDistance != WDist.Zero ? strafeDistance : lastVisibleMaximumRange));
|
||||
else if (attackAircraft.Info.AttackType == AirAttackType.Default && !aircraft.Info.CanHover)
|
||||
QueueChild(new FlyAttackRun(self, target, lastVisibleMaximumRange));
|
||||
|
||||
// Turn to face the target if required.
|
||||
else if (!attackAircraft.TargetInFiringArc(self, target, attackAircraft.Info.FacingTolerance))
|
||||
@@ -173,7 +188,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (newStance > oldStance || forceAttack)
|
||||
return;
|
||||
|
||||
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
|
||||
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
attackAircraft.ClearRequestedTarget();
|
||||
}
|
||||
|
||||
@@ -189,4 +205,79 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FlyAttackRun : Activity
|
||||
{
|
||||
Target target;
|
||||
WDist exitRange;
|
||||
bool targetIsVisibleActor;
|
||||
|
||||
public FlyAttackRun(Actor self, Target t, WDist exitRange)
|
||||
{
|
||||
ChildHasPriority = false;
|
||||
|
||||
target = t;
|
||||
this.exitRange = exitRange;
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
QueueChild(new Fly(self, target, target.CenterPosition));
|
||||
QueueChild(new Fly(self, target, exitRange, WDist.MaxValue, target.CenterPosition));
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (TickChild(self) || IsCanceling)
|
||||
return true;
|
||||
|
||||
// Cancel the run if the target become invalid (e.g. killed) while visible
|
||||
var targetWasVisibleActor = targetIsVisibleActor;
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
targetIsVisibleActor = target.Type == TargetType.Actor && !targetIsHiddenActor;
|
||||
|
||||
if (targetWasVisibleActor && !target.IsValidFor(self))
|
||||
Cancel(self);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class StrafeAttackRun : Activity
|
||||
{
|
||||
Target target;
|
||||
WDist exitRange;
|
||||
readonly AttackAircraft attackAircraft;
|
||||
|
||||
public StrafeAttackRun(Actor self, AttackAircraft attackAircraft, Target t, WDist exitRange)
|
||||
{
|
||||
ChildHasPriority = false;
|
||||
|
||||
target = t;
|
||||
this.attackAircraft = attackAircraft;
|
||||
this.exitRange = exitRange;
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
QueueChild(new Fly(self, target, target.CenterPosition));
|
||||
QueueChild(new Fly(self, target, exitRange, WDist.MaxValue, target.CenterPosition));
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (TickChild(self) || IsCanceling)
|
||||
return true;
|
||||
|
||||
// Strafe attacks target the ground below the original target
|
||||
// Update the position if we seen the target move; keep the previous one if it dies or disappears
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
attackAircraft.SetRequestedTarget(self, Target.FromTargetPositions(target), true);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly IMove move;
|
||||
Target fallbackTarget;
|
||||
bool movedToTarget = false;
|
||||
|
||||
public TakeOff(Actor self, Target fallbackTarget)
|
||||
public TakeOff(Actor self)
|
||||
{
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
move = self.Trait<IMove>();
|
||||
this.fallbackTarget = fallbackTarget;
|
||||
}
|
||||
|
||||
public TakeOff(Actor self)
|
||||
: this(self, Target.Invalid) { }
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
if (aircraft.ForceLanding)
|
||||
@@ -42,8 +36,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length >= aircraft.Info.MinAirborneAltitude)
|
||||
return;
|
||||
|
||||
// We are taking off, so remove reservation and influence in ground cells.
|
||||
aircraft.UnReserve();
|
||||
// We are taking off, so remove influence in ground cells.
|
||||
aircraft.RemoveInfluence();
|
||||
|
||||
if (aircraft.Info.TakeoffSounds.Length > 0)
|
||||
@@ -73,24 +66,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only move to the fallback target if we don't have anything better to do
|
||||
if (NextActivity == null && fallbackTarget.IsValidFor(self) && !movedToTarget)
|
||||
{
|
||||
QueueChild(new AttackMoveActivity(self, () => move.MoveToTarget(self, fallbackTarget, targetLineColor: Color.OrangeRed)));
|
||||
movedToTarget = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
foreach (var n in ChildActivity.TargetLineNodes(self))
|
||||
yield return n;
|
||||
else
|
||||
yield return new TargetLineNode(fallbackTarget, Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (newStance > oldStance || forceAttack)
|
||||
return;
|
||||
|
||||
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
|
||||
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
target = Target.Invalid;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,8 +97,14 @@ namespace OpenRA.Mods.Common.Activities
|
||||
// Put back into world
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (self.IsDead)
|
||||
return;
|
||||
|
||||
var cargo = carryall.Carryable;
|
||||
var carryable = carryall.Carryable.Trait<Carryable>();
|
||||
if (cargo == null)
|
||||
return;
|
||||
|
||||
var carryable = cargo.Trait<Carryable>();
|
||||
w.Add(cargo);
|
||||
carryall.DetachCarryable(self);
|
||||
carryable.UnReserve(cargo);
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
case EnterState.Exiting:
|
||||
{
|
||||
QueueChild(move.MoveIntoWorld(self));
|
||||
QueueChild(move.ReturnToCell(self));
|
||||
lastState = EnterState.Finished;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
if (b != WVec.Zero && c != WVec.Zero)
|
||||
{
|
||||
var cosA = (int)(1024 * (b.LengthSquared + c.LengthSquared - a.LengthSquared) / (2 * b.Length * c.Length));
|
||||
var cosA = (int)(512 * (b.LengthSquared + c.LengthSquared - a.LengthSquared) / b.Length / c.Length);
|
||||
|
||||
// Cost modifier varies between 0 and ResourceRefineryDirectionPenalty
|
||||
return Math.Abs(harvInfo.ResourceRefineryDirectionPenalty / 2) + harvInfo.ResourceRefineryDirectionPenalty * cosA / 2048;
|
||||
@@ -245,7 +245,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
yield return n;
|
||||
|
||||
if (orderLocation != null)
|
||||
yield return new TargetLineNode(Target.FromCell(self.World, orderLocation.Value), Color.Green);
|
||||
yield return new TargetLineNode(Target.FromCell(self.World, orderLocation.Value), Color.Crimson);
|
||||
else if (deliverActor != null)
|
||||
yield return new TargetLineNode(Target.FromActor(deliverActor), Color.Green);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
|
||||
{
|
||||
yield return new TargetLineNode(Target.FromCell(self.World, targetCell), Color.Green);
|
||||
yield return new TargetLineNode(Target.FromCell(self.World, targetCell), Color.Crimson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
ChildActivity.Cancel(self);
|
||||
var attackBases = autoTarget.ActiveAttackBases;
|
||||
foreach (var ab in attackBases)
|
||||
QueueChild(ab.GetAttackActivity(self, target, false, false));
|
||||
QueueChild(ab.GetAttackActivity(self, AttackSource.AttackMove, target, false, false));
|
||||
|
||||
// Make sure to continue moving when the attack activities have finished.
|
||||
QueueChild(getInner());
|
||||
|
||||
@@ -208,6 +208,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
var nextCell = path[path.Count - 1];
|
||||
|
||||
// Something else might have moved us, so the path is no longer valid.
|
||||
if (!Util.AreAdjacentCells(mobile.ToCell, nextCell))
|
||||
{
|
||||
path = EvalPath();
|
||||
return null;
|
||||
}
|
||||
|
||||
var containsTemporaryBlocker = WorldUtils.ContainsTemporaryBlocker(self.World, nextCell, self);
|
||||
|
||||
// Next cell in the move is blocked by another actor
|
||||
@@ -331,9 +338,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
{
|
||||
if (Move.mobile.IsTraitDisabled)
|
||||
return false;
|
||||
|
||||
var ret = InnerTick(self, Move.mobile);
|
||||
|
||||
if (moveFraction > MoveFractionTotal)
|
||||
@@ -412,7 +416,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var nextCell = parent.PopPath(self);
|
||||
if (nextCell != null)
|
||||
{
|
||||
if (IsTurn(mobile, nextCell.Value.First))
|
||||
if (!mobile.IsTraitPaused && !mobile.IsTraitDisabled && IsTurn(mobile, nextCell.Value.First))
|
||||
{
|
||||
var nextSubcellOffset = map.Grid.OffsetOfSubCell(nextCell.Value.Second);
|
||||
var ret = new MoveFirstHalf(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -20,20 +21,18 @@ namespace OpenRA.Mods.Common.Activities
|
||||
public class PickupUnit : Activity
|
||||
{
|
||||
readonly Actor cargo;
|
||||
readonly IMove movement;
|
||||
|
||||
readonly Carryall carryall;
|
||||
readonly IFacing carryallFacing;
|
||||
|
||||
readonly Carryable carryable;
|
||||
readonly IFacing carryableFacing;
|
||||
readonly BodyOrientation carryableBody;
|
||||
|
||||
readonly int delay;
|
||||
|
||||
enum PickupState { Intercept, LockCarryable, Pickup }
|
||||
// TODO: Expose this to yaml
|
||||
readonly WDist targetLockRange = WDist.FromCells(4);
|
||||
|
||||
PickupState state;
|
||||
enum PickupState { Intercept, LockCarryable, Pickup }
|
||||
PickupState state = PickupState.Intercept;
|
||||
|
||||
public PickupUnit(Actor self, Actor cargo, int delay)
|
||||
{
|
||||
@@ -43,16 +42,20 @@ namespace OpenRA.Mods.Common.Activities
|
||||
carryableFacing = cargo.Trait<IFacing>();
|
||||
carryableBody = cargo.Trait<BodyOrientation>();
|
||||
|
||||
movement = self.Trait<IMove>();
|
||||
carryall = self.Trait<Carryall>();
|
||||
carryallFacing = self.Trait<IFacing>();
|
||||
|
||||
state = PickupState.Intercept;
|
||||
ChildHasPriority = false;
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
carryall.ReserveCarryable(self, cargo);
|
||||
if (carryall.ReserveCarryable(self, cargo))
|
||||
{
|
||||
// Fly to the target and wait for it to be locked for pickup
|
||||
// These activities will be cancelled and replaced by Land once the target has been locked
|
||||
QueueChild(new Fly(self, Target.FromActor(cargo)));
|
||||
QueueChild(new FlyCircle(self));
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
@@ -74,26 +77,21 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return true;
|
||||
}
|
||||
|
||||
if (carryall.State != Carryall.CarryallState.Reserved)
|
||||
return true;
|
||||
// Wait until we are near the target before we try to lock it
|
||||
var distSq = (cargo.CenterPosition - self.CenterPosition).HorizontalLengthSquared;
|
||||
if (state == PickupState.Intercept && distSq <= targetLockRange.LengthSquared)
|
||||
state = PickupState.LockCarryable;
|
||||
|
||||
switch (state)
|
||||
if (state == PickupState.LockCarryable)
|
||||
{
|
||||
case PickupState.Intercept:
|
||||
QueueChild(movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4)));
|
||||
state = PickupState.LockCarryable;
|
||||
return false;
|
||||
|
||||
case PickupState.LockCarryable:
|
||||
if (!carryable.LockForPickup(cargo, self))
|
||||
Cancel(self);
|
||||
|
||||
state = PickupState.Pickup;
|
||||
return false;
|
||||
|
||||
case PickupState.Pickup:
|
||||
var lockResponse = carryable.LockForPickup(cargo, self);
|
||||
if (lockResponse == LockResponse.Failed)
|
||||
Cancel(self);
|
||||
else if (lockResponse == LockResponse.Success)
|
||||
{
|
||||
// Land at the target location
|
||||
// Pickup position and facing are now known - swap the fly/wait activity with Land
|
||||
ChildActivity.Cancel(self);
|
||||
|
||||
var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation));
|
||||
QueueChild(new Land(self, Target.FromActor(cargo), -carryableBody.LocalToWorld(localOffset), carryableFacing.Facing));
|
||||
|
||||
@@ -104,11 +102,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
// Remove our carryable from world
|
||||
QueueChild(new AttachUnit(self, cargo));
|
||||
QueueChild(new TakeOff(self));
|
||||
return false;
|
||||
|
||||
state = PickupState.Pickup;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
// Return once we are in the pickup state and the pickup activities have completed
|
||||
return TickChild(self) && state == PickupState.Pickup;
|
||||
}
|
||||
|
||||
public override IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
|
||||
|
||||
@@ -187,10 +187,12 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
if (wasRepaired || isHostInvalid || (!stayOnResupplier && aircraft.Info.TakeOffOnResupply))
|
||||
{
|
||||
if (rp != null)
|
||||
QueueChild(move.MoveTo(rp.Location, repairableNear != null ? null : host.Actor));
|
||||
if (self.CurrentActivity.NextActivity == null && rp != null)
|
||||
QueueChild(move.MoveTo(rp.Location, repairableNear != null ? null : host.Actor, targetLineColor: Color.Green));
|
||||
else
|
||||
QueueChild(new TakeOff(self));
|
||||
|
||||
aircraft.UnReserve();
|
||||
}
|
||||
|
||||
// Aircraft without TakeOffOnResupply remain on the resupplier until something else needs it
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (self.IsDead)
|
||||
return;
|
||||
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var move = actor.Trait<IMove>();
|
||||
var pos = actor.Trait<IPositionable>();
|
||||
|
||||
pos.SetPosition(self, exitSubCell.Value.First, exitSubCell.Value.Second);
|
||||
pos.SetPosition(actor, exitSubCell.Value.First, exitSubCell.Value.Second);
|
||||
pos.SetVisualPosition(actor, spawn);
|
||||
|
||||
actor.CancelActivity();
|
||||
|
||||
@@ -24,13 +24,13 @@ namespace OpenRA.Mods.Common
|
||||
public int Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class MoveIntoWorldDelayInit : IActorInit<int>
|
||||
public class CreationActivityDelayInit : IActorInit<int>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
readonly int value = 0;
|
||||
|
||||
public MoveIntoWorldDelayInit() { }
|
||||
public MoveIntoWorldDelayInit(int init) { value = init; }
|
||||
public CreationActivityDelayInit() { }
|
||||
public CreationActivityDelayInit(int init) { value = init; }
|
||||
public int Value(World world) { return value; }
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Effects
|
||||
if (range == WDist.Zero)
|
||||
return NoCells;
|
||||
|
||||
return Shroud.ProjectedCellsInRange(map, pos, range)
|
||||
return Shroud.ProjectedCellsInRange(map, pos, WDist.Zero, range)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
|
||||
public string OrderID { get; private set; }
|
||||
public int OrderPriority { get; private set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
|
||||
@@ -243,6 +243,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
var plugInfo = activeVariant.PlugInfo;
|
||||
var lineBuildInfo = activeVariant.LineBuildInfo;
|
||||
var preview = activeVariant.Preview;
|
||||
var owner = queue.Actor.Owner;
|
||||
|
||||
if (plugInfo != null)
|
||||
{
|
||||
@@ -251,7 +252,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
|
||||
footprint.Add(topLeft, MakeCellType(AcceptsPlug(topLeft, plugInfo)));
|
||||
}
|
||||
else if (lineBuildInfo != null)
|
||||
else if (lineBuildInfo != null && owner.Shroud.IsExplored(topLeft))
|
||||
{
|
||||
// Linebuild for walls.
|
||||
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
|
||||
@@ -259,7 +260,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
|
||||
if (!Game.GetModifierKeys().HasModifier(Modifiers.Shift))
|
||||
{
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, actorInfo, buildingInfo))
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(world, topLeft, actorInfo, buildingInfo, owner))
|
||||
{
|
||||
var lineBuildable = world.IsCellBuildable(t.First, actorInfo, buildingInfo);
|
||||
var lineCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, t.First);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.Orders
|
||||
public string OrderID { get; private set; }
|
||||
public int OrderPriority { get; private set; }
|
||||
public bool? ForceAttack = null;
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public abstract bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor);
|
||||
public abstract bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor);
|
||||
|
||||
@@ -47,9 +47,11 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
|
||||
if (entryLocation.HasValue)
|
||||
{
|
||||
var pi = ai.TraitInfoOrDefault<AircraftInfo>();
|
||||
initDict.Add(new CenterPositionInit(owner.World.Map.CenterOfCell(entryLocation.Value) + new WVec(0, 0, pi != null ? pi.CruiseAltitude.Length : 0)));
|
||||
initDict.Add(new LocationInit(entryLocation.Value));
|
||||
|
||||
var pi = ai.TraitInfoOrDefault<AircraftInfo>();
|
||||
if (pi != null)
|
||||
initDict.Add(new CenterPositionInit(owner.World.Map.CenterOfCell(entryLocation.Value) + new WVec(0, 0, pi.CruiseAltitude.Length)));
|
||||
}
|
||||
|
||||
if (entryLocation.HasValue && nextLocation.HasValue)
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
Log.Write("lua", "{1} is not revealed for player {0}!", Self.Owner, targetActor);
|
||||
|
||||
foreach (var attack in attackBases)
|
||||
attack.AttackTarget(target, true, allowMove, forceAttack);
|
||||
attack.AttackTarget(target, AttackSource.Default, true, allowMove, forceAttack);
|
||||
}
|
||||
|
||||
[Desc("Checks if the targeted actor is a valid target for this actor.")]
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
var pos = Self.CenterPosition;
|
||||
mobile.SetPosition(Self, cell);
|
||||
mobile.SetVisualPosition(Self, pos);
|
||||
Self.QueueActivity(mobile.MoveIntoWorld(Self));
|
||||
Self.QueueActivity(mobile.ReturnToCell(Self));
|
||||
}
|
||||
|
||||
[ScriptActorPropertyActivity]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using Eluant;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Scripting;
|
||||
@@ -35,7 +36,13 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
public int PassengerCount { get { return cargo.Passengers.Count(); } }
|
||||
|
||||
[Desc("Teleport an existing actor inside this transport.")]
|
||||
public void LoadPassenger(Actor a) { cargo.Load(Self, a); }
|
||||
public void LoadPassenger(Actor a)
|
||||
{
|
||||
if (!a.IsIdle)
|
||||
throw new LuaException("LoadPassenger requires the passenger to be idle.");
|
||||
|
||||
cargo.Load(Self, a);
|
||||
}
|
||||
|
||||
[Desc("Remove the first actor from the transport. This actor is not added to the world.")]
|
||||
public Actor UnloadPassenger() { return cargo.Unload(Self); }
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Server
|
||||
lastPing = Game.RunTime;
|
||||
|
||||
// Ignore client timeout in singleplayer games to make debugging easier
|
||||
if (server.LobbyInfo.NonBotClients.Count() < 2 && !server.Dedicated)
|
||||
if (server.LobbyInfo.NonBotClients.Count() < 2 && server.Type != ServerType.Dedicated)
|
||||
foreach (var c in server.Conns.ToList())
|
||||
server.SendOrderTo(c, "Ping", Game.RunTime.ToString());
|
||||
else
|
||||
|
||||
@@ -42,7 +42,20 @@ namespace OpenRA.Mods.Common
|
||||
|
||||
// Bot-controlled units aren't yet capable of understanding visibility changes
|
||||
if (viewer.IsBot)
|
||||
{
|
||||
// Prevent that bot-controlled units endlessly fire at frozen actors.
|
||||
// TODO: Teach the AI to support long range artillery units with units that provide line of sight
|
||||
if (t.Type == TargetType.FrozenActor)
|
||||
{
|
||||
if (t.FrozenActor.Actor != null)
|
||||
return Target.FromActor(t.FrozenActor.Actor);
|
||||
|
||||
// Original actor was killed
|
||||
return Target.Invalid;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t.Type == TargetType.Actor)
|
||||
{
|
||||
@@ -68,8 +81,7 @@ namespace OpenRA.Mods.Common
|
||||
return Target.FromActor(t.FrozenActor.Actor);
|
||||
|
||||
// Original actor was killed while hidden
|
||||
if (t.Actor == null)
|
||||
return Target.Invalid;
|
||||
return Target.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public abstract class AffectsShroudInfo : ConditionalTraitInfo
|
||||
{
|
||||
public readonly WDist MinRange = WDist.Zero;
|
||||
|
||||
public readonly WDist Range = WDist.Zero;
|
||||
|
||||
[Desc("If >= 0, prevent cells that are this much higher than the actor from being revealed.")]
|
||||
@@ -61,15 +63,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
PPos[] ProjectedCells(Actor self)
|
||||
{
|
||||
var map = self.World.Map;
|
||||
var range = Range;
|
||||
if (range == WDist.Zero)
|
||||
var minRange = Info.MinRange;
|
||||
var maxRange = Range;
|
||||
if (maxRange <= minRange)
|
||||
return NoCells;
|
||||
|
||||
if (Info.Type == VisibilityType.Footprint)
|
||||
{
|
||||
// PERF: Reuse collection to avoid allocations.
|
||||
footprint.UnionWith(self.OccupiesSpace.OccupiedCells()
|
||||
.SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range, Info.MaxHeightDelta)));
|
||||
.SelectMany(kv => Shroud.ProjectedCellsInRange(map, map.CenterOfCell(kv.First), minRange, maxRange, Info.MaxHeightDelta)));
|
||||
var cells = footprint.ToArray();
|
||||
footprint.Clear();
|
||||
return cells;
|
||||
@@ -79,7 +82,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (Info.Type == VisibilityType.GroundPosition)
|
||||
pos -= new WVec(WDist.Zero, WDist.Zero, self.World.Map.DistanceAboveTerrain(pos));
|
||||
|
||||
return Shroud.ProjectedCellsInRange(map, pos, range, Info.MaxHeightDelta)
|
||||
return Shroud.ProjectedCellsInRange(map, pos, minRange, maxRange, Info.MaxHeightDelta)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
bool IOccupySpaceInfo.SharesCell { get { return false; } }
|
||||
|
||||
// Used to determine if an aircraft can spawn landed
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
@@ -173,6 +173,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!checkTransientActors)
|
||||
return true;
|
||||
|
||||
// Since aircraft don't share cells, we don't pass the subCell parameter
|
||||
return !world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor);
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public class Aircraft : ITick, ISync, IFacing, IPositionable, IMove, IIssueOrder, IResolveOrder, IOrderVoice, IDeathActorInitModifier,
|
||||
INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing, INotifyBecomingIdle,
|
||||
IActorPreviewInitModifier, IIssueDeployOrder, IObservesVariables
|
||||
IActorPreviewInitModifier, IIssueDeployOrder, IObservesVariables, ICreationActivity
|
||||
{
|
||||
static readonly Pair<CPos, SubCell>[] NoCells = { };
|
||||
|
||||
@@ -220,7 +221,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
IEnumerable<CPos> landingCells = Enumerable.Empty<CPos>();
|
||||
bool requireForceMove;
|
||||
int moveIntoWorldDelay;
|
||||
int creationActivityDelay;
|
||||
|
||||
public static WPos GroundPosition(Actor self)
|
||||
{
|
||||
@@ -251,7 +252,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
SetPosition(self, init.Get<CenterPositionInit, WPos>());
|
||||
|
||||
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : Info.InitialFacing;
|
||||
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
|
||||
|
||||
if (init.Contains<CreationActivityDelayInit>())
|
||||
creationActivityDelay = init.Get<CreationActivityDelayInit, int>();
|
||||
}
|
||||
|
||||
public WDist LandAltitude
|
||||
@@ -309,8 +312,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
notifyMoving = self.TraitsImplementing<INotifyMoving>().ToArray();
|
||||
positionOffsets = self.TraitsImplementing<IAircraftCenterPositionOffset>().ToArray();
|
||||
overrideAircraftLanding = self.TraitOrDefault<IOverrideAircraftLanding>();
|
||||
|
||||
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
@@ -506,26 +507,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
MayYieldReservation = true;
|
||||
}
|
||||
|
||||
public void UnReserve(bool takeOff = false)
|
||||
public void UnReserve()
|
||||
{
|
||||
if (reservation == null)
|
||||
return;
|
||||
|
||||
// Move to the host's rally point if it has one
|
||||
var rp = ReservedActor != null ? ReservedActor.TraitOrDefault<RallyPoint>() : null;
|
||||
|
||||
reservation.Dispose();
|
||||
reservation = null;
|
||||
ReservedActor = null;
|
||||
MayYieldReservation = false;
|
||||
|
||||
if (takeOff && self.World.Map.DistanceAboveTerrain(CenterPosition).Length <= LandAltitude.Length)
|
||||
{
|
||||
if (rp != null)
|
||||
self.QueueActivity(new TakeOff(self, Target.FromCell(self.World, rp.Location)));
|
||||
else
|
||||
self.QueueActivity(new TakeOff(self));
|
||||
}
|
||||
}
|
||||
|
||||
bool AircraftCanEnter(Actor a, TargetModifiers modifiers)
|
||||
@@ -858,18 +848,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, int delay = 0)
|
||||
{
|
||||
return new MoveIntoWorldActivity(self, delay);
|
||||
}
|
||||
public Activity ReturnToCell(Actor self) { return null; }
|
||||
|
||||
class MoveIntoWorldActivity : Activity
|
||||
class AssociateWithAirfieldActivity : Activity
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly Aircraft aircraft;
|
||||
readonly int delay;
|
||||
|
||||
public MoveIntoWorldActivity(Actor self, int delay = 0)
|
||||
public AssociateWithAirfieldActivity(Actor self, int delay = 0)
|
||||
{
|
||||
this.self = self;
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
@@ -1179,9 +1166,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
inits.Add(new DynamicFacingInit(() => Facing));
|
||||
}
|
||||
|
||||
Activity ICreationActivity.GetCreationActivity()
|
||||
{
|
||||
return new AssociateWithAirfieldActivity(self, creationActivityDelay);
|
||||
}
|
||||
|
||||
public class AircraftMoveOrderTargeter : IOrderTargeter
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly BuildingInfluence bi;
|
||||
|
||||
public string OrderID { get; protected set; }
|
||||
public int OrderPriority { get { return 4; } }
|
||||
@@ -1190,11 +1183,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public AircraftMoveOrderTargeter(Aircraft aircraft)
|
||||
{
|
||||
this.aircraft = aircraft;
|
||||
bi = aircraft.self.World.WorldActor.TraitOrDefault<BuildingInfluence>();
|
||||
OrderID = "Move";
|
||||
}
|
||||
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers)
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
|
||||
{
|
||||
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
|
||||
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
|
||||
return true;
|
||||
|
||||
return modifiers.HasModifier(TargetModifiers.ForceMove);
|
||||
}
|
||||
|
||||
@@ -1203,10 +1201,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (target.Type != TargetType.Terrain || (aircraft.requireForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove)))
|
||||
return false;
|
||||
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove) && aircraft.Info.CanForceLand)
|
||||
OrderID = "Land";
|
||||
|
||||
var location = self.World.Map.CellContaining(target.CenterPosition);
|
||||
|
||||
// Aircraft can be force-landed by issuing a force-move order on a clear terrain cell
|
||||
// Cells that contain a blocking building are treated as regular force move orders, overriding
|
||||
// selection for left-mouse orders
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove) && aircraft.Info.CanForceLand)
|
||||
{
|
||||
var building = bi.GetBuildingAt(location);
|
||||
if (building == null || building.TraitOrDefault<Selectable>() == null || aircraft.CanLand(location, blockedByMobile: false))
|
||||
OrderID = "Land";
|
||||
}
|
||||
|
||||
var explored = self.Owner.Shroud.IsExplored(location);
|
||||
cursor = self.World.Map.Contains(location) ?
|
||||
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? "move") :
|
||||
|
||||
@@ -17,15 +17,18 @@ using OpenRA.Traits;
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
// TODO: Add CurleyShuffle (TD, TS), Circle (Generals Gunship-style)
|
||||
public enum AirAttackType { Hover, Strafe }
|
||||
public enum AirAttackType { Default, Hover, Strafe }
|
||||
|
||||
public class AttackAircraftInfo : AttackFollowInfo, Requires<AircraftInfo>
|
||||
{
|
||||
[Desc("Attack behavior. Currently supported types are Strafe (default) and Hover.")]
|
||||
public readonly AirAttackType AttackType = AirAttackType.Strafe;
|
||||
[Desc("Attack behavior. Currently supported types are:",
|
||||
"Default: Attack while following the default movement rules.",
|
||||
"Hover: Hover, even if the Aircraft can't hover while idle.",
|
||||
"Strafe: Perform a fixed-length attack run on the target.")]
|
||||
public readonly AirAttackType AttackType = AirAttackType.Default;
|
||||
|
||||
[Desc("Delay, in game ticks, before strafing aircraft turns to attack.")]
|
||||
public readonly int AttackTurnDelay = 50;
|
||||
[Desc("Distance the strafing aircraft makes to a target before turning for another pass. When set to WDist.Zero this defaults to the maximum armament range.")]
|
||||
public readonly WDist StrafeRunLength = WDist.Zero;
|
||||
|
||||
[Desc("Does this actor cancel its attack activity when it needs to resupply? Setting this to 'false' will make the actor resume attack after reloading.")]
|
||||
public readonly bool AbortOnResupply = true;
|
||||
@@ -45,9 +48,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
aircraftInfo = self.Info.TraitInfo<AircraftInfo>();
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
{
|
||||
return new FlyAttack(self, newTarget, forceAttack, targetLineColor);
|
||||
return new FlyAttack(self, source, newTarget, forceAttack, targetLineColor);
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
OnRemovedFromWorld(self);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor)
|
||||
{
|
||||
throw new NotImplementedException("AttackBomber requires a scripted target");
|
||||
}
|
||||
|
||||
@@ -42,18 +42,24 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
public class FallsToEarth : IEffectiveOwner
|
||||
public class FallsToEarth : IEffectiveOwner, INotifyCreated
|
||||
{
|
||||
readonly FallsToEarthInfo info;
|
||||
readonly Player effectiveOwner;
|
||||
|
||||
public FallsToEarth(ActorInitializer init, FallsToEarthInfo info)
|
||||
{
|
||||
init.Self.QueueActivity(false, new FallToEarth(init.Self, info));
|
||||
this.info = info;
|
||||
effectiveOwner = init.Contains<EffectiveOwnerInit>() ? init.Get<EffectiveOwnerInit, Player>() : init.Self.Owner;
|
||||
}
|
||||
|
||||
// We return init.Self.Owner if there's no effective owner
|
||||
bool IEffectiveOwner.Disguised { get { return true; } }
|
||||
Player IEffectiveOwner.Owner { get { return effectiveOwner; } }
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
self.QueueActivity(false, new FallToEarth(self, info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public enum AttackSource { Default, AutoTarget, AttackMove }
|
||||
|
||||
public abstract class AttackBaseInfo : PausableConditionalTraitInfo
|
||||
{
|
||||
[Desc("Armament names")]
|
||||
@@ -199,7 +201,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!order.Target.IsValidFor(self))
|
||||
return;
|
||||
|
||||
AttackTarget(order.Target, order.Queued, true, forceAttack, Info.TargetLineColor);
|
||||
AttackTarget(order.Target, AttackSource.Default, order.Queued, true, forceAttack, Info.TargetLineColor);
|
||||
self.ShowTargetLines();
|
||||
}
|
||||
|
||||
@@ -224,7 +226,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return order.OrderString == attackOrderName || order.OrderString == forceAttackOrderName ? Info.Voice : null;
|
||||
}
|
||||
|
||||
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null);
|
||||
public abstract Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null);
|
||||
|
||||
public bool HasAnyValidWeapons(Target t, bool checkForCenterTargetingWeapons = false)
|
||||
{
|
||||
@@ -391,7 +393,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
&& a.Weapon.IsValidAgainst(t, self.World, self));
|
||||
}
|
||||
|
||||
public void AttackTarget(Target target, bool queued, bool allowMove, bool forceAttack = false, Color? targetLineColor = null)
|
||||
public void AttackTarget(Target target, AttackSource source, bool queued, bool allowMove, bool forceAttack = false, Color? targetLineColor = null)
|
||||
{
|
||||
if (IsTraitDisabled)
|
||||
return;
|
||||
@@ -399,7 +401,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!target.IsValidFor(self))
|
||||
return;
|
||||
|
||||
var activity = GetAttackActivity(self, target, allowMove, forceAttack, targetLineColor);
|
||||
var activity = GetAttackActivity(self, source, target, allowMove, forceAttack, targetLineColor);
|
||||
self.QueueActivity(queued, activity);
|
||||
OnResolveAttackOrder(self, activity, target, queued, forceAttack);
|
||||
}
|
||||
@@ -436,7 +438,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public string OrderID { get; private set; }
|
||||
public int OrderPriority { get; private set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
bool CanTargetActor(Actor self, Target target, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
base.Tick(self);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
{
|
||||
return new AttackActivity(self, newTarget, allowMove, forceAttack, targetLineColor);
|
||||
}
|
||||
@@ -293,6 +293,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
// The target may become hidden in the same tick the AttackActivity constructor is called,
|
||||
// causing lastVisible* to remain uninitialized.
|
||||
// Fix the fallback values based on the frozen actor properties
|
||||
else if (target.Type == TargetType.FrozenActor && !lastVisibleTarget.IsValidFor(self))
|
||||
{
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
lastVisibleOwner = target.FrozenActor.Owner;
|
||||
lastVisibleTargetTypes = target.FrozenActor.TargetTypes;
|
||||
}
|
||||
|
||||
var maxRange = lastVisibleMaximumRange;
|
||||
var minRange = lastVisibleMinimumRange;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
@@ -353,7 +364,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (newStance > oldStance || forceAttack)
|
||||
return;
|
||||
|
||||
if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
// If lastVisibleTarget is invalid we could never view the target in the first place, so we just drop it here too
|
||||
if (!lastVisibleTarget.IsValidFor(self) || !autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes))
|
||||
attack.ClearRequestedTarget();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return TargetInFiringArc(self, target, Info.FacingTolerance);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
{
|
||||
return new Activities.Attack(self, newTarget, allowMove, forceAttack, targetLineColor);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public AttackOmni(Actor self, AttackOmniInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
public override Activity GetAttackActivity(Actor self, AttackSource source, Target newTarget, bool allowMove, bool forceAttack, Color? targetLineColor = null)
|
||||
{
|
||||
return new SetTarget(this, newTarget, allowMove, forceAttack, targetLineColor);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return prefix + "-blocked";
|
||||
}
|
||||
|
||||
public override bool InputOverridesSelection(WorldRenderer wr, World world, int2 xy, MouseInput mi)
|
||||
public override bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
|
||||
{
|
||||
// Custom order generators always override selection
|
||||
return true;
|
||||
|
||||
@@ -102,10 +102,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
// Prepare for transport pickup
|
||||
public override bool LockForPickup(Actor self, Actor carrier)
|
||||
public override LockResponse LockForPickup(Actor self, Actor carrier)
|
||||
{
|
||||
if (state == State.Locked || !WantsTransport)
|
||||
return false;
|
||||
if ((state == State.Locked && Carrier != carrier) || !WantsTransport)
|
||||
return LockResponse.Failed;
|
||||
|
||||
// Last chance to change our mind...
|
||||
var delta = self.World.Map.CenterOfCell(Destination.Value) - self.CenterPosition;
|
||||
@@ -113,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
// Cancel pickup
|
||||
MovementCancelled(self);
|
||||
return false;
|
||||
return LockResponse.Failed;
|
||||
}
|
||||
|
||||
return base.LockForPickup(self, carrier);
|
||||
|
||||
@@ -113,7 +113,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
QueueChild(new PickupUnit(self, cargo, 0));
|
||||
if (!cargo.IsDead)
|
||||
QueueChild(new PickupUnit(self, cargo, 0));
|
||||
}
|
||||
|
||||
public override bool Tick(Actor self)
|
||||
|
||||
@@ -127,6 +127,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyOwnerChanged
|
||||
{
|
||||
public readonly IEnumerable<AttackBase> ActiveAttackBases;
|
||||
|
||||
readonly bool allowMovement;
|
||||
|
||||
[Sync]
|
||||
int nextScanTime = 0;
|
||||
|
||||
@@ -187,6 +190,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
stance = self.Owner.IsBot || !self.Owner.Playable ? info.InitialStanceAI : info.InitialStance;
|
||||
|
||||
PredictedStance = stance;
|
||||
|
||||
allowMovement = Info.AllowMovement && self.TraitOrDefault<IMove>() != null;
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
@@ -243,6 +248,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
attacker = passenger.Transport;
|
||||
}
|
||||
|
||||
// Don't fire at an invisible enemy when we can't move to reveal it
|
||||
var allowMove = allowMovement && Stance > UnitStance.Defend;
|
||||
if (!allowMove && !attacker.CanBeViewedByPlayer(self.Owner))
|
||||
return;
|
||||
|
||||
// Not a lot we can do about things we can't hurt... although maybe we should automatically run away?
|
||||
var attackerAsTarget = Target.FromActor(attacker);
|
||||
if (!ActiveAttackBases.Any(a => a.HasAnyValidWeapons(attackerAsTarget)))
|
||||
@@ -254,7 +264,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
Aggressor = attacker;
|
||||
|
||||
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
|
||||
Attack(self, Target.FromActor(Aggressor), allowMove);
|
||||
}
|
||||
|
||||
@@ -263,7 +272,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (IsTraitDisabled || Stance < UnitStance.Defend)
|
||||
return;
|
||||
|
||||
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
|
||||
var allowMove = allowMovement && Stance > UnitStance.Defend;
|
||||
var allowTurn = Info.AllowTurning && Stance > UnitStance.HoldFire;
|
||||
ScanAndAttack(self, allowMove, allowTurn);
|
||||
}
|
||||
@@ -312,12 +321,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
void Attack(Actor self, Target target, bool allowMove)
|
||||
{
|
||||
foreach (var ab in ActiveAttackBases)
|
||||
ab.AttackTarget(target, false, allowMove);
|
||||
ab.AttackTarget(target, AttackSource.AutoTarget, false, allowMove);
|
||||
}
|
||||
|
||||
public bool HasValidTargetPriority(Actor self, Player owner, BitSet<TargetableType> targetTypes)
|
||||
{
|
||||
if (Stance <= UnitStance.ReturnFire)
|
||||
if (owner == null || Stance <= UnitStance.ReturnFire)
|
||||
return false;
|
||||
|
||||
return activeTargetPriorities.Any(ati =>
|
||||
|
||||
@@ -72,9 +72,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
int scanInterval;
|
||||
bool firstTick = true;
|
||||
|
||||
// MCVs that the bot already knows about. Any MCV not on this list needs to be given an order.
|
||||
List<Actor> activeMCVs = new List<Actor>();
|
||||
|
||||
public McvManagerBotModule(Actor self, McvManagerBotModuleInfo info)
|
||||
: base(info)
|
||||
{
|
||||
@@ -140,18 +137,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void DeployMcvs(IBot bot, bool chooseLocation)
|
||||
{
|
||||
activeMCVs.RemoveAll(unitCannotBeOrdered);
|
||||
|
||||
var newMCVs = world.ActorsHavingTrait<Transforms>()
|
||||
.Where(a => a.Owner == player &&
|
||||
a.IsIdle &&
|
||||
Info.McvTypes.Contains(a.Info.Name) &&
|
||||
!activeMCVs.Contains(a));
|
||||
.Where(a => a.Owner == player && a.IsIdle && Info.McvTypes.Contains(a.Info.Name));
|
||||
|
||||
foreach (var a in newMCVs)
|
||||
activeMCVs.Add(a);
|
||||
|
||||
foreach (var mcv in activeMCVs)
|
||||
foreach (var mcv in newMCVs)
|
||||
DeployMcv(bot, mcv, chooseLocation);
|
||||
}
|
||||
|
||||
@@ -202,15 +191,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
cells = cells.Shuffle(world.LocalRandom);
|
||||
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (!world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
|
||||
continue;
|
||||
|
||||
if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell))
|
||||
continue;
|
||||
|
||||
return cell;
|
||||
}
|
||||
if (world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
|
||||
return cell;
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -228,8 +210,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
return new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter)),
|
||||
new MiniYamlNode("ActiveMCVs", FieldSaver.FormatValue(activeMCVs.Select(a => a.ActorID).ToArray()))
|
||||
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -241,14 +222,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
|
||||
if (initialBaseCenterNode != null)
|
||||
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
|
||||
|
||||
var activeMCVsNode = data.FirstOrDefault(n => n.Key == "ActiveMCVs");
|
||||
if (activeMCVsNode != null)
|
||||
{
|
||||
activeMCVs.Clear();
|
||||
activeMCVs.AddRange(FieldLoader.GetValue<uint[]>("ActiveMCVs", activeMCVsNode.Value.Value)
|
||||
.Select(a => world.GetActorById(a)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var attackForce = RegisterNewSquad(bot, SquadType.Assault);
|
||||
|
||||
foreach (var a in unitsHangingAroundTheBase)
|
||||
if (!a.Info.HasTraitInfo<AircraftInfo>())
|
||||
if (!a.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(a.Info.Name))
|
||||
attackForce.Units.Add(a);
|
||||
|
||||
unitsHangingAroundTheBase.Clear();
|
||||
@@ -279,7 +279,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// TODO: This should use common names & ExcludeFromSquads instead of hardcoding TraitInfo checks
|
||||
var ownUnits = activeUnits
|
||||
.Where(unit => unit.IsIdle && unit.Info.HasTraitInfo<AttackBaseInfo>()
|
||||
&& !unit.Info.HasTraitInfo<AircraftInfo>() && !unit.Info.HasTraitInfo<HarvesterInfo>()).ToList();
|
||||
&& !unit.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(unit.Info.Name) && !unit.Info.HasTraitInfo<HarvesterInfo>()).ToList();
|
||||
|
||||
if (!allEnemyBaseBuilder.Any() || ownUnits.Count < Info.SquadSize)
|
||||
return;
|
||||
@@ -288,7 +288,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
// Don't rush enemy aircraft!
|
||||
var enemies = World.FindActorsInCircle(b.CenterPosition, WDist.FromCells(Info.RushAttackScanRadius))
|
||||
.Where(unit => IsEnemyUnit(unit) && unit.Info.HasTraitInfo<AttackBaseInfo>() && !unit.Info.HasTraitInfo<AircraftInfo>()).ToList();
|
||||
.Where(unit => IsEnemyUnit(unit) && unit.Info.HasTraitInfo<AttackBaseInfo>() && !unit.Info.HasTraitInfo<AircraftInfo>() && !Info.NavalUnitsTypes.Contains(unit.Info.Name)).ToList();
|
||||
|
||||
if (AttackOrFleeFuzzy.Rush.CanAttack(ownUnits, enemies))
|
||||
{
|
||||
@@ -357,8 +357,14 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
new MiniYamlNode("Squads", "", Squads.Select(s => new MiniYamlNode("Squad", s.Serialize())).ToList()),
|
||||
new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter)),
|
||||
new MiniYamlNode("UnitsHangingAroundTheBase", FieldSaver.FormatValue(unitsHangingAroundTheBase.Select(a => a.ActorID).ToArray())),
|
||||
new MiniYamlNode("ActiveUnits", FieldSaver.FormatValue(activeUnits.Select(a => a.ActorID).ToArray())),
|
||||
new MiniYamlNode("UnitsHangingAroundTheBase", FieldSaver.FormatValue(unitsHangingAroundTheBase
|
||||
.Where(a => !unitCannotBeOrdered(a))
|
||||
.Select(a => a.ActorID)
|
||||
.ToArray())),
|
||||
new MiniYamlNode("ActiveUnits", FieldSaver.FormatValue(activeUnits
|
||||
.Where(a => !unitCannotBeOrdered(a))
|
||||
.Select(a => a.ActorID)
|
||||
.ToArray())),
|
||||
new MiniYamlNode("RushTicks", FieldSaver.FormatValue(rushTicks)),
|
||||
new MiniYamlNode("AssignRolesTicks", FieldSaver.FormatValue(assignRolesTicks)),
|
||||
new MiniYamlNode("AttackForceTicks", FieldSaver.FormatValue(attackForceTicks)),
|
||||
@@ -380,7 +386,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
unitsHangingAroundTheBase.Clear();
|
||||
unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue<uint[]>("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value.Value)
|
||||
.Select(a => self.World.GetActorById(a)));
|
||||
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
|
||||
}
|
||||
|
||||
var activeUnitsNode = data.FirstOrDefault(n => n.Key == "ActiveUnits");
|
||||
@@ -388,7 +394,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
activeUnits.Clear();
|
||||
activeUnits.AddRange(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value.Value)
|
||||
.Select(a => self.World.GetActorById(a)));
|
||||
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
|
||||
}
|
||||
|
||||
var rushTicksNode = data.FirstOrDefault(n => n.Key == "RushTicks");
|
||||
|
||||
@@ -40,9 +40,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
readonly Dictionary<SupportPowerInstance, int> waitingPowers = new Dictionary<SupportPowerInstance, int>();
|
||||
readonly Dictionary<string, SupportPowerDecision> powerDecisions = new Dictionary<string, SupportPowerDecision>();
|
||||
readonly List<SupportPowerInstance> stalePowers = new List<SupportPowerInstance>();
|
||||
SupportPowerManager supportPowerManager;
|
||||
Dictionary<SupportPowerInstance, int> waitingPowers = new Dictionary<SupportPowerInstance, int>();
|
||||
Dictionary<string, SupportPowerDecision> powerDecisions = new Dictionary<string, SupportPowerDecision>();
|
||||
|
||||
public SupportPowerBotModule(Actor self, SupportPowerBotModuleInfo info)
|
||||
: base(info)
|
||||
@@ -108,6 +109,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
bot.QueueOrder(new Order(sp.Key, supportPowerManager.Self, Target.FromCell(world, attackLocation.Value), false) { SuppressVisualFeedback = true });
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale powers
|
||||
stalePowers.AddRange(waitingPowers.Keys.Where(wp => !supportPowerManager.Powers.ContainsKey(wp.Key)));
|
||||
foreach (var p in stalePowers)
|
||||
waitingPowers.Remove(p);
|
||||
|
||||
stalePowers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Scans the map in chunks, evaluating all actors in each.</summary>
|
||||
@@ -208,8 +216,14 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var waitingPowersNode = data.FirstOrDefault(n => n.Key == "WaitingPowers");
|
||||
if (waitingPowersNode != null)
|
||||
{
|
||||
foreach (var n in waitingPowersNode.Value.Nodes)
|
||||
waitingPowers[supportPowerManager.Powers[n.Key]] = FieldLoader.GetValue<int>("WaitingPowers", n.Value.Value);
|
||||
{
|
||||
SupportPowerInstance instance;
|
||||
if (supportPowerManager.Powers.TryGetValue(n.Key, out instance))
|
||||
waitingPowers[instance] = FieldLoader.GetValue<int>("WaitingPowers", n.Value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
readonly List<string> queuedBuildRequests = new List<string>();
|
||||
|
||||
IBotRequestPauseUnitProduction[] requestPause;
|
||||
|
||||
List<Actor> idleUnits = new List<Actor>();
|
||||
int idleUnitCount;
|
||||
|
||||
int ticks;
|
||||
|
||||
@@ -68,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void IBotNotifyIdleBaseUnits.UpdatedIdleBaseUnits(List<Actor> idleUnits)
|
||||
{
|
||||
this.idleUnits = idleUnits;
|
||||
idleUnitCount = idleUnits.Count;
|
||||
}
|
||||
|
||||
void IBotTick.BotTick(IBot bot)
|
||||
@@ -88,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
foreach (var q in Info.UnitQueues)
|
||||
BuildUnit(bot, q, idleUnits.Count < Info.IdleBaseUnitsMaximum);
|
||||
BuildUnit(bot, q, idleUnitCount < Info.IdleBaseUnitsMaximum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +217,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return new List<MiniYamlNode>()
|
||||
{
|
||||
new MiniYamlNode("QueuedBuildRequests", FieldSaver.FormatValue(queuedBuildRequests.ToArray())),
|
||||
new MiniYamlNode("IdleUnits", FieldSaver.FormatValue(idleUnits.Select(a => a.ActorID).ToArray()))
|
||||
new MiniYamlNode("IdleUnitCount", FieldSaver.FormatValue(idleUnitCount))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -234,13 +233,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
queuedBuildRequests.AddRange(FieldLoader.GetValue<string[]>("QueuedBuildRequests", queuedBuildRequestsNode.Value.Value));
|
||||
}
|
||||
|
||||
var idleUnitsNode = data.FirstOrDefault(n => n.Key == "IdleUnits");
|
||||
if (idleUnitsNode != null)
|
||||
{
|
||||
idleUnits.Clear();
|
||||
idleUnits.AddRange(FieldLoader.GetValue<uint[]>("IdleUnits", idleUnitsNode.Value.Value)
|
||||
.Select(a => world.GetActorById(a)));
|
||||
}
|
||||
var idleUnitCountNode = data.FirstOrDefault(n => n.Key == "IdleUnitCount");
|
||||
if (idleUnitCountNode != null)
|
||||
idleUnitCount = FieldLoader.GetValue<int>("IdleUnitCount", idleUnitCountNode.Value.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
world.IsCellBuildable(t, ai, bi, toIgnore));
|
||||
}
|
||||
|
||||
public static IEnumerable<Pair<CPos, Actor>> GetLineBuildCells(World world, CPos cell, ActorInfo ai, BuildingInfo bi)
|
||||
public static IEnumerable<Pair<CPos, Actor>> GetLineBuildCells(World world, CPos cell, ActorInfo ai, BuildingInfo bi, Player owner)
|
||||
{
|
||||
var lbi = ai.TraitInfo<LineBuildInfo>();
|
||||
var topLeft = cell; // 1x1 assumption!
|
||||
@@ -81,9 +81,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (dirs[d] != 0)
|
||||
continue;
|
||||
|
||||
// Continue the search if the cell is empty or not visible
|
||||
var c = topLeft + i * vecs[d];
|
||||
if (world.IsCellBuildable(c, ai, bi))
|
||||
continue; // Cell is empty; continue search
|
||||
if (world.IsCellBuildable(c, ai, bi) || !owner.Shroud.IsExplored(c))
|
||||
continue;
|
||||
|
||||
// Cell contains an actor. Is it the type we want?
|
||||
connectors[d] = world.ActorMap.GetActorsAt(c)
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public string OrderID { get { return "SetRallyPoint"; } }
|
||||
public int OrderPriority { get { return 0; } }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
public bool ForceSet { get; private set; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -18,10 +19,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Reserve landing places for aircraft.")]
|
||||
class ReservableInfo : TraitInfo<Reservable> { }
|
||||
|
||||
public class Reservable : ITick, INotifyOwnerChanged, INotifySold, INotifyActorDisposing
|
||||
public class Reservable : ITick, INotifyOwnerChanged, INotifySold, INotifyActorDisposing, INotifyCreated
|
||||
{
|
||||
Actor reservedFor;
|
||||
Aircraft reservedForAircraft;
|
||||
RallyPoint rallyPoint;
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
rallyPoint = self.TraitOrDefault<RallyPoint>();
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
@@ -41,7 +48,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public IDisposable Reserve(Actor self, Actor forActor, Aircraft forAircraft)
|
||||
{
|
||||
if (reservedForAircraft != null && reservedForAircraft.MayYieldReservation)
|
||||
reservedForAircraft.UnReserve(true);
|
||||
UnReserve(self);
|
||||
|
||||
reservedFor = forActor;
|
||||
reservedForAircraft = forAircraft;
|
||||
@@ -71,17 +78,27 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return res == null || res.reservedForAircraft == null || res.reservedForAircraft.MayYieldReservation || res.reservedFor == forActor;
|
||||
}
|
||||
|
||||
private void UnReserve()
|
||||
void UnReserve(Actor self)
|
||||
{
|
||||
if (reservedForAircraft != null)
|
||||
reservedForAircraft.UnReserve(true);
|
||||
{
|
||||
if (reservedForAircraft.GetActorBelow() == self)
|
||||
{
|
||||
if (rallyPoint != null)
|
||||
reservedFor.QueueActivity(reservedForAircraft.MoveTo(rallyPoint.Location, null, targetLineColor: Color.Green));
|
||||
else
|
||||
reservedFor.QueueActivity(new TakeOff(reservedFor));
|
||||
}
|
||||
|
||||
reservedForAircraft.UnReserve();
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyActorDisposing.Disposing(Actor self) { UnReserve(); }
|
||||
void INotifyActorDisposing.Disposing(Actor self) { UnReserve(self); }
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UnReserve(); }
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { UnReserve(self); }
|
||||
|
||||
void INotifySold.Selling(Actor self) { UnReserve(); }
|
||||
void INotifySold.Sold(Actor self) { UnReserve(); }
|
||||
void INotifySold.Selling(Actor self) { UnReserve(self); }
|
||||
void INotifySold.Sold(Actor self) { UnReserve(self); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +158,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly TransformsIntoAircraft aircraft;
|
||||
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers)
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
|
||||
{
|
||||
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
|
||||
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
|
||||
return true;
|
||||
|
||||
return modifiers.HasModifier(TargetModifiers.ForceMove);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,8 +158,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly TransformsIntoMobile mobile;
|
||||
readonly bool rejectMove;
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers)
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
|
||||
{
|
||||
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
|
||||
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
|
||||
return true;
|
||||
|
||||
return modifiers.HasModifier(TargetModifiers.ForceMove);
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (captures == null)
|
||||
return false;
|
||||
|
||||
// HACK: Make sure the target is not moving and at its normal position with respect to the cell grid
|
||||
var enterMobile = target.TraitOrDefault<Mobile>();
|
||||
if (enterMobile != null && enterMobile.IsMovingBetweenCells)
|
||||
return false;
|
||||
|
||||
if (progressWatchers.Any() || targetManager.progressWatchers.Any())
|
||||
{
|
||||
currentTargetTotal = captures.Info.CaptureDelay;
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, blockedByMobile: false))
|
||||
&& CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait<IPositionable>().CanEnterCell(c, null, immediate)));
|
||||
&& CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => !p.IsDead && p.Trait<IPositionable>().CanEnterCell(c, null, immediate)));
|
||||
}
|
||||
|
||||
public bool CanLoad(Actor self, Actor a)
|
||||
@@ -258,7 +258,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
internal void UnreserveSpace(Actor a)
|
||||
{
|
||||
if (!reserves.Contains(a))
|
||||
if (!reserves.Contains(a) || self.IsDead)
|
||||
return;
|
||||
|
||||
reservedWeight -= GetWeight(a);
|
||||
@@ -409,11 +409,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// If not initialized then this will be notified in the first tick
|
||||
if (initialized)
|
||||
{
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.OnPassengerEntered(self, a);
|
||||
|
||||
foreach (var nec in a.TraitsImplementing<INotifyEnteredCargo>())
|
||||
nec.OnEnteredCargo(a, self);
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.OnPassengerEntered(self, a);
|
||||
}
|
||||
|
||||
var p = a.Trait<Passenger>();
|
||||
@@ -510,11 +510,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
c.Trait<Passenger>().Transport = self;
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.OnPassengerEntered(self, c);
|
||||
|
||||
foreach (var nec in c.TraitsImplementing<INotifyEnteredCargo>())
|
||||
nec.OnEnteredCargo(c, self);
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.OnPassengerEntered(self, c);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
@@ -35,6 +35,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public override object Create(ActorInitializer init) { return new Carryable(init.Self, this); }
|
||||
}
|
||||
|
||||
public enum LockResponse { Success, Pending, Failed }
|
||||
|
||||
public interface IDelayCarryallPickup
|
||||
{
|
||||
bool TryLockForPickup(Actor self, Actor carrier);
|
||||
}
|
||||
|
||||
public class Carryable : ConditionalTrait<CarryableInfo>
|
||||
{
|
||||
ConditionManager conditionManager;
|
||||
@@ -42,6 +49,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
int carriedToken = ConditionManager.InvalidConditionToken;
|
||||
int lockedToken = ConditionManager.InvalidConditionToken;
|
||||
|
||||
Mobile mobile;
|
||||
IDelayCarryallPickup[] delayPickups;
|
||||
|
||||
public Actor Carrier { get; private set; }
|
||||
public bool Reserved { get { return state != State.Free; } }
|
||||
public CPos? Destination { get; protected set; }
|
||||
@@ -57,6 +67,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
conditionManager = self.Trait<ConditionManager>();
|
||||
mobile = self.TraitOrDefault<Mobile>();
|
||||
delayPickups = self.TraitsImplementing<IDelayCarryallPickup>().ToArray();
|
||||
|
||||
base.Created(self);
|
||||
}
|
||||
@@ -111,18 +123,28 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
|
||||
// Prepare for transport pickup
|
||||
public virtual bool LockForPickup(Actor self, Actor carrier)
|
||||
public virtual LockResponse LockForPickup(Actor self, Actor carrier)
|
||||
{
|
||||
if (state == State.Locked)
|
||||
return false;
|
||||
if (state == State.Locked && Carrier != carrier)
|
||||
return LockResponse.Failed;
|
||||
|
||||
state = State.Locked;
|
||||
Carrier = carrier;
|
||||
if (delayPickups.Any(d => d.IsTraitEnabled() && !d.TryLockForPickup(self, carrier)))
|
||||
return LockResponse.Pending;
|
||||
|
||||
if (lockedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(Info.LockedCondition))
|
||||
lockedToken = conditionManager.GrantCondition(self, Info.LockedCondition);
|
||||
if (state != State.Locked)
|
||||
{
|
||||
state = State.Locked;
|
||||
Carrier = carrier;
|
||||
|
||||
return true;
|
||||
if (lockedToken == ConditionManager.InvalidConditionToken && !string.IsNullOrEmpty(Info.LockedCondition))
|
||||
lockedToken = conditionManager.GrantCondition(self, Info.LockedCondition);
|
||||
}
|
||||
|
||||
// Make sure we are not moving and at our normal position with respect to the cell grid
|
||||
if (mobile != null && mobile.IsMovingBetweenCells)
|
||||
return LockResponse.Pending;
|
||||
|
||||
return LockResponse.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public string OrderID { get { return "DeliverUnit"; } }
|
||||
public int OrderPriority { get { return 6; } }
|
||||
public bool IsQueued { get; protected set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public CarryallDeliverUnitTargeter(AircraftInfo aircraftInfo, CarryallInfo info)
|
||||
{
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Undeploy before the actor tries to move?")]
|
||||
public readonly bool UndeployOnMove = false;
|
||||
|
||||
[Desc("Undeploy before the actor is picked up by a Carryall?")]
|
||||
public readonly bool UndeployOnPickup = false;
|
||||
|
||||
[VoiceReference]
|
||||
public readonly string Voice = "Action";
|
||||
|
||||
@@ -67,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public enum DeployState { Undeployed, Deploying, Deployed, Undeploying }
|
||||
|
||||
public class GrantConditionOnDeploy : PausableConditionalTrait<GrantConditionOnDeployInfo>, IResolveOrder, IIssueOrder,
|
||||
INotifyDeployComplete, IIssueDeployOrder, IOrderVoice, IWrapMove
|
||||
INotifyDeployComplete, IIssueDeployOrder, IOrderVoice, IWrapMove, IDelayCarryallPickup
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly bool checkTerrainType;
|
||||
@@ -137,6 +140,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return activity;
|
||||
}
|
||||
|
||||
bool IDelayCarryallPickup.TryLockForPickup(Actor self, Actor carrier)
|
||||
{
|
||||
if (!Info.UndeployOnPickup || deployState == DeployState.Undeployed || IsTraitDisabled)
|
||||
return true;
|
||||
|
||||
if (deployState == DeployState.Deployed && !IsTraitPaused)
|
||||
Undeploy();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnumerable<IOrderTargeter> IIssueOrder.Orders
|
||||
{
|
||||
get
|
||||
|
||||
@@ -40,8 +40,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
bool IOccupySpaceInfo.SharesCell { get { return false; } }
|
||||
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
// Since crates don't share cells and GetAvailableSubCell only returns SubCell.Full or SubCell.Invalid, we ignore the subCell parameter
|
||||
return GetAvailableSubCell(world, cell, ignoreActor, checkTransientActors) != SubCell.Invalid;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
protected override void TraitEnabled(Actor self)
|
||||
{
|
||||
self.World.ActorMap.UpdatePosition(self, self.OccupiesSpace);
|
||||
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
|
||||
}
|
||||
|
||||
protected override void TraitDisabled(Actor self)
|
||||
{
|
||||
self.World.ActorMap.UpdatePosition(self, self.OccupiesSpace);
|
||||
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (IsTraitDisabled || self.Owner.WinState == WinState.Lost || !self.World.Map.Contains(self.Location))
|
||||
return;
|
||||
|
||||
var r = self.World.SharedRandom.Next(1, 100);
|
||||
|
||||
if (r <= 100 - Info.SuccessRate)
|
||||
if (self.World.SharedRandom.Next(100) >= Info.SuccessRate)
|
||||
return;
|
||||
|
||||
var cp = self.CenterPosition;
|
||||
@@ -60,48 +58,47 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if ((inAir && !Info.EjectInAir) || (!inAir && !Info.EjectOnGround))
|
||||
return;
|
||||
|
||||
var pilot = self.World.CreateActor(false, Info.PilotActor.ToLowerInvariant(),
|
||||
new TypeDictionary { new OwnerInit(self.Owner), new LocationInit(self.Location) });
|
||||
|
||||
var pilotPositionable = pilot.TraitOrDefault<IPositionable>();
|
||||
var pilotCell = self.Location;
|
||||
var pilotSubCell = pilotPositionable.GetAvailableSubCell(pilotCell);
|
||||
if (pilotSubCell == SubCell.Invalid)
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (!Info.AllowUnsuitableCell)
|
||||
{
|
||||
pilot.Dispose();
|
||||
return;
|
||||
var pilotInfo = self.World.Map.Rules.Actors[Info.PilotActor.ToLowerInvariant()];
|
||||
var pilotPositionable = pilotInfo.TraitInfo<IPositionableInfo>();
|
||||
if (!pilotPositionable.CanEnterCell(self.World, null, self.Location))
|
||||
return;
|
||||
}
|
||||
|
||||
pilotSubCell = SubCell.Any;
|
||||
}
|
||||
|
||||
if (inAir)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
var td = new TypeDictionary
|
||||
{
|
||||
pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell);
|
||||
var dropPosition = pilot.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - pilot.CenterPosition.Z);
|
||||
pilotPositionable.SetVisualPosition(pilot, dropPosition);
|
||||
new OwnerInit(self.Owner),
|
||||
new LocationInit(self.Location),
|
||||
};
|
||||
|
||||
w.Add(pilot);
|
||||
});
|
||||
|
||||
Game.Sound.Play(SoundType.World, Info.ChuteSound, cp);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
// If airborne, offset the spawn location so the pilot doesn't drop on another infantry's head
|
||||
var spawnPos = cp;
|
||||
if (inAir)
|
||||
{
|
||||
w.Add(pilot);
|
||||
pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell);
|
||||
var subCell = self.World.ActorMap.FreeSubCell(self.Location);
|
||||
if (subCell != SubCell.Invalid)
|
||||
{
|
||||
td.Add(new SubCellInit(subCell));
|
||||
spawnPos = self.World.Map.CenterOfSubCell(self.Location, subCell) + new WVec(0, 0, spawnPos.Z);
|
||||
}
|
||||
}
|
||||
|
||||
td.Add(new CenterPositionInit(spawnPos));
|
||||
|
||||
var pilot = self.World.CreateActor(true, Info.PilotActor.ToLowerInvariant(), td);
|
||||
|
||||
if (!inAir)
|
||||
{
|
||||
var pilotMobile = pilot.TraitOrDefault<Mobile>();
|
||||
if (pilotMobile != null)
|
||||
pilotMobile.Nudge(pilot, pilot, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
Game.Sound.Play(SoundType.World, Info.ChuteSound, cp);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public void GiveLevels(int numLevels, bool silent = false)
|
||||
{
|
||||
if (MaxLevel == 0)
|
||||
return;
|
||||
|
||||
var newLevel = Math.Min(Level + numLevels, MaxLevel);
|
||||
GiveExperience(nextLevel[newLevel - 1].First - experience, silent);
|
||||
}
|
||||
@@ -105,6 +108,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (amount < 0)
|
||||
throw new ArgumentException("Revoking experience is not implemented.", "amount");
|
||||
|
||||
if (MaxLevel == 0)
|
||||
return;
|
||||
|
||||
experience = (experience + amount).Clamp(0, nextLevel[MaxLevel - 1].First);
|
||||
|
||||
while (Level < MaxLevel && experience >= nextLevel[Level].First)
|
||||
|
||||
@@ -130,8 +130,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
mobile = self.Trait<Mobile>();
|
||||
resLayer = self.World.WorldActor.Trait<ResourceLayer>();
|
||||
claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>();
|
||||
|
||||
self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null)));
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
@@ -141,6 +139,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
UpdateCondition(self);
|
||||
|
||||
self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null)));
|
||||
|
||||
// Note: This is queued in a FrameEndTask because otherwise the activity is dropped/overridden while moving out of a factory.
|
||||
if (Info.SearchOnCreation)
|
||||
self.World.AddFrameEndTask(w => self.QueueActivity(new FindAndDeliverResources(self)));
|
||||
@@ -379,7 +379,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public string OrderID { get { return "Harvest"; } }
|
||||
public int OrderPriority { get { return 10; } }
|
||||
public bool IsQueued { get; protected set; }
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers) { return true; }
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
bool IOccupySpaceInfo.SharesCell { get { return false; } }
|
||||
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
// IPositionable*Info*.CanEnterCell is only ever used for things like exiting production facilities,
|
||||
// all places relevant for husks check IPositionable.CanEnterCell instead, so we can safely set this to true.
|
||||
|
||||
@@ -82,7 +82,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// initialized and used by CanEnterCell
|
||||
Locomotor locomotor;
|
||||
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
/// <summary>
|
||||
/// Note: If the target <paramref name="cell"/> has any free subcell, the value of <paramref name="subCell"/> is ignored.
|
||||
/// </summary>
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
// PERF: Avoid repeated trait queries on the hot path
|
||||
if (locomotor == null)
|
||||
@@ -93,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return false;
|
||||
|
||||
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
|
||||
return locomotor.CanMoveFreelyInto(self, cell, ignoreActor, check);
|
||||
return locomotor.CanMoveFreelyInto(self, cell, subCell, ignoreActor, check);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any)
|
||||
@@ -139,12 +142,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
public class Mobile : PausableConditionalTrait<MobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, ITick,
|
||||
public class Mobile : PausableConditionalTrait<MobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, ITick, ICreationActivity,
|
||||
IFacing, IDeathActorInitModifier, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove, IActorPreviewInitModifier, INotifyBecomingIdle
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly Lazy<IEnumerable<int>> speedModifiers;
|
||||
readonly int moveIntoWorldDelay;
|
||||
|
||||
readonly bool returnToCellOnCreation;
|
||||
readonly bool returnToCellOnCreationRecalculateSubCell = true;
|
||||
readonly int creationActivityDelay;
|
||||
|
||||
#region IMove CurrentMovementTypes
|
||||
MovementType movementTypes;
|
||||
@@ -160,8 +166,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var oldValue = movementTypes;
|
||||
movementTypes = value;
|
||||
if (value != oldValue)
|
||||
{
|
||||
self.World.ActorMap.UpdateOccupiedCells(self.OccupiesSpace);
|
||||
foreach (var n in notifyMoving)
|
||||
n.MovementTypeChanged(self, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -177,6 +186,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
IWrapMove[] moveWrappers;
|
||||
bool requireForceMove;
|
||||
|
||||
public bool IsMovingBetweenCells
|
||||
{
|
||||
get { return FromCell != ToCell; }
|
||||
}
|
||||
|
||||
#region IFacing
|
||||
[Sync]
|
||||
public int Facing
|
||||
@@ -210,7 +224,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
if (FromCell == ToCell)
|
||||
return new[] { Pair.New(FromCell, FromSubCell) };
|
||||
if (CanEnterCell(ToCell))
|
||||
|
||||
// HACK: Should be fixed properly, see https://github.com/OpenRA/OpenRA/pull/17292 for an explanation
|
||||
if (Info.LocomotorInfo.SharesCell)
|
||||
return new[] { Pair.New(ToCell, ToSubCell) };
|
||||
|
||||
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
|
||||
@@ -226,7 +242,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
ToSubCell = FromSubCell = info.LocomotorInfo.SharesCell ? init.World.Map.Grid.DefaultSubCell : SubCell.FullCell;
|
||||
if (init.Contains<SubCellInit>())
|
||||
{
|
||||
FromSubCell = ToSubCell = init.Get<SubCellInit, SubCell>();
|
||||
returnToCellOnCreationRecalculateSubCell = false;
|
||||
}
|
||||
|
||||
if (init.Contains<LocationInit>())
|
||||
{
|
||||
@@ -234,14 +253,19 @@ namespace OpenRA.Mods.Common.Traits
|
||||
SetVisualPosition(self, init.World.Map.CenterOfSubCell(FromCell, FromSubCell));
|
||||
}
|
||||
|
||||
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
|
||||
Facing = oldFacing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
|
||||
|
||||
// Sets the visual position to WPos accuracy
|
||||
// Use LocationInit if you want to insert the actor into the ActorMap!
|
||||
// Sets the initial visual position
|
||||
// Unit will move into the cell grid (defined by LocationInit) as its initial activity
|
||||
if (init.Contains<CenterPositionInit>())
|
||||
SetVisualPosition(self, init.Get<CenterPositionInit, WPos>());
|
||||
{
|
||||
oldPos = init.Get<CenterPositionInit, WPos>();
|
||||
SetVisualPosition(self, oldPos);
|
||||
returnToCellOnCreation = true;
|
||||
}
|
||||
|
||||
moveIntoWorldDelay = init.Contains<MoveIntoWorldDelayInit>() ? init.Get<MoveIntoWorldDelayInit, int>() : 0;
|
||||
if (init.Contains<CreationActivityDelayInit>())
|
||||
creationActivityDelay = init.Get<CreationActivityDelayInit, int>();
|
||||
}
|
||||
|
||||
protected override void Created(Actor self)
|
||||
@@ -254,7 +278,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
|
||||
.Single(l => l.Info.Name == Info.Locomotor);
|
||||
|
||||
self.QueueActivity(MoveIntoWorld(self, moveIntoWorldDelay));
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
@@ -295,7 +318,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public void Nudge(Actor self, Actor nudger, bool force)
|
||||
{
|
||||
if (IsTraitDisabled || IsTraitPaused)
|
||||
if (IsTraitDisabled || IsTraitPaused || requireForceMove)
|
||||
return;
|
||||
|
||||
// Initial fairly braindead implementation.
|
||||
@@ -455,7 +478,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors);
|
||||
return Info.CanEnterCell(self.World, self, cell, ToSubCell, ignoreActor, checkTransientActors);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -567,27 +590,27 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return WrapMove(new Follow(self, target, minRange, maxRange, initialTargetPosition, targetLineColor));
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, int delay = 0)
|
||||
public Activity ReturnToCell(Actor self)
|
||||
{
|
||||
return new MoveIntoWorldActivity(self, delay);
|
||||
return new ReturnToCellActivity(self);
|
||||
}
|
||||
|
||||
class MoveIntoWorldActivity : Activity
|
||||
class ReturnToCellActivity : Activity
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly Mobile mobile;
|
||||
readonly bool recalculateSubCell;
|
||||
|
||||
CPos cell;
|
||||
SubCell subCell;
|
||||
WPos pos;
|
||||
int delay;
|
||||
|
||||
public MoveIntoWorldActivity(Actor self, int delay = 0)
|
||||
public ReturnToCellActivity(Actor self, int delay = 0, bool recalculateSubCell = false)
|
||||
{
|
||||
this.self = self;
|
||||
mobile = self.Trait<Mobile>();
|
||||
IsInterruptible = false;
|
||||
this.delay = delay;
|
||||
this.recalculateSubCell = recalculateSubCell;
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
@@ -603,7 +626,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
cell = mobile.ToCell;
|
||||
subCell = mobile.ToSubCell;
|
||||
|
||||
if (subCell == SubCell.Any)
|
||||
if (recalculateSubCell)
|
||||
subCell = mobile.Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
|
||||
|
||||
// TODO: solve/reduce cell is full problem
|
||||
@@ -890,13 +913,22 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
Activity ICreationActivity.GetCreationActivity()
|
||||
{
|
||||
return returnToCellOnCreation ? new ReturnToCellActivity(self, creationActivityDelay, returnToCellOnCreationRecalculateSubCell) : null;
|
||||
}
|
||||
|
||||
class MoveOrderTargeter : IOrderTargeter
|
||||
{
|
||||
readonly Mobile mobile;
|
||||
readonly LocomotorInfo locomotorInfo;
|
||||
readonly bool rejectMove;
|
||||
public bool TargetOverridesSelection(TargetModifiers modifiers)
|
||||
public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
|
||||
{
|
||||
// Always prioritise orders over selecting other peoples actors or own actors that are already selected
|
||||
if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
|
||||
return true;
|
||||
|
||||
return modifiers.HasModifier(TargetModifiers.ForceMove);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (specificCargoToken == ConditionManager.InvalidConditionToken && Info.CargoConditions.TryGetValue(cargo.Info.Name, out specificCargoCondition))
|
||||
specificCargoToken = conditionManager.GrantCondition(self, specificCargoCondition);
|
||||
}
|
||||
|
||||
// Allow scripted / initial actors to move from the unload point back into the cell grid on unload
|
||||
// This is handled by the RideTransport activity for player-loaded cargo
|
||||
if (self.IsIdle)
|
||||
{
|
||||
// IMove is not used anywhere else in this trait, there is no benefit to caching it from Created.
|
||||
var move = self.TraitOrDefault<IMove>();
|
||||
if (move != null)
|
||||
self.QueueActivity(move.ReturnToCell(self));
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyExitedCargo.OnExitedCargo(Actor self, Actor cargo)
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (string.IsNullOrEmpty(segmentType))
|
||||
segmentType = actorInfo.Name;
|
||||
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(w, targetLocation, actorInfo, buildingInfo))
|
||||
foreach (var t in BuildingUtils.GetLineBuildCells(w, targetLocation, actorInfo, buildingInfo, order.Player))
|
||||
{
|
||||
if (t.First == targetLocation)
|
||||
continue;
|
||||
|
||||
@@ -28,14 +28,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public int OrderCount;
|
||||
|
||||
public int EarnedThisMinute
|
||||
{
|
||||
get
|
||||
{
|
||||
return resources != null ? resources.Earned - earnedAtBeginningOfMinute : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int Experience
|
||||
{
|
||||
get
|
||||
@@ -44,10 +36,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
public Queue<int> EarnedSamples = new Queue<int>(100);
|
||||
int earnedAtBeginningOfMinute;
|
||||
// Low resolution (every 30 seconds) record of earnings, covering the entire game
|
||||
public List<int> IncomeSamples = new List<int>(100);
|
||||
public int Income;
|
||||
public int DisplayIncome;
|
||||
|
||||
public Queue<int> ArmySamples = new Queue<int>(100);
|
||||
public List<int> ArmySamples = new List<int>(100);
|
||||
|
||||
public int KillsCost;
|
||||
public int DeathsCost;
|
||||
@@ -59,40 +53,63 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public int BuildingsDead;
|
||||
|
||||
public int ArmyValue;
|
||||
|
||||
// High resolution (every second) record of earnings, limited to the last minute
|
||||
readonly Queue<int> earnedSeconds = new Queue<int>(60);
|
||||
|
||||
int lastIncome;
|
||||
int lastIncomeTick;
|
||||
int ticks;
|
||||
int replayTimestep;
|
||||
|
||||
bool armyGraphDisabled;
|
||||
bool incomeGraphDisabled;
|
||||
|
||||
public PlayerStatistics(Actor self) { }
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
resources = self.TraitOrDefault<PlayerResources>();
|
||||
experience = self.TraitOrDefault<PlayerExperience>();
|
||||
}
|
||||
|
||||
void UpdateEarnedThisMinute()
|
||||
{
|
||||
EarnedSamples.Enqueue(EarnedThisMinute);
|
||||
earnedAtBeginningOfMinute = resources != null ? resources.Earned : 0;
|
||||
if (EarnedSamples.Count > 100)
|
||||
EarnedSamples.Dequeue();
|
||||
}
|
||||
|
||||
void UpdateArmyThisMinute()
|
||||
{
|
||||
ArmySamples.Enqueue(ArmyValue);
|
||||
if (ArmySamples.Count > 100)
|
||||
ArmySamples.Dequeue();
|
||||
incomeGraphDisabled = resources == null;
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
var timestep = self.World.IsReplay ? replayTimestep : self.World.Timestep;
|
||||
ticks++;
|
||||
|
||||
if (timestep * self.World.WorldTick % 60000 == 0)
|
||||
var timestep = self.World.IsReplay ? replayTimestep : self.World.Timestep;
|
||||
if (ticks * timestep >= 30000)
|
||||
{
|
||||
UpdateEarnedThisMinute();
|
||||
UpdateArmyThisMinute();
|
||||
ticks = 0;
|
||||
|
||||
if (!armyGraphDisabled && (ArmyValue != 0 || self.Owner.WinState == WinState.Undefined))
|
||||
ArmySamples.Add(ArmyValue);
|
||||
else
|
||||
armyGraphDisabled = true;
|
||||
|
||||
if (!incomeGraphDisabled && (Income != 0 || self.Owner.WinState == WinState.Undefined))
|
||||
IncomeSamples.Add(Income);
|
||||
else
|
||||
incomeGraphDisabled = true;
|
||||
}
|
||||
|
||||
if (resources == null)
|
||||
return;
|
||||
|
||||
var tickDelta = self.World.WorldTick - lastIncomeTick;
|
||||
if (tickDelta * timestep >= 1000)
|
||||
{
|
||||
lastIncomeTick = self.World.WorldTick;
|
||||
|
||||
var lastEarned = earnedSeconds.Count > 59 ? earnedSeconds.Dequeue() : 0;
|
||||
lastIncome = DisplayIncome = Income;
|
||||
Income = resources.Earned - lastEarned;
|
||||
earnedSeconds.Enqueue(resources.Earned);
|
||||
}
|
||||
else
|
||||
DisplayIncome = int2.Lerp(lastIncome, Income, tickDelta * timestep, 1000);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
@@ -126,8 +143,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (w.IsReplay)
|
||||
replayTimestep = w.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
|
||||
|
||||
UpdateEarnedThisMinute();
|
||||
UpdateArmyThisMinute();
|
||||
if (!armyGraphDisabled)
|
||||
ArmySamples.Add(ArmyValue);
|
||||
|
||||
if (!incomeGraphDisabled)
|
||||
IncomeSamples.Add(Income);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
td.Add(new CenterPositionInit(spawn));
|
||||
td.Add(new FacingInit(initialFacing));
|
||||
if (exitinfo != null)
|
||||
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
|
||||
td.Add(new CreationActivityDelayInit(exitinfo.ExitDelay));
|
||||
}
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
@@ -130,7 +130,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.NotifyBlocker(self.Location + s.ExitCell);
|
||||
|
||||
return mobileInfo == null ||
|
||||
mobileInfo.CanEnterCell(self.World, self, self.Location + s.ExitCell, self);
|
||||
mobileInfo.CanEnterCell(self.World, self, self.Location + s.ExitCell, ignoreActor: self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
td.Add(new LocationInit(exit));
|
||||
td.Add(new CenterPositionInit(spawn));
|
||||
td.Add(new FacingInit(initialFacing));
|
||||
td.Add(new MoveIntoWorldDelayInit(exitinfo.ExitDelay));
|
||||
td.Add(new CreationActivityDelayInit(exitinfo.ExitDelay));
|
||||
}
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
|
||||
@@ -29,6 +29,13 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
[SequenceReference("Image")]
|
||||
public readonly string EndSequence = "end";
|
||||
|
||||
[PaletteReference("IsPlayerPalette")]
|
||||
[Desc("Custom palette name.")]
|
||||
public readonly string Palette = null;
|
||||
|
||||
[Desc("Custom palette is a player palette BaseName.")]
|
||||
public readonly bool IsPlayerPalette = false;
|
||||
|
||||
[Desc("Damage types that this should be used for (defined on the warheads).",
|
||||
"Leave empty to disable all filtering.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
|
||||
@@ -54,7 +61,8 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
var rs = self.Trait<RenderSprites>();
|
||||
|
||||
anim = new Animation(self.World, info.Image);
|
||||
rs.Add(new AnimationWithOffset(anim, null, () => !isSmoking));
|
||||
rs.Add(new AnimationWithOffset(anim, null, () => !isSmoking),
|
||||
info.Palette, info.IsPlayerPalette);
|
||||
}
|
||||
|
||||
void INotifyDamage.Damaged(Actor self, AttackInfo e)
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace OpenRA.Mods.Common.Traits.Render
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
rsm = self.TraitOrDefault<IRenderInfantrySequenceModifier>();
|
||||
idleDelay = self.World.SharedRandom.Next(Info.MinIdleDelay, Info.MaxIdleDelay);
|
||||
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down)
|
||||
{
|
||||
if (!activated)
|
||||
if (!activated && world.Map.Contains(cell))
|
||||
{
|
||||
targetCell = cell;
|
||||
targetLocation = mi.Location;
|
||||
@@ -119,7 +119,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
IEnumerable<IRenderable> IOrderGenerator.RenderAboveShroud(WorldRenderer wr, World world) { yield break; }
|
||||
|
||||
string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { return cursor; }
|
||||
string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
|
||||
{
|
||||
return world.Map.Contains(cell) ? cursor : "generic-blocked";
|
||||
}
|
||||
|
||||
bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; }
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public IEnumerable<SupportPowerInstance> GetPowersForActor(Actor a)
|
||||
{
|
||||
if (a.Owner != Self.Owner || !a.Info.HasTraitInfo<SupportPowerInfo>())
|
||||
if (Powers.Count == 0 || a.Owner != Self.Owner || !a.Info.HasTraitInfo<SupportPowerInfo>())
|
||||
return NoInstances;
|
||||
|
||||
return a.TraitsImplementing<SupportPower>()
|
||||
@@ -162,7 +162,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public int RemainingTime;
|
||||
public int TotalTime;
|
||||
public bool Active { get; private set; }
|
||||
public bool Disabled { get { return (!prereqsAvailable && !manager.DevMode.AllTech) || !instancesEnabled || oneShotFired; } }
|
||||
public bool Disabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return manager.Self.Owner.WinState == WinState.Lost ||
|
||||
(!prereqsAvailable && !manager.DevMode.AllTech) ||
|
||||
!instancesEnabled ||
|
||||
oneShotFired;
|
||||
}
|
||||
}
|
||||
|
||||
public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } }
|
||||
public bool Ready { get { return Active && RemainingTime == 0; } }
|
||||
|
||||
20
OpenRA.Mods.Common/Traits/UpdatesDerrickCount.cs
Normal file
20
OpenRA.Mods.Common/Traits/UpdatesDerrickCount.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2019 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Tag trait for updating the 'Oil Derrick' count economy statistic.")]
|
||||
public class UpdatesDerrickCountInfo : TraitInfo<UpdatesDerrickCount> { }
|
||||
|
||||
public class UpdatesDerrickCount { }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user