Compare commits
125 Commits
devtest-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
485103d09b | ||
|
|
861eb0d8a7 | ||
|
|
6bc9b547ed | ||
|
|
37c2c2c341 | ||
|
|
2541481e7c | ||
|
|
30c663f0c7 | ||
|
|
404b1fad7c | ||
|
|
77b91420a9 | ||
|
|
cdf74cf6d6 | ||
|
|
d5b0a989d4 | ||
|
|
04c51fce07 | ||
|
|
c41d2aee55 | ||
|
|
4944f0524f | ||
|
|
33006e6ba5 | ||
|
|
ceff7ae207 | ||
|
|
f0156083a3 | ||
|
|
3a94c74c44 | ||
|
|
e6a799bdb0 | ||
|
|
e3b49bc02c | ||
|
|
29ce5f183c | ||
|
|
091a38937a | ||
|
|
bfea2b3b87 | ||
|
|
8ca8d32232 | ||
|
|
d3c5aa53be | ||
|
|
adfb905f97 | ||
|
|
c2ead21bf0 | ||
|
|
aaaa5c78de | ||
|
|
b5105e5e50 | ||
|
|
b18110012b | ||
|
|
38bf3eb30d | ||
|
|
7ad4ba7adb | ||
|
|
9d624579fc | ||
|
|
46f0978789 | ||
|
|
880f635e83 | ||
|
|
3bfb43a305 | ||
|
|
4ca4aebf50 | ||
|
|
8df18da419 | ||
|
|
98b4d6e828 | ||
|
|
5f1d4a4ff1 | ||
|
|
dc722b0a0c | ||
|
|
142e6776af | ||
|
|
baa786a4fd | ||
|
|
75ccdfce7a | ||
|
|
1672041dd4 | ||
|
|
f63bd278e1 | ||
|
|
0a2731fc72 | ||
|
|
c304a0682e | ||
|
|
1372b5dd85 | ||
|
|
1f861ce35c | ||
|
|
fce4afd448 | ||
|
|
210de9440c | ||
|
|
d3f1f39635 | ||
|
|
789676919c | ||
|
|
9cafc1d7b4 | ||
|
|
76497eaea6 | ||
|
|
93cef0241d | ||
|
|
a641ac08f8 | ||
|
|
2d0ee684b8 | ||
|
|
f83d680197 | ||
|
|
b54e45f4a4 | ||
|
|
55b8f42ac5 | ||
|
|
8f16a792cc | ||
|
|
66282cbd87 | ||
|
|
96ce888d97 | ||
|
|
0c7a1ec3bf | ||
|
|
5c8aa7f0f7 | ||
|
|
45fa0dc41e | ||
|
|
3cb807329a | ||
|
|
6215e4358a | ||
|
|
caddb39e79 | ||
|
|
fff1d0bfa9 | ||
|
|
f26a7d7a3f | ||
|
|
b9d1a83158 | ||
|
|
b568023489 | ||
|
|
f6bc07894b | ||
|
|
da451896e8 | ||
|
|
7d26e78cb3 | ||
|
|
4c28776a93 | ||
|
|
6a8f4301fb | ||
|
|
6ddbab929c | ||
|
|
42779c72cf | ||
|
|
35acae244c | ||
|
|
965de05b0a | ||
|
|
75f5076ac3 | ||
|
|
70cddb72f2 | ||
|
|
19da3bc4c2 | ||
|
|
f950a61886 | ||
|
|
d3fbb83877 | ||
|
|
8e85431266 | ||
|
|
ecd4d996c7 | ||
|
|
378fecd2db | ||
|
|
67d3010d78 | ||
|
|
eee71368a8 | ||
|
|
527b4da3f2 | ||
|
|
7ae2822b6e | ||
|
|
8fdddd238b | ||
|
|
8dfcc716c8 | ||
|
|
892b60d0eb | ||
|
|
987f2a26fa | ||
|
|
cc2912b993 | ||
|
|
76b15d7e37 | ||
|
|
1a0b975d31 | ||
|
|
b55a1b86db | ||
|
|
7840f92f39 | ||
|
|
c13a0e2c9e | ||
|
|
edd92c7d3b | ||
|
|
beb9cae9ca | ||
|
|
1ecb921364 | ||
|
|
ffc7314a7f | ||
|
|
a4420bad23 | ||
|
|
b8c90ef506 | ||
|
|
f30b659b3f | ||
|
|
35ee5b807a | ||
|
|
7f21a868bd | ||
|
|
1eb8c395e7 | ||
|
|
d2a8a8f19a | ||
|
|
b826c5e22a | ||
|
|
65f5b763e9 | ||
|
|
b167424654 | ||
|
|
0777fe534d | ||
|
|
c3a8065b99 | ||
|
|
59dcab4db0 | ||
|
|
4631cbfc2a | ||
|
|
315f1188ed | ||
|
|
eae3f297f3 |
@@ -336,6 +336,8 @@ namespace OpenRA
|
||||
foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
|
||||
t.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
World.Selection.OnOwnerChanged(this, oldOwner, newOwner);
|
||||
|
||||
if (wasInWorld)
|
||||
World.Add(this);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace OpenRA.GameRules
|
||||
public readonly string Filename;
|
||||
public readonly string Title;
|
||||
public readonly bool Hidden;
|
||||
public readonly float VolumeModifier = 1f;
|
||||
|
||||
public int Length { get; private set; } // seconds
|
||||
public bool Exists { get; private set; }
|
||||
@@ -31,6 +32,9 @@ namespace OpenRA.GameRules
|
||||
if (nd.ContainsKey("Hidden"))
|
||||
bool.TryParse(nd["Hidden"].Value, out Hidden);
|
||||
|
||||
if (nd.ContainsKey("VolumeModifier"))
|
||||
VolumeModifier = FieldLoader.GetValue<float>("VolumeModifier", nd["VolumeModifier"].Value);
|
||||
|
||||
var ext = nd.ContainsKey("Extension") ? nd["Extension"].Value : "aud";
|
||||
Filename = (nd.ContainsKey("Filename") ? nd["Filename"].Value : key) + "." + ext;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA.GameRules
|
||||
{
|
||||
FieldLoader.Load(this, y);
|
||||
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(0, a.Value)));
|
||||
VoicePools = Exts.Lazy(() => Voices.ToDictionary(a => a.Key, a => new SoundPool(1f, a.Value)));
|
||||
NotificationsPools = Exts.Lazy(() => ParseSoundPool(y, "Notifications"));
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ namespace OpenRA.GameRules
|
||||
var classifiction = y.Nodes.First(x => x.Key == key);
|
||||
foreach (var t in classifiction.Value.Nodes)
|
||||
{
|
||||
var rateLimit = 0;
|
||||
var rateLimitNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "RateLimit");
|
||||
if (rateLimitNode != null)
|
||||
rateLimit = FieldLoader.GetValue<int>(rateLimitNode.Key, rateLimitNode.Value.Value);
|
||||
var volumeModifier = 1f;
|
||||
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == "VolumeModifier");
|
||||
if (volumeModifierNode != null)
|
||||
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
|
||||
|
||||
var names = FieldLoader.GetValue<string[]>(t.Key, t.Value.Value);
|
||||
var sp = new SoundPool(rateLimit, names);
|
||||
var sp = new SoundPool(volumeModifier, names);
|
||||
ret.Add(t.Key, sp);
|
||||
}
|
||||
|
||||
@@ -59,15 +59,14 @@ namespace OpenRA.GameRules
|
||||
|
||||
public class SoundPool
|
||||
{
|
||||
public readonly float VolumeModifier;
|
||||
readonly string[] clips;
|
||||
readonly int rateLimit;
|
||||
readonly List<string> liveclips = new List<string>();
|
||||
long lastPlayed = 0;
|
||||
|
||||
public SoundPool(int rateLimit, params string[] clips)
|
||||
public SoundPool(float volumeModifier, params string[] clips)
|
||||
{
|
||||
VolumeModifier = volumeModifier;
|
||||
this.clips = clips;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
public string GetNext()
|
||||
@@ -79,16 +78,6 @@ namespace OpenRA.GameRules
|
||||
if (liveclips.Count == 0)
|
||||
return null;
|
||||
|
||||
// Perform rate limiting if necessary
|
||||
if (rateLimit != 0)
|
||||
{
|
||||
var now = Game.RunTime;
|
||||
if (lastPlayed != 0 && now < lastPlayed + rateLimit)
|
||||
return null;
|
||||
|
||||
lastPlayed = now;
|
||||
}
|
||||
|
||||
var i = Game.CosmeticRandom.Next(liveclips.Count);
|
||||
var s = liveclips[i];
|
||||
liveclips.RemoveAt(i);
|
||||
|
||||
@@ -21,7 +21,14 @@ namespace OpenRA
|
||||
{
|
||||
public int Hash { get; private set; }
|
||||
public IEnumerable<Actor> Actors { get { return actors; } }
|
||||
|
||||
readonly HashSet<Actor> actors = new HashSet<Actor>();
|
||||
readonly INotifySelection[] worldNotifySelection;
|
||||
|
||||
internal Selection(IEnumerable<INotifySelection> worldNotifySelection)
|
||||
{
|
||||
this.worldNotifySelection = worldNotifySelection.ToArray();
|
||||
}
|
||||
|
||||
void UpdateHash()
|
||||
{
|
||||
@@ -31,17 +38,41 @@ namespace OpenRA
|
||||
Hash += 1;
|
||||
}
|
||||
|
||||
public void Add(World w, Actor a)
|
||||
public void Add(Actor a)
|
||||
{
|
||||
actors.Add(a);
|
||||
UpdateHash();
|
||||
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
foreach (var ns in w.WorldActor.TraitsImplementing<INotifySelection>())
|
||||
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
|
||||
public void Remove(Actor a)
|
||||
{
|
||||
if (actors.Remove(a))
|
||||
{
|
||||
UpdateHash();
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnOwnerChanged(Actor a, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (!actors.Contains(a))
|
||||
return;
|
||||
|
||||
// Remove the actor from the original owners selection
|
||||
// Call UpdateHash directly for everyone else so watchers can account for the owner change if needed
|
||||
if (oldOwner == a.World.LocalPlayer)
|
||||
Remove(a);
|
||||
else
|
||||
UpdateHash();
|
||||
}
|
||||
|
||||
public bool Contains(Actor a)
|
||||
{
|
||||
return actors.Contains(a);
|
||||
@@ -77,7 +108,7 @@ namespace OpenRA
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
|
||||
foreach (var ns in world.WorldActor.TraitsImplementing<INotifySelection>())
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
|
||||
if (world.IsGameOver)
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace OpenRA
|
||||
|
||||
Func<ISoundFormat, ISound> stream = soundFormat => soundEngine.Play2DStream(
|
||||
soundFormat.GetPCMInputStream(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate,
|
||||
false, true, WPos.Zero, MusicVolume);
|
||||
false, true, WPos.Zero, MusicVolume * m.VolumeModifier);
|
||||
music = LoadSound(m.Filename, stream);
|
||||
|
||||
if (music == null)
|
||||
@@ -352,7 +352,7 @@ namespace OpenRA
|
||||
|
||||
var id = voicedActor != null ? voicedActor.ActorID : 0;
|
||||
|
||||
string clip;
|
||||
SoundPool pool;
|
||||
var suffix = rules.DefaultVariant;
|
||||
var prefix = rules.DefaultPrefix;
|
||||
|
||||
@@ -361,16 +361,17 @@ namespace OpenRA
|
||||
if (!rules.VoicePools.Value.ContainsKey(definition))
|
||||
throw new InvalidOperationException("Can't find {0} in voice pool.".F(definition));
|
||||
|
||||
clip = rules.VoicePools.Value[definition].GetNext();
|
||||
pool = rules.VoicePools.Value[definition];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!rules.NotificationsPools.Value.ContainsKey(definition))
|
||||
throw new InvalidOperationException("Can't find {0} in notification pool.".F(definition));
|
||||
|
||||
clip = rules.NotificationsPools.Value[definition].GetNext();
|
||||
pool = rules.NotificationsPools.Value[definition];
|
||||
}
|
||||
|
||||
var clip = pool.GetNext();
|
||||
if (string.IsNullOrEmpty(clip))
|
||||
return false;
|
||||
|
||||
@@ -388,7 +389,7 @@ namespace OpenRA
|
||||
{
|
||||
var sound = soundEngine.Play2D(sounds[name],
|
||||
false, relative, pos,
|
||||
InternalSoundVolume * volumeModifier, attenuateVolume);
|
||||
InternalSoundVolume * volumeModifier * pool.VolumeModifier, attenuateVolume);
|
||||
if (id != 0)
|
||||
{
|
||||
if (currentSounds.ContainsKey(id))
|
||||
|
||||
@@ -18,6 +18,11 @@ using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public interface ICreatesFrozenActors
|
||||
{
|
||||
void OnVisibilityChanged(FrozenActor frozen);
|
||||
}
|
||||
|
||||
[Desc("Required for FrozenUnderFog to work. Attach this to the player actor.")]
|
||||
public class FrozenActorLayerInfo : Requires<ShroudInfo>, ITraitInfo
|
||||
{
|
||||
@@ -32,11 +37,13 @@ namespace OpenRA.Traits
|
||||
public readonly PPos[] Footprint;
|
||||
public readonly WPos CenterPosition;
|
||||
readonly Actor actor;
|
||||
readonly ICreatesFrozenActors frozenTrait;
|
||||
readonly Player viewer;
|
||||
readonly Shroud shroud;
|
||||
|
||||
public Player Owner { get; private set; }
|
||||
public BitSet<TargetableType> TargetTypes { get; private set; }
|
||||
public WPos[] TargetablePositions { get; private set; }
|
||||
|
||||
public ITooltipInfo TooltipInfo { get; private set; }
|
||||
public Player TooltipOwner { get; private set; }
|
||||
@@ -70,9 +77,10 @@ namespace OpenRA.Traits
|
||||
|
||||
int flashTicks;
|
||||
|
||||
public FrozenActor(Actor actor, PPos[] footprint, Player viewer, bool startsRevealed)
|
||||
public FrozenActor(Actor actor, ICreatesFrozenActors frozenTrait, PPos[] footprint, Player viewer, bool startsRevealed)
|
||||
{
|
||||
this.actor = actor;
|
||||
this.frozenTrait = frozenTrait;
|
||||
this.viewer = viewer;
|
||||
shroud = viewer.Shroud;
|
||||
NeedRenderables = startsRevealed;
|
||||
@@ -111,6 +119,7 @@ namespace OpenRA.Traits
|
||||
{
|
||||
Owner = actor.Owner;
|
||||
TargetTypes = actor.GetEnabledTargetTypes();
|
||||
TargetablePositions = actor.GetTargetablePositions().ToArray();
|
||||
Hidden = !actor.CanBeViewedByPlayer(viewer);
|
||||
|
||||
if (health != null)
|
||||
@@ -153,6 +162,11 @@ namespace OpenRA.Traits
|
||||
Shrouded = false;
|
||||
}
|
||||
|
||||
// Force the backing trait to update so other actors can't
|
||||
// query inconsistent state (both hidden or both visible)
|
||||
if (Visible != wasVisible)
|
||||
frozenTrait.OnVisibilityChanged(this);
|
||||
|
||||
NeedRenderables |= Visible && !wasVisible;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,42 +19,74 @@ namespace OpenRA.Traits
|
||||
public struct Target
|
||||
{
|
||||
public static readonly Target[] None = { };
|
||||
public static readonly Target Invalid = new Target { type = TargetType.Invalid };
|
||||
public static readonly Target Invalid = new Target();
|
||||
|
||||
TargetType type;
|
||||
Actor actor;
|
||||
FrozenActor frozen;
|
||||
WPos pos;
|
||||
CPos? cell;
|
||||
SubCell? subCell;
|
||||
int generation;
|
||||
readonly TargetType type;
|
||||
readonly Actor actor;
|
||||
readonly FrozenActor frozen;
|
||||
readonly WPos terrainCenterPosition;
|
||||
readonly WPos[] terrainPositions;
|
||||
readonly CPos? cell;
|
||||
readonly SubCell? subCell;
|
||||
readonly int generation;
|
||||
|
||||
public static Target FromPos(WPos p) { return new Target { pos = p, type = TargetType.Terrain }; }
|
||||
public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell)
|
||||
Target(WPos terrainCenterPosition, WPos[] terrainPositions = null)
|
||||
{
|
||||
return new Target
|
||||
{
|
||||
pos = w.Map.CenterOfSubCell(c, subCell),
|
||||
cell = c,
|
||||
subCell = subCell,
|
||||
type = TargetType.Terrain
|
||||
};
|
||||
type = TargetType.Terrain;
|
||||
this.terrainCenterPosition = terrainCenterPosition;
|
||||
this.terrainPositions = terrainPositions ?? new[] { terrainCenterPosition };
|
||||
|
||||
actor = null;
|
||||
frozen = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromActor(Actor a)
|
||||
Target(World w, CPos c, SubCell subCell)
|
||||
{
|
||||
if (a == null)
|
||||
return Invalid;
|
||||
type = TargetType.Terrain;
|
||||
terrainCenterPosition = w.Map.CenterOfSubCell(c, subCell);
|
||||
terrainPositions = new[] { terrainCenterPosition };
|
||||
cell = c;
|
||||
this.subCell = subCell;
|
||||
|
||||
return new Target
|
||||
{
|
||||
actor = a,
|
||||
type = TargetType.Actor,
|
||||
generation = a.Generation,
|
||||
};
|
||||
actor = null;
|
||||
frozen = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromFrozenActor(FrozenActor a) { return new Target { frozen = a, type = TargetType.FrozenActor }; }
|
||||
Target(Actor a)
|
||||
{
|
||||
type = TargetType.Actor;
|
||||
actor = a;
|
||||
generation = a.Generation;
|
||||
|
||||
terrainCenterPosition = WPos.Zero;
|
||||
terrainPositions = null;
|
||||
frozen = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
}
|
||||
|
||||
Target(FrozenActor fa)
|
||||
{
|
||||
type = TargetType.FrozenActor;
|
||||
frozen = fa;
|
||||
|
||||
terrainCenterPosition = WPos.Zero;
|
||||
terrainPositions = null;
|
||||
actor = null;
|
||||
cell = null;
|
||||
subCell = null;
|
||||
generation = 0;
|
||||
}
|
||||
|
||||
public static Target FromPos(WPos p) { return new Target(p); }
|
||||
public static Target FromTargetPositions(Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); }
|
||||
public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target(w, c, subCell); }
|
||||
public static Target FromActor(Actor a) { return a != null ? new Target(a) : Invalid; }
|
||||
public static Target FromFrozenActor(FrozenActor fa) { return new Target(fa); }
|
||||
|
||||
public Actor Actor { get { return actor; } }
|
||||
public FrozenActor FrozenActor { get { return frozen; } }
|
||||
@@ -134,7 +166,7 @@ namespace OpenRA.Traits
|
||||
case TargetType.FrozenActor:
|
||||
return frozen.CenterPosition;
|
||||
case TargetType.Terrain:
|
||||
return pos;
|
||||
return terrainCenterPosition;
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
throw new InvalidOperationException("Attempting to query the position of an invalid Target");
|
||||
@@ -151,14 +183,11 @@ namespace OpenRA.Traits
|
||||
switch (Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
if (!actor.Targetables.Any(Exts.IsTraitEnabled))
|
||||
return new[] { actor.CenterPosition };
|
||||
|
||||
return actor.GetTargetablePositions();
|
||||
case TargetType.FrozenActor:
|
||||
return new[] { frozen.CenterPosition };
|
||||
return frozen.TargetablePositions;
|
||||
case TargetType.Terrain:
|
||||
return new[] { pos };
|
||||
return terrainPositions;
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
return NoPositions;
|
||||
@@ -186,7 +215,7 @@ namespace OpenRA.Traits
|
||||
return frozen.ToString();
|
||||
|
||||
case TargetType.Terrain:
|
||||
return pos.ToString();
|
||||
return terrainCenterPosition.ToString();
|
||||
|
||||
default:
|
||||
case TargetType.Invalid:
|
||||
@@ -199,6 +228,6 @@ namespace OpenRA.Traits
|
||||
internal Actor SerializableActor { get { return actor; } }
|
||||
internal CPos? SerializableCell { get { return cell; } }
|
||||
internal SubCell? SerializableSubCell { get { return subCell; } }
|
||||
internal WPos SerializablePos { get { return pos; } }
|
||||
internal WPos SerializablePos { get { return terrainCenterPosition; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Selection Selection = new Selection();
|
||||
public readonly Selection Selection;
|
||||
|
||||
public void CancelInputMode() { OrderGenerator = new UnitOrderGenerator(); }
|
||||
|
||||
@@ -179,6 +179,8 @@ namespace OpenRA
|
||||
ActorMap = WorldActor.Trait<IActorMap>();
|
||||
ScreenMap = WorldActor.Trait<ScreenMap>();
|
||||
|
||||
Selection = new Selection(WorldActor.TraitsImplementing<INotifySelection>());
|
||||
|
||||
// Add players
|
||||
foreach (var cmp in WorldActor.TraitsImplementing<ICreatePlayers>())
|
||||
cmp.CreatePlayers(this);
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -20,32 +21,49 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
class Infiltrate : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly Infiltrates infiltrates;
|
||||
readonly INotifyInfiltration[] notifiers;
|
||||
Actor enterActor;
|
||||
|
||||
public Infiltrate(Actor self, Actor target, Infiltrates infiltrate)
|
||||
: base(self, target, infiltrate.Info.EnterBehaviour)
|
||||
public Infiltrate(Actor self, Target target, Infiltrates infiltrates)
|
||||
: base(self, target, Color.Red)
|
||||
{
|
||||
this.target = target;
|
||||
infiltrates = infiltrate;
|
||||
this.infiltrates = infiltrates;
|
||||
notifiers = self.TraitsImplementing<INotifyInfiltration>().ToArray();
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void TickInner(Actor self, Target target, bool targetIsDeadOrHiddenActor)
|
||||
{
|
||||
if (target.IsDead)
|
||||
return;
|
||||
if (infiltrates.IsTraitDisabled)
|
||||
Cancel(self, true);
|
||||
}
|
||||
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
if (!infiltrates.Info.ValidStances.HasStance(stance))
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure we can still demolish the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (!infiltrates.CanInfiltrateTarget(self, Target.FromActor(targetActor)))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
enterActor = targetActor;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor || !infiltrates.CanInfiltrateTarget(self, Target.FromActor(targetActor)))
|
||||
return;
|
||||
|
||||
foreach (var ini in notifiers)
|
||||
ini.Infiltrating(self);
|
||||
|
||||
foreach (var t in target.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(target, self, infiltrates.Info.Types);
|
||||
foreach (var t in targetActor.TraitsImplementing<INotifyInfiltrated>())
|
||||
t.Infiltrated(targetActor, self, infiltrates.Info.Types);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null)
|
||||
@@ -54,14 +72,11 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
if (!string.IsNullOrEmpty(infiltrates.Info.Notification))
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech",
|
||||
infiltrates.Info.Notification, self.Owner.Faction.InternalName);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (infiltrates.IsTraitDisabled)
|
||||
Cancel(self);
|
||||
|
||||
return base.Tick(self);
|
||||
if (infiltrates.Info.EnterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
else if (infiltrates.Info.EnterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Cnc.Traits;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -21,13 +23,17 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
{
|
||||
public class LeapAttack : Activity
|
||||
{
|
||||
readonly Target target;
|
||||
readonly AttackLeapInfo info;
|
||||
readonly AttackLeap attack;
|
||||
readonly Mobile mobile, targetMobile;
|
||||
readonly EdibleByLeap edible;
|
||||
readonly Mobile mobile;
|
||||
readonly bool allowMovement;
|
||||
readonly IFacing facing;
|
||||
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
WDist lastVisibleMinRange;
|
||||
WDist lastVisibleMaxRange;
|
||||
Activity inner;
|
||||
|
||||
public LeapAttack(Actor self, Target target, bool allowMovement, AttackLeap attack, AttackLeapInfo info)
|
||||
{
|
||||
@@ -35,61 +41,99 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
this.info = info;
|
||||
this.attack = attack;
|
||||
this.allowMovement = allowMovement;
|
||||
|
||||
mobile = self.Trait<Mobile>();
|
||||
facing = self.TraitOrDefault<IFacing>();
|
||||
|
||||
if (target.Type == TargetType.Actor)
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
targetMobile = target.Actor.TraitOrDefault<Mobile>();
|
||||
edible = target.Actor.TraitOrDefault<EdibleByLeap>();
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
attack.IsAiming = true;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || edible == null)
|
||||
return NextActivity;
|
||||
|
||||
// Run this even if the target became invalid to avoid visual glitches
|
||||
if (ChildActivity != null)
|
||||
if (inner != null)
|
||||
{
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
inner = ActivityUtils.RunActivity(self, inner);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (target.Type != TargetType.Actor || !edible.CanLeap(self) || !target.IsValidFor(self) || !attack.HasAnyValidWeapons(target))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var minRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
var maxRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
if (!target.IsInRange(self.CenterPosition, maxRange) || target.IsInRange(self.CenterPosition, minRange))
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
if (!allowMovement)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
if (!checkTarget.IsInRange(pos, lastVisibleMaxRange) || checkTarget.IsInRange(pos, lastVisibleMinRange))
|
||||
{
|
||||
if (!allowMovement || lastVisibleMaxRange == WDist.Zero || lastVisibleMaxRange < lastVisibleMinRange)
|
||||
return NextActivity;
|
||||
|
||||
QueueChild(new MoveWithinRange(self, target, minRange, maxRange));
|
||||
inner = mobile.MoveWithinRange(target, lastVisibleMinRange, lastVisibleMaxRange, checkTarget.CenterPosition, Color.Red);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Ready to leap, but target isn't visible
|
||||
if (targetIsHiddenActor || target.Type != TargetType.Actor)
|
||||
return NextActivity;
|
||||
|
||||
// Target is not valid
|
||||
if (!target.IsValidFor(self) || !attack.HasAnyValidWeapons(target))
|
||||
return NextActivity;
|
||||
|
||||
var edible = target.Actor.TraitOrDefault<EdibleByLeap>();
|
||||
if (edible == null || !edible.CanLeap(self))
|
||||
return NextActivity;
|
||||
|
||||
// Can't leap yet
|
||||
if (attack.Armaments.All(a => a.IsReloading))
|
||||
return this;
|
||||
|
||||
// Use CenterOfSubCell with ToSubCell instead of target.Centerposition
|
||||
// to avoid continuous facing adjustments as the target moves
|
||||
var targetMobile = target.Actor.TraitOrDefault<Mobile>();
|
||||
var targetSubcell = targetMobile != null ? targetMobile.ToSubCell : SubCell.Any;
|
||||
|
||||
var destination = self.World.Map.CenterOfSubCell(target.Actor.Location, targetSubcell);
|
||||
var origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell);
|
||||
var desiredFacing = (destination - origin).Yaw.Facing;
|
||||
if (facing != null && facing.Facing != desiredFacing)
|
||||
if (mobile.Facing != desiredFacing)
|
||||
{
|
||||
QueueChild(new Turn(self, desiredFacing));
|
||||
inner = new Turn(self, desiredFacing);
|
||||
return this;
|
||||
}
|
||||
|
||||
QueueChild(new Leap(self, target, mobile, targetMobile, info.Speed.Length, attack, edible));
|
||||
inner = new Leap(self, target, mobile, targetMobile, info.Speed.Length, attack, edible);
|
||||
|
||||
// Re-queue the child activities to kill the target if it didn't die in one go
|
||||
return this;
|
||||
@@ -98,7 +142,14 @@ namespace OpenRA.Mods.Cnc.Activities
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
attack.IsAiming = false;
|
||||
base.OnLastRun(self);
|
||||
}
|
||||
|
||||
public override bool Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!IsCanceled && inner != null && !inner.Cancel(self))
|
||||
return false;
|
||||
|
||||
return base.Cancel(self, keepQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Cnc.Scripting
|
||||
if (infiltrates == null)
|
||||
throw new LuaException("{0} tried to infiltrate invalid target {1}!".F(Self, target));
|
||||
|
||||
Self.QueueActivity(new Infiltrate(Self, target, infiltrates));
|
||||
Self.QueueActivity(new Infiltrate(Self, Target.FromActor(target), infiltrates));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
// Check that AttackTDGunboatTurreted hasn't cancelled the target by modifying attack.Target
|
||||
// Having both this and AttackTDGunboatTurreted modify that field is a horrible hack.
|
||||
if (hasTicked && attack.Target.Type == TargetType.Invalid)
|
||||
if (hasTicked && attack.requestedTarget.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
|
||||
attack.Target = target;
|
||||
attack.requestedTarget = target;
|
||||
hasTicked = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
nt.AfterTransform(a);
|
||||
|
||||
if (selected)
|
||||
self.World.Selection.Add(self.World, a);
|
||||
self.World.Selection.Add(a);
|
||||
|
||||
if (controlgroup.HasValue)
|
||||
self.World.Selection.AddToControlGroup(a, controlgroup.Value);
|
||||
|
||||
@@ -32,7 +32,11 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
// This only makes sense if the frozen actor has already been revealed (i.e. has renderables)
|
||||
if (fa.HasRenderables)
|
||||
{
|
||||
// HACK: RefreshState updated *all* actor state, not just the owner
|
||||
// This is generally bogus, and specifically breaks cursors and tooltips by setting Hidden to false
|
||||
var hidden = fa.Hidden;
|
||||
fa.RefreshState();
|
||||
fa.Hidden = hidden;
|
||||
fa.NeedRenderables = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
public object Create(ActorInitializer init) { return new GpsDot(this); }
|
||||
}
|
||||
|
||||
class GpsDot : INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
class GpsDot : INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
{
|
||||
readonly GpsDotInfo info;
|
||||
GpsDotEffect effect;
|
||||
@@ -38,9 +38,13 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
effect = new GpsDotEffect(self, info);
|
||||
}
|
||||
|
||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||
{
|
||||
self.World.AddFrameEndTask(w => w.Add(effect));
|
||||
}
|
||||
|
||||
|
||||
@@ -92,21 +92,34 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
? Info.Voice : null;
|
||||
}
|
||||
|
||||
public bool CanInfiltrateTarget(Actor self, Target target)
|
||||
{
|
||||
switch (target.Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
return Info.Types.Overlaps(target.Actor.GetEnabledTargetTypes()) &&
|
||||
Info.ValidStances.HasStance(self.Owner.Stances[target.Actor.Owner]);
|
||||
case TargetType.FrozenActor:
|
||||
return target.FrozenActor.IsValid && Info.Types.Overlaps(target.FrozenActor.TargetTypes) &&
|
||||
Info.ValidStances.HasStance(self.Owner.Stances[target.FrozenActor.Owner]);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "Infiltrate" || !IsValidOrder(self, order) || IsTraitDisabled)
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor
|
||||
|| !Info.Types.Overlaps(target.Actor.GetAllTargetTypes()))
|
||||
if (!CanInfiltrateTarget(self, order.Target))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new Infiltrate(self, target.Actor, this));
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new Infiltrate(self, order.Target, this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,21 +197,18 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
{
|
||||
if (order.OrderString == "DetonateAttack")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new MoveAdjacentTo(self, target));
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new MoveAdjacentTo(self, order.Target, targetLineColor: Color.Red));
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
else if (order.OrderString == "Detonate")
|
||||
{
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.QueueActivity(new CallFunc(StartDetonationSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
@@ -181,11 +182,15 @@ namespace OpenRA.Mods.Cnc.Traits
|
||||
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return null; }
|
||||
public Activity MoveTo(CPos cell, Actor ignoreActor) { return null; }
|
||||
public Activity MoveWithinRange(Target target, WDist range) { return null; }
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) { return null; }
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { return null; }
|
||||
public Activity MoveWithinRange(Target target, WDist range,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null) { return null; }
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange,
|
||||
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, CPos cell, SubCell subCell = SubCell.Any) { return null; }
|
||||
public Activity MoveToTarget(Actor self, Target target) { 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; }
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { return null; }
|
||||
|
||||
|
||||
@@ -20,23 +20,6 @@ namespace OpenRA.Mods.Common
|
||||
|
||||
public enum WaterCheck { NotChecked, EnoughWater, NotEnoughWater }
|
||||
|
||||
public class CaptureTarget<TInfoType> where TInfoType : class, ITraitInfoInterface
|
||||
{
|
||||
internal readonly Actor Actor;
|
||||
internal readonly TInfoType Info;
|
||||
|
||||
/// <summary>The order string given to the capturer so they can capture this actor.</summary>
|
||||
/// <example>ExternalCaptureActor</example>
|
||||
internal readonly string OrderString;
|
||||
|
||||
internal CaptureTarget(Actor actor, string orderString)
|
||||
{
|
||||
Actor = actor;
|
||||
Info = actor.Info.TraitInfoOrDefault<TInfoType>();
|
||||
OrderString = orderString;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AIUtils
|
||||
{
|
||||
public static bool IsAreaAvailable<T>(World world, Player player, Map map, int radius, HashSet<string> terrainTypes)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -19,19 +20,32 @@ namespace OpenRA.Mods.Common.Activities
|
||||
public class Fly : Activity
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly Target target;
|
||||
readonly WDist maxRange;
|
||||
readonly WDist minRange;
|
||||
readonly Color? targetLineColor;
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
bool soundPlayed;
|
||||
|
||||
public Fly(Actor self, Target t)
|
||||
public Fly(Actor self, Target t, WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
target = t;
|
||||
this.targetLineColor = targetLineColor;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
else if (initialTargetPosition.HasValue)
|
||||
lastVisibleTarget = Target.FromPos(initialTargetPosition.Value);
|
||||
}
|
||||
|
||||
public Fly(Actor self, Target t, WDist minRange, WDist maxRange)
|
||||
: this(self, t)
|
||||
public Fly(Actor self, Target t, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
: this(self, t, initialTargetPosition, targetLineColor)
|
||||
{
|
||||
this.maxRange = maxRange;
|
||||
this.minRange = minRange;
|
||||
@@ -61,14 +75,30 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
Cancel(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
if (!soundPlayed && aircraft.Info.TakeoffSounds.Length > 0 && self.IsAtGroundLevel())
|
||||
{
|
||||
Game.Sound.Play(SoundType.World, aircraft.Info.TakeoffSounds.Random(self.World.SharedRandom), aircraft.CenterPosition);
|
||||
@@ -76,23 +106,43 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
|
||||
// Inside the target annulus, so we're done
|
||||
var insideMaxRange = maxRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, maxRange);
|
||||
var insideMinRange = minRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, minRange);
|
||||
var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, maxRange);
|
||||
var insideMinRange = minRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, minRange);
|
||||
if (insideMaxRange && !insideMinRange)
|
||||
return NextActivity;
|
||||
|
||||
var d = target.CenterPosition - self.CenterPosition;
|
||||
var delta = checkTarget.CenterPosition - self.CenterPosition;
|
||||
|
||||
// The next move would overshoot, so consider it close enough
|
||||
var move = aircraft.FlyStep(aircraft.Facing);
|
||||
if (d.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
return NextActivity;
|
||||
|
||||
// Don't turn until we've reached the cruise altitude
|
||||
var desiredFacing = d.Yaw.Facing;
|
||||
var desiredFacing = delta.Yaw.Facing;
|
||||
var targetAltitude = aircraft.CenterPosition.Z + aircraft.Info.CruiseAltitude.Length - self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length;
|
||||
if (aircraft.CenterPosition.Z < targetAltitude)
|
||||
desiredFacing = aircraft.Facing;
|
||||
else
|
||||
{
|
||||
// Using the turn rate, compute a hypothetical circle traced by a continuous turn.
|
||||
// If it contains the destination point, it's unreachable without more complex manuvering.
|
||||
var turnRadius = CalculateTurnRadius(aircraft.MovementSpeed, aircraft.TurnSpeed);
|
||||
|
||||
// The current facing is a tangent of the minimal turn circle.
|
||||
// Make a perpendicular vector, and use it to locate the turn's center.
|
||||
var turnCenterFacing = aircraft.Facing;
|
||||
turnCenterFacing += Util.GetNearestFacing(aircraft.Facing, desiredFacing) > 0 ? 64 : -64;
|
||||
|
||||
var turnCenterDir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(turnCenterFacing));
|
||||
turnCenterDir *= turnRadius;
|
||||
turnCenterDir /= 1024;
|
||||
|
||||
// Compare with the target point, and keep flying away if it's inside the circle.
|
||||
var turnCenter = aircraft.CenterPosition + turnCenterDir;
|
||||
if ((checkTarget.CenterPosition - turnCenter).HorizontalLengthSquared < turnRadius * turnRadius)
|
||||
desiredFacing = aircraft.Facing;
|
||||
}
|
||||
|
||||
FlyToward(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude);
|
||||
|
||||
@@ -103,5 +153,13 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
yield return target;
|
||||
}
|
||||
|
||||
public static int CalculateTurnRadius(int speed, int turnSpeed)
|
||||
{
|
||||
// turnSpeed -> divide into 256 to get the number of ticks per complete rotation
|
||||
// speed -> multiply to get distance travelled per rotation (circumference)
|
||||
// 45 -> divide by 2*pi to get the turn radius: 45==256/(2*pi), with some extra leeway
|
||||
return 45 * speed / turnSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -22,6 +23,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly AttackAircraft attackAircraft;
|
||||
readonly Rearmable rearmable;
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
WDist lastVisibleMaximumRange;
|
||||
bool useLastVisibleTarget;
|
||||
|
||||
int ticksUntilTurn;
|
||||
|
||||
@@ -32,38 +36,81 @@ namespace OpenRA.Mods.Common.Activities
|
||||
attackAircraft = self.Trait<AttackAircraft>();
|
||||
rearmable = self.TraitOrDefault<Rearmable>();
|
||||
ticksUntilTurn = attackAircraft.AttackAircraftInfo.AttackTurnDelay;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
Cancel(self);
|
||||
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
|
||||
target = target.Recalculate(self.Owner);
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
if (!target.IsValidFor(self))
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
// If all valid weapons have depleted their ammo and Rearmable trait exists, return to RearmActor to reload and then resume the activity
|
||||
if (rearmable != null && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
|
||||
if (rearmable != null && !useLastVisibleTarget && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
|
||||
return ActivityUtils.SequenceActivities(new ReturnToBase(self, aircraft.Info.AbortOnResupply), this);
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// We don't know where the target actually is, so move to where we last saw it
|
||||
if (useLastVisibleTarget)
|
||||
{
|
||||
// We've reached the assumed position but it is not there - give up
|
||||
if (checkTarget.IsInRange(pos, lastVisibleMaximumRange))
|
||||
return NextActivity;
|
||||
|
||||
// Fly towards the last known position
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new Fly(self, target, WDist.Zero, lastVisibleMaximumRange, checkTarget.CenterPosition, Color.Red),
|
||||
this);
|
||||
}
|
||||
|
||||
// If we reach here the target is guaranteed to be both visible and alive, so use target instead of checkTarget.
|
||||
// The target may not be in range, but try attacking anyway...
|
||||
attackAircraft.DoAttack(self, target);
|
||||
|
||||
if (ChildActivity == null)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
// TODO: This should fire each weapon at its maximum range
|
||||
if (attackAircraft != null && target.IsInRange(self.CenterPosition, attackAircraft.Armaments.Where(Exts.IsTraitEnabled).Select(a => a.Weapon.MinRange).Min()))
|
||||
ChildActivity = ActivityUtils.SequenceActivities(new FlyTimed(ticksUntilTurn, self), new Fly(self, target), new FlyTimed(ticksUntilTurn, self));
|
||||
if (attackAircraft != null && target.IsInRange(self.CenterPosition, attackAircraft.GetMinimumRange()))
|
||||
ChildActivity = ActivityUtils.SequenceActivities(
|
||||
new FlyTimed(ticksUntilTurn, self),
|
||||
new Fly(self, target, checkTarget.CenterPosition, Color.Red),
|
||||
new FlyTimed(ticksUntilTurn, self));
|
||||
else
|
||||
ChildActivity = ActivityUtils.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn, self));
|
||||
ChildActivity = ActivityUtils.SequenceActivities(
|
||||
new Fly(self, target, checkTarget.CenterPosition, Color.Red),
|
||||
new FlyTimed(ticksUntilTurn, self));
|
||||
|
||||
// HACK: This needs to be done in this round-about way because TakeOff doesn't behave as expected when it doesn't have a NextActivity.
|
||||
if (self.World.Map.DistanceAboveTerrain(self.CenterPosition).Length < aircraft.Info.MinAirborneAltitude)
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (NextActivity != null && remainingTicks <= 0)
|
||||
return NextActivity;
|
||||
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
@@ -41,8 +44,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
if (remainingTicks > 0)
|
||||
remainingTicks--;
|
||||
else if (remainingTicks == 0)
|
||||
return NextActivity;
|
||||
|
||||
// We can't possibly turn this fast
|
||||
var desiredFacing = aircraft.Facing + 64;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -17,38 +18,80 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FlyFollow : Activity
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly WDist minRange;
|
||||
readonly WDist maxRange;
|
||||
readonly Color? targetLineColor;
|
||||
Target target;
|
||||
Aircraft aircraft;
|
||||
WDist minRange;
|
||||
WDist maxRange;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
bool wasMovingWithinRange;
|
||||
|
||||
public FlyFollow(Actor self, Target target, WDist minRange, WDist maxRange)
|
||||
public FlyFollow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition, Color? targetLineColor = null)
|
||||
{
|
||||
this.target = target;
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
this.targetLineColor = targetLineColor;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
else if (initialTargetPosition.HasValue)
|
||||
lastVisibleTarget = Target.FromPos(initialTargetPosition.Value);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
Cancel(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange))
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// If we are ticking again after previously sequencing a MoveWithRange then that move must have completed
|
||||
// Either we are in range and can see the target, or we've lost track of it and should give up
|
||||
if (wasMovingWithinRange && targetIsHiddenActor)
|
||||
return NextActivity;
|
||||
|
||||
wasMovingWithinRange = false;
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// We've reached the required range - if the target is visible and valid then we wait
|
||||
// otherwise if it is hidden or dead we give up
|
||||
if (checkTarget.IsInRange(pos, maxRange) && !checkTarget.IsInRange(pos, minRange))
|
||||
{
|
||||
Fly.FlyToward(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude);
|
||||
return this;
|
||||
return useLastVisibleTarget ? NextActivity : this;
|
||||
}
|
||||
|
||||
return ActivityUtils.SequenceActivities(new Fly(self, target, minRange, maxRange), this);
|
||||
wasMovingWithinRange = true;
|
||||
return ActivityUtils.SequenceActivities(
|
||||
aircraft.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, targetLineColor),
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,72 +21,84 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly AttackAircraft attackAircraft;
|
||||
readonly bool attackOnlyVisibleTargets;
|
||||
readonly Rearmable rearmable;
|
||||
|
||||
Target target;
|
||||
bool canHideUnderFog;
|
||||
protected Target Target
|
||||
{
|
||||
get
|
||||
{
|
||||
return target;
|
||||
}
|
||||
Target lastVisibleTarget;
|
||||
WDist lastVisibleMaximumRange;
|
||||
bool useLastVisibleTarget;
|
||||
|
||||
private set
|
||||
{
|
||||
target = value;
|
||||
if (target.Type == TargetType.Actor)
|
||||
canHideUnderFog = target.Actor.Info.HasTraitInfo<HiddenUnderFogInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
public HeliAttack(Actor self, Target target, bool attackOnlyVisibleTargets = true)
|
||||
public HeliAttack(Actor self, Target target)
|
||||
{
|
||||
Target = target;
|
||||
this.target = target;
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
attackAircraft = self.Trait<AttackAircraft>();
|
||||
this.attackOnlyVisibleTargets = attackOnlyVisibleTargets;
|
||||
rearmable = self.TraitOrDefault<Rearmable>();
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
Cancel(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var targetPos = attackAircraft.GetTargetPosition(pos, target);
|
||||
if (attackOnlyVisibleTargets && target.Type == TargetType.Actor && canHideUnderFog
|
||||
&& !target.Actor.CanBeViewedByPlayer(self.Owner))
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
var newTarget = Target.FromCell(self.World, self.World.Map.CellContaining(targetPos));
|
||||
|
||||
Cancel(self);
|
||||
self.SetTargetLine(newTarget, Color.Green);
|
||||
return new HeliFly(self, newTarget);
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target);
|
||||
}
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
// If all valid weapons have depleted their ammo and Rearmable trait exists, return to RearmActor to reload and then resume the activity
|
||||
if (rearmable != null && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
|
||||
if (rearmable != null && !useLastVisibleTarget && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self)))
|
||||
return ActivityUtils.SequenceActivities(new HeliReturnToBase(self, aircraft.Info.AbortOnResupply), this);
|
||||
|
||||
var dist = targetPos - pos;
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// Can rotate facing while ascending
|
||||
var desiredFacing = dist.HorizontalLengthSquared != 0 ? dist.Yaw.Facing : aircraft.Facing;
|
||||
// Update facing
|
||||
var delta = attackAircraft.GetTargetPosition(pos, checkTarget) - pos;
|
||||
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
|
||||
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.TurnSpeed);
|
||||
|
||||
if (HeliFly.AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
// We don't know where the target actually is, so move to where we last saw it
|
||||
if (useLastVisibleTarget)
|
||||
{
|
||||
// We've reached the assumed position but it is not there - give up
|
||||
if (checkTarget.IsInRange(pos, lastVisibleMaximumRange))
|
||||
return NextActivity;
|
||||
|
||||
// Fly towards the last known position
|
||||
aircraft.SetPosition(self, aircraft.CenterPosition + aircraft.FlyStep(desiredFacing));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Fly towards the target
|
||||
if (!target.IsInRange(pos, attackAircraft.GetMaximumRangeVersusTarget(target)))
|
||||
aircraft.SetPosition(self, aircraft.CenterPosition + aircraft.FlyStep(desiredFacing));
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -19,19 +20,33 @@ namespace OpenRA.Mods.Common.Activities
|
||||
public class HeliFly : Activity
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly Target target;
|
||||
readonly WDist maxRange;
|
||||
readonly WDist minRange;
|
||||
readonly Color? targetLineColor;
|
||||
bool soundPlayed;
|
||||
|
||||
public HeliFly(Actor self, Target t)
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
|
||||
public HeliFly(Actor self, Target t, WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
target = t;
|
||||
this.targetLineColor = targetLineColor;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
else if (initialTargetPosition.HasValue)
|
||||
lastVisibleTarget = Target.FromPos(initialTargetPosition.Value);
|
||||
}
|
||||
|
||||
public HeliFly(Actor self, Target t, WDist minRange, WDist maxRange)
|
||||
: this(self, t)
|
||||
public HeliFly(Actor self, Target t, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
: this(self, t, initialTargetPosition, targetLineColor)
|
||||
{
|
||||
this.maxRange = maxRange;
|
||||
this.minRange = minRange;
|
||||
@@ -56,12 +71,25 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
// Refuse to take off if it would land immediately again.
|
||||
if (aircraft.ForceLanding)
|
||||
{
|
||||
Cancel(self);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
if (!soundPlayed && aircraft.Info.TakeoffSounds.Length > 0 && self.IsAtGroundLevel())
|
||||
@@ -73,30 +101,33 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
var pos = target.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// Rotate towards the target
|
||||
var dist = pos - self.CenterPosition;
|
||||
var desiredFacing = dist.HorizontalLengthSquared != 0 ? dist.Yaw.Facing : aircraft.Facing;
|
||||
// Update facing
|
||||
var delta = checkTarget.CenterPosition - aircraft.CenterPosition;
|
||||
var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing;
|
||||
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.TurnSpeed);
|
||||
if (AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
var move = aircraft.FlyStep(desiredFacing);
|
||||
|
||||
// Inside the minimum range, so reverse
|
||||
if (minRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, minRange))
|
||||
if (minRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, minRange))
|
||||
{
|
||||
aircraft.SetPosition(self, aircraft.CenterPosition - move);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Inside the maximum range, so we're done
|
||||
if (maxRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, maxRange))
|
||||
if (maxRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, maxRange))
|
||||
return NextActivity;
|
||||
|
||||
// The next move would overshoot, so just set the final position
|
||||
if (dist.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
{
|
||||
var targetAltitude = aircraft.CenterPosition.Z + aircraft.Info.CruiseAltitude.Length - self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length;
|
||||
aircraft.SetPosition(self, pos + new WVec(0, 0, targetAltitude - pos.Z));
|
||||
aircraft.SetPosition(self, checkTarget.CenterPosition + new WVec(0, 0, targetAltitude - checkTarget.CenterPosition.Z));
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -89,7 +90,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
var target = Target.FromPos(nearestResupplier.CenterPosition + randomPosition);
|
||||
|
||||
return ActivityUtils.SequenceActivities(new HeliFly(self, target, WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase), this);
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new HeliFly(self, target, WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase, targetLineColor: Color.Green),
|
||||
this);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
@@ -38,12 +38,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
else
|
||||
{
|
||||
// HACK: Append NextInQueue to TakeOff to avoid moving to the Rallypoint (if NextInQueue is non-null).
|
||||
ChildActivity = ActivityUtils.SequenceActivities(
|
||||
aircraft.GetResupplyActivities(host)
|
||||
.Append(new AllowYieldingReservation(self))
|
||||
.Append(new TakeOff(self))
|
||||
.Append(NextInQueue)
|
||||
.Append(new TakeOff(self, (a, b, c) => NextInQueue == null && b.NextInQueue == null))
|
||||
.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -52,11 +53,6 @@ namespace OpenRA.Mods.Common.Activities
|
||||
.ClosestTo(self);
|
||||
}
|
||||
|
||||
int CalculateTurnRadius(int speed)
|
||||
{
|
||||
return 45 * speed / aircraft.Info.TurnSpeed;
|
||||
}
|
||||
|
||||
void Calculate(Actor self)
|
||||
{
|
||||
if (dest == null || dest.IsDead || Reservable.IsReserved(dest))
|
||||
@@ -76,7 +72,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
// Add 10% to the turning radius to ensure we have enough room
|
||||
var speed = aircraft.MovementSpeed * 32 / 35;
|
||||
var turnRadius = CalculateTurnRadius(speed);
|
||||
var turnRadius = Fly.CalculateTurnRadius(speed, aircraft.Info.TurnSpeed);
|
||||
|
||||
// Find the center of the turning circles for clockwise and counterclockwise turns
|
||||
var angle = WAngle.FromFacing(aircraft.Facing);
|
||||
@@ -140,7 +136,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
if (nearestResupplier != null)
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new Fly(self, Target.FromActor(nearestResupplier), WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase),
|
||||
new Fly(self, Target.FromActor(nearestResupplier), WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase, targetLineColor: Color.Green),
|
||||
new FlyCircle(self, aircraft.Info.NumberOfTicksToVerifyAvailableAirport),
|
||||
this);
|
||||
else
|
||||
@@ -153,7 +149,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
List<Activity> landingProcedures = new List<Activity>();
|
||||
|
||||
var turnRadius = CalculateTurnRadius(aircraft.Info.Speed);
|
||||
var turnRadius = Fly.CalculateTurnRadius(aircraft.Info.Speed, aircraft.Info.TurnSpeed);
|
||||
|
||||
landingProcedures.Add(new Fly(self, Target.FromPos(w1), WDist.Zero, new WDist(turnRadius * 3)));
|
||||
landingProcedures.Add(new Fly(self, Target.FromPos(w2)));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
@@ -18,11 +19,19 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
readonly Aircraft aircraft;
|
||||
readonly IMove move;
|
||||
Func<Actor, Activity, CPos, bool> moveToRallyPoint;
|
||||
|
||||
public TakeOff(Actor self)
|
||||
{
|
||||
aircraft = self.Trait<Aircraft>();
|
||||
move = self.Trait<IMove>();
|
||||
moveToRallyPoint = (actor, activity, pos) => NextActivity == null;
|
||||
}
|
||||
|
||||
public TakeOff(Actor self, Func<Actor, Activity, CPos, bool> moveToRallyPoint)
|
||||
: this(self)
|
||||
{
|
||||
this.moveToRallyPoint = moveToRallyPoint;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
@@ -43,10 +52,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var destination = rp != null ? rp.Location :
|
||||
(hasHost ? self.World.Map.CellContaining(host.CenterPosition) : self.Location);
|
||||
|
||||
if (NextInQueue == null)
|
||||
if (moveToRallyPoint(self, this, destination))
|
||||
return new AttackMoveActivity(self, move.MoveTo(destination, 1));
|
||||
else
|
||||
return NextInQueue;
|
||||
return NextActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -29,7 +30,12 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly IFacing facing;
|
||||
readonly IPositionable positionable;
|
||||
readonly bool forceAttack;
|
||||
|
||||
protected Target target;
|
||||
Target lastVisibleTarget;
|
||||
WDist lastVisibleMaximumRange;
|
||||
bool useLastVisibleTarget;
|
||||
bool wasMovingWithinRange;
|
||||
|
||||
WDist minRange;
|
||||
WDist maxRange;
|
||||
@@ -48,16 +54,71 @@ namespace OpenRA.Mods.Common.Activities
|
||||
positionable = self.Trait<IPositionable>();
|
||||
|
||||
move = allowMovement ? self.TraitOrDefault<IMove>() : null;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMaximumRange = attackTraits.Where(x => !x.IsTraitDisabled)
|
||||
.Min(x => x.GetMaximumRangeVersusTarget(target));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Target RecalculateTarget(Actor self)
|
||||
protected virtual Target RecalculateTarget(Actor self, out bool targetIsHiddenActor)
|
||||
{
|
||||
return target.Recalculate(self.Owner);
|
||||
return target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
target = RecalculateTarget(self);
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
bool targetIsHiddenActor;
|
||||
target = RecalculateTarget(self, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attackTraits.Where(x => !x.IsTraitDisabled)
|
||||
.Min(x => x.GetMaximumRangeVersusTarget(target));
|
||||
}
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// If we are ticking again after previously sequencing a MoveWithRange then that move must have completed
|
||||
// Either we are in range and can see the target, or we've lost track of it and should give up
|
||||
if (wasMovingWithinRange && targetIsHiddenActor)
|
||||
return NextActivity;
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
wasMovingWithinRange = false;
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// We don't know where the target actually is, so move to where we last saw it
|
||||
if (useLastVisibleTarget)
|
||||
{
|
||||
// We've reached the assumed position but it is not there or we can't move any further - give up
|
||||
if (checkTarget.IsInRange(pos, lastVisibleMaximumRange) || move == null || lastVisibleMaximumRange == WDist.Zero)
|
||||
return NextActivity;
|
||||
|
||||
// Move towards the last known position
|
||||
wasMovingWithinRange = true;
|
||||
return ActivityUtils.SequenceActivities(
|
||||
move.MoveWithinRange(target, WDist.Zero, lastVisibleMaximumRange, checkTarget.CenterPosition, Color.Red),
|
||||
this);
|
||||
}
|
||||
|
||||
turnActivity = moveActivity = null;
|
||||
attackStatus = AttackStatus.UnableToAttack;
|
||||
|
||||
@@ -74,7 +135,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return turnActivity;
|
||||
|
||||
if (attackStatus.HasFlag(AttackStatus.NeedsToMove))
|
||||
{
|
||||
wasMovingWithinRange = true;
|
||||
return moveActivity;
|
||||
}
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
@@ -104,7 +168,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var sightRange = rs != null ? rs.Range : WDist.FromCells(2);
|
||||
|
||||
attackStatus |= AttackStatus.NeedsToMove;
|
||||
moveActivity = ActivityUtils.SequenceActivities(move.MoveWithinRange(target, sightRange), this);
|
||||
moveActivity = ActivityUtils.SequenceActivities(
|
||||
move.MoveWithinRange(target, sightRange, target.CenterPosition, Color.Red),
|
||||
this);
|
||||
|
||||
return AttackStatus.NeedsToMove;
|
||||
}
|
||||
|
||||
@@ -128,7 +195,12 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return AttackStatus.UnableToAttack;
|
||||
|
||||
attackStatus |= AttackStatus.NeedsToMove;
|
||||
moveActivity = ActivityUtils.SequenceActivities(move.MoveWithinRange(target, minRange, maxRange), this);
|
||||
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
moveActivity = ActivityUtils.SequenceActivities(
|
||||
move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, Color.Red),
|
||||
this);
|
||||
|
||||
return AttackStatus.NeedsToMove;
|
||||
}
|
||||
|
||||
@@ -147,47 +219,4 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return AttackStatus.Attacking;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TargetExts
|
||||
{
|
||||
/// <summary>Update (Frozen)Actor targets to account for visibility changes or actor replacement</summary>
|
||||
public static Target Recalculate(this Target t, Player viewer)
|
||||
{
|
||||
// Check whether the target has transformed into something else
|
||||
// HACK: This relies on knowing the internal implementation details of Target
|
||||
if (t.Type == TargetType.Invalid && t.Actor != null && t.Actor.ReplacedByActor != null)
|
||||
t = Target.FromActor(t.Actor.ReplacedByActor);
|
||||
|
||||
// Bot-controlled units aren't yet capable of understanding visibility changes
|
||||
if (viewer.IsBot)
|
||||
return t;
|
||||
|
||||
if (t.Type == TargetType.Actor)
|
||||
{
|
||||
// Actor has been hidden under the fog
|
||||
if (!t.Actor.CanBeViewedByPlayer(viewer))
|
||||
{
|
||||
// Replace with FrozenActor if applicable, otherwise drop the target
|
||||
var frozen = viewer.FrozenActorLayer.FromID(t.Actor.ActorID);
|
||||
return frozen != null ? Target.FromFrozenActor(frozen) : Target.Invalid;
|
||||
}
|
||||
}
|
||||
else if (t.Type == TargetType.FrozenActor)
|
||||
{
|
||||
// Frozen actor has been revealed
|
||||
if (!t.FrozenActor.Visible || !t.FrozenActor.IsValid)
|
||||
{
|
||||
// Original actor is still alive
|
||||
if (t.FrozenActor.Actor != null && t.FrozenActor.Actor.CanBeViewedByPlayer(viewer))
|
||||
return Target.FromActor(t.FrozenActor.Actor);
|
||||
|
||||
// Original actor was killed while hidden
|
||||
if (t.Actor == null)
|
||||
return Target.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -17,49 +18,62 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class CaptureActor : Enter
|
||||
{
|
||||
readonly Actor actor;
|
||||
readonly CaptureManager targetManager;
|
||||
readonly CaptureManager manager;
|
||||
|
||||
public CaptureActor(Actor self, Actor target)
|
||||
: base(self, target, EnterBehaviour.Exit)
|
||||
Actor enterActor;
|
||||
CaptureManager enterCaptureManager;
|
||||
|
||||
public CaptureActor(Actor self, Target target)
|
||||
: base(self, target, Color.Red)
|
||||
{
|
||||
actor = target;
|
||||
manager = self.Trait<CaptureManager>();
|
||||
targetManager = target.Trait<CaptureManager>();
|
||||
}
|
||||
|
||||
protected override bool CanReserve(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
return !actor.IsDead && !targetManager.BeingCaptured && targetManager.CanBeTargetedBy(actor, self, manager);
|
||||
}
|
||||
if (enterActor != targetActor)
|
||||
{
|
||||
enterActor = targetActor;
|
||||
enterCaptureManager = targetActor.TraitOrDefault<CaptureManager>();
|
||||
}
|
||||
|
||||
// Make sure we can still capture the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (enterCaptureManager == null || !enterCaptureManager.CanBeTargetedBy(enterActor, self, manager))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool TryStartEnter(Actor self)
|
||||
{
|
||||
// StartCapture returns false when a capture delay is enabled
|
||||
// We wait until it returns true before allowing entering the target
|
||||
Captures captures;
|
||||
if (!manager.StartCapture(self, actor, targetManager, out captures))
|
||||
if (!manager.StartCapture(self, enterActor, enterCaptureManager, out captures))
|
||||
return false;
|
||||
|
||||
if (!captures.Info.ConsumedByCapture)
|
||||
{
|
||||
// Immediately capture without entering or disposing the actor
|
||||
DoCapture(self, captures);
|
||||
AbortOrExit(self);
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
if (!CanReserve(self))
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (enterActor != targetActor)
|
||||
return;
|
||||
|
||||
if (enterCaptureManager.BeingCaptured || !enterCaptureManager.CanBeTargetedBy(enterActor, self, manager))
|
||||
return;
|
||||
|
||||
// Prioritize capturing over sabotaging
|
||||
var captures = manager.ValidCapturesWithLowestSabotageThreshold(self, actor, targetManager);
|
||||
var captures = manager.ValidCapturesWithLowestSabotageThreshold(self, enterActor, enterCaptureManager);
|
||||
if (captures == null)
|
||||
return;
|
||||
|
||||
@@ -68,23 +82,23 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
void DoCapture(Actor self, Captures captures)
|
||||
{
|
||||
var oldOwner = actor.Owner;
|
||||
var oldOwner = enterActor.Owner;
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
// The target died or was already captured during this tick
|
||||
if (actor.IsDead || oldOwner != actor.Owner)
|
||||
if (enterActor.IsDead || oldOwner != enterActor.Owner)
|
||||
return;
|
||||
|
||||
// Sabotage instead of capture
|
||||
if (captures.Info.SabotageThreshold > 0 && !actor.Owner.NonCombatant)
|
||||
if (captures.Info.SabotageThreshold > 0 && !enterActor.Owner.NonCombatant)
|
||||
{
|
||||
var health = actor.Trait<IHealth>();
|
||||
var health = enterActor.Trait<IHealth>();
|
||||
|
||||
// Cast to long to avoid overflow when multiplying by the health
|
||||
if (100 * (long)health.HP > captures.Info.SabotageThreshold * (long)health.MaxHP)
|
||||
{
|
||||
var damage = (int)((long)health.MaxHP * captures.Info.SabotageHPRemoval / 100);
|
||||
actor.InflictDamage(self, new Damage(damage, captures.Info.SabotageDamageTypes));
|
||||
enterActor.InflictDamage(self, new Damage(damage, captures.Info.SabotageDamageTypes));
|
||||
|
||||
if (captures.Info.ConsumedByCapture)
|
||||
self.Dispose();
|
||||
@@ -94,10 +108,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
}
|
||||
|
||||
// Do the capture
|
||||
actor.ChangeOwnerSync(self.Owner);
|
||||
enterActor.ChangeOwnerSync(self.Owner);
|
||||
|
||||
foreach (var t in actor.TraitsImplementing<INotifyCapture>())
|
||||
t.OnCapture(actor, self, oldOwner, self.Owner, captures.Info.CaptureTypes);
|
||||
foreach (var t in enterActor.TraitsImplementing<INotifyCapture>())
|
||||
t.OnCapture(enterActor, self, oldOwner, self.Owner, captures.Info.CaptureTypes);
|
||||
|
||||
if (self.Owner.Stances[oldOwner].HasStance(captures.Info.PlayerExperienceStances))
|
||||
{
|
||||
@@ -123,17 +137,14 @@ namespace OpenRA.Mods.Common.Activities
|
||||
base.OnActorDispose(self);
|
||||
}
|
||||
|
||||
void CancelCapture(Actor self)
|
||||
protected override void OnCancel(Actor self)
|
||||
{
|
||||
manager.CancelCapture(self, actor, targetManager);
|
||||
CancelCapture(self);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
void CancelCapture(Actor self)
|
||||
{
|
||||
if (!targetManager.CanBeTargetedBy(actor, self, manager))
|
||||
Cancel(self);
|
||||
|
||||
return base.Tick(self);
|
||||
manager.CancelCapture(self, enterActor, enterCaptureManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,54 +9,79 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class Demolish : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly IDemolishable[] demolishables;
|
||||
readonly int delay;
|
||||
readonly int flashes;
|
||||
readonly int flashesDelay;
|
||||
readonly int flashInterval;
|
||||
readonly INotifyDemolition[] notifiers;
|
||||
readonly EnterBehaviour enterBehaviour;
|
||||
|
||||
public Demolish(Actor self, Actor target, EnterBehaviour enterBehaviour, int delay,
|
||||
Actor enterActor;
|
||||
IDemolishable[] enterDemolishables;
|
||||
|
||||
public Demolish(Actor self, Target target, EnterBehaviour enterBehaviour, int delay,
|
||||
int flashes, int flashesDelay, int flashInterval)
|
||||
: base(self, target, enterBehaviour)
|
||||
: base(self, target, Color.Red)
|
||||
{
|
||||
this.target = target;
|
||||
demolishables = target.TraitsImplementing<IDemolishable>().ToArray();
|
||||
notifiers = self.TraitsImplementing<INotifyDemolition>().ToArray();
|
||||
this.delay = delay;
|
||||
this.flashes = flashes;
|
||||
this.flashesDelay = flashesDelay;
|
||||
this.flashInterval = flashInterval;
|
||||
this.enterBehaviour = enterBehaviour;
|
||||
}
|
||||
|
||||
protected override bool CanReserve(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
return demolishables.Any(i => i.IsValidTarget(target, self));
|
||||
enterActor = targetActor;
|
||||
enterDemolishables = targetActor.TraitsImplementing<IDemolishable>().ToArray();
|
||||
|
||||
// Make sure we can still demolish the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (!enterDemolishables.Any(i => i.IsValidTarget(enterActor, self)))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (target.IsDead)
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
w.Add(new FlashTarget(target, count: flashes, delay: flashesDelay, interval: flashInterval));
|
||||
if (!enterDemolishables.Any(i => i.IsValidTarget(enterActor, self)))
|
||||
return;
|
||||
|
||||
w.Add(new FlashTarget(enterActor, count: flashes, delay: flashesDelay, interval: flashInterval));
|
||||
|
||||
foreach (var ind in notifiers)
|
||||
ind.Demolishing(self);
|
||||
|
||||
foreach (var d in demolishables)
|
||||
d.Demolish(target, self, delay);
|
||||
foreach (var d in enterDemolishables)
|
||||
d.Demolish(enterActor, self, delay);
|
||||
|
||||
if (enterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
else if (enterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,44 +9,44 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class DonateCash : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly int payload;
|
||||
readonly int experience;
|
||||
readonly int playerExperience;
|
||||
|
||||
public DonateCash(Actor self, Actor target, int payload, int playerExperience)
|
||||
: base(self, target, EnterBehaviour.Dispose)
|
||||
public DonateCash(Actor self, Target target, int payload, int playerExperience)
|
||||
: base(self, target, Color.Yellow)
|
||||
{
|
||||
this.target = target;
|
||||
this.payload = payload;
|
||||
this.experience = playerExperience;
|
||||
this.playerExperience = playerExperience;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
if (target.IsDead)
|
||||
return;
|
||||
|
||||
var donated = target.Owner.PlayerActor.Trait<PlayerResources>().ChangeCash(payload);
|
||||
var targetOwner = targetActor.Owner;
|
||||
var donated = targetOwner.PlayerActor.Trait<PlayerResources>().ChangeCash(payload);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null && target.Owner != self.Owner)
|
||||
exp.GiveExperience(experience);
|
||||
if (exp != null && targetOwner != self.Owner)
|
||||
exp.GiveExperience(playerExperience);
|
||||
|
||||
if (self.Owner.IsAlliedWith(self.World.RenderPlayer))
|
||||
self.World.AddFrameEndTask(w => w.Add(new FloatingText(target.CenterPosition, target.Owner.Color.RGB, FloatingText.FormatCashTick(donated), 30)));
|
||||
self.World.AddFrameEndTask(w => w.Add(new FloatingText(targetActor.CenterPosition, targetOwner.Color.RGB, FloatingText.FormatCashTick(donated), 30)));
|
||||
|
||||
foreach (var nct in target.TraitsImplementing<INotifyCashTransfer>())
|
||||
nct.OnAcceptingCash(target, self);
|
||||
foreach (var nct in targetActor.TraitsImplementing<INotifyCashTransfer>())
|
||||
nct.OnAcceptingCash(targetActor, self);
|
||||
|
||||
foreach (var nct in self.TraitsImplementing<INotifyCashTransfer>())
|
||||
nct.OnDeliveringCash(self, target);
|
||||
nct.OnDeliveringCash(self, targetActor);
|
||||
|
||||
self.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,45 +9,60 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class DonateExperience : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly GainsExperience targetGainsExperience;
|
||||
readonly int level;
|
||||
readonly int playerExperience;
|
||||
|
||||
public DonateExperience(Actor self, Actor target, int level, int playerExperience, GainsExperience targetGainsExperience)
|
||||
: base(self, target, EnterBehaviour.Dispose)
|
||||
Actor enterActor;
|
||||
GainsExperience enterGainsExperience;
|
||||
|
||||
public DonateExperience(Actor self, Target target, int level, int playerExperience)
|
||||
: base(self, target, Color.Yellow)
|
||||
{
|
||||
this.target = target;
|
||||
this.level = level;
|
||||
this.playerExperience = playerExperience;
|
||||
this.targetGainsExperience = targetGainsExperience;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
if (target.IsDead)
|
||||
enterActor = targetActor;
|
||||
enterGainsExperience = targetActor.TraitOrDefault<GainsExperience>();
|
||||
|
||||
if (enterGainsExperience == null || enterGainsExperience.Level == enterGainsExperience.MaxLevel)
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
targetGainsExperience.GiveLevels(level);
|
||||
if (enterGainsExperience.Level == enterGainsExperience.MaxLevel)
|
||||
return;
|
||||
|
||||
enterGainsExperience.GiveLevels(level);
|
||||
|
||||
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
|
||||
if (exp != null && target.Owner != self.Owner)
|
||||
if (exp != null && enterActor.Owner != self.Owner)
|
||||
exp.GiveExperience(playerExperience);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (target.IsDead || targetGainsExperience.Level == targetGainsExperience.MaxLevel)
|
||||
Cancel(self);
|
||||
|
||||
return base.Tick(self);
|
||||
self.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* 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
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -21,287 +22,154 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public abstract class Enter : Activity
|
||||
{
|
||||
public enum ReserveStatus { None, TooFar, Pending, Ready }
|
||||
enum EnterState { ApproachingOrEntering, WaitingToEnter, Inside, Exiting, Done }
|
||||
enum EnterState { Approaching, Waiting, Entering, Exiting }
|
||||
|
||||
readonly IMove move;
|
||||
readonly int maxTries = 0;
|
||||
readonly EnterBehaviour enterBehaviour;
|
||||
readonly bool repathWhileMoving;
|
||||
readonly Color? targetLineColor;
|
||||
|
||||
public Target Target { get { return target; } }
|
||||
Target target;
|
||||
EnterState nextState = EnterState.ApproachingOrEntering; // Hint/starting point for next state
|
||||
bool isEnteringOrInside = false; // Used to know if exiting should be used
|
||||
WPos savedPos; // Position just before entering
|
||||
Activity inner;
|
||||
bool firstApproach = true;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
EnterState lastState = EnterState.Approaching;
|
||||
|
||||
protected Enter(Actor self, Actor target, EnterBehaviour enterBehaviour, int maxTries = 1, bool repathWhileMoving = true)
|
||||
protected Enter(Actor self, Target target, Color? targetLineColor = null)
|
||||
{
|
||||
move = self.Trait<IMove>();
|
||||
this.target = Target.FromActor(target);
|
||||
this.maxTries = maxTries;
|
||||
this.enterBehaviour = enterBehaviour;
|
||||
this.repathWhileMoving = repathWhileMoving;
|
||||
this.target = target;
|
||||
this.targetLineColor = targetLineColor;
|
||||
}
|
||||
|
||||
// CanEnter(target) should to be true; otherwise, Enter may abort.
|
||||
// Tries counter starts at 1 (reset every tick)
|
||||
protected virtual bool TryGetAlternateTarget(Actor self, int tries, ref Target target) { return false; }
|
||||
protected virtual bool CanReserve(Actor self) { return true; }
|
||||
protected virtual ReserveStatus Reserve(Actor self)
|
||||
{
|
||||
return !CanReserve(self) ? ReserveStatus.None : move.CanEnterTargetNow(self, target) ? ReserveStatus.Ready : ReserveStatus.TooFar;
|
||||
}
|
||||
|
||||
protected virtual void Unreserve(Actor self, bool abort) { }
|
||||
protected virtual void OnInside(Actor self) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the actor is ready to transition from approaching to entering the target.
|
||||
/// Return true to start entering, or false to wait in the WaitingToEnter state.
|
||||
/// Called early in the activity tick to allow subclasses to update state.
|
||||
/// Call Cancel(self, true) if it is no longer valid to enter
|
||||
/// </summary>
|
||||
protected virtual bool TryStartEnter(Actor self) { return true; }
|
||||
protected virtual void TickInner(Actor self, Target target, bool targetIsDeadOrHiddenActor) { }
|
||||
|
||||
protected bool TryGetAlternateTargetInCircle(
|
||||
Actor self, WDist radius, Action<Target> update, Func<Actor, bool> primaryFilter, Func<Actor, bool>[] preferenceFilters = null)
|
||||
/// <summary>
|
||||
/// Called when the actor is ready to transition from approaching to entering the target actor.
|
||||
/// Return true to start entering, or false to wait in the WaitingToEnter state.
|
||||
/// Call Cancel(self, true) before returning false if it is no longer valid to enter
|
||||
/// </summary>
|
||||
protected virtual bool TryStartEnter(Actor self, Actor targetActor) { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the actor has entered the target actor
|
||||
/// Return true if the action succeeded and the actor should be Killed/Disposed
|
||||
/// (assuming the relevant EnterBehaviour), or false if the actor should exit unharmed
|
||||
/// </summary>
|
||||
protected virtual void OnEnterComplete(Actor self, Actor targetActor) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the activity is cancelled to allow subclasses to clean up their own state.
|
||||
/// </summary>
|
||||
protected virtual void OnCancel(Actor self) { }
|
||||
|
||||
Activity moveActivity;
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var diff = new WVec(radius, radius, WDist.Zero);
|
||||
var candidates = self.World.ActorMap.ActorsInBox(self.CenterPosition - diff, self.CenterPosition + diff)
|
||||
.Where(primaryFilter).Select(a => new { Actor = a, Ls = (self.CenterPosition - a.CenterPosition).HorizontalLengthSquared })
|
||||
.Where(p => p.Ls <= radius.LengthSquared).OrderBy(p => p.Ls).Select(p => p.Actor);
|
||||
if (preferenceFilters != null)
|
||||
foreach (var filter in preferenceFilters)
|
||||
// Update our view of the target
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Cancel immediately if the target died while we were entering it
|
||||
if (!IsCanceled && useLastVisibleTarget && lastState == EnterState.Entering)
|
||||
Cancel(self, true);
|
||||
|
||||
TickInner(self, target, useLastVisibleTarget);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// We need to wait for movement to finish before transitioning to
|
||||
// the next state or next activity
|
||||
if (moveActivity != null)
|
||||
{
|
||||
moveActivity = ActivityUtils.RunActivity(self, moveActivity);
|
||||
if (moveActivity != null)
|
||||
return this;
|
||||
}
|
||||
|
||||
// Note that lastState refers to what we have just *finished* doing
|
||||
switch (lastState)
|
||||
{
|
||||
case EnterState.Approaching:
|
||||
case EnterState.Waiting:
|
||||
{
|
||||
var preferredCandidate = candidates.FirstOrDefault(filter);
|
||||
if (preferredCandidate == null)
|
||||
continue;
|
||||
target = Target.FromActor(preferredCandidate);
|
||||
update(target);
|
||||
return true;
|
||||
// NOTE: We can safely cancel in this case because we know the
|
||||
// actor has finished any in-progress move activities
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
// Lost track of the target
|
||||
if (useLastVisibleTarget && lastVisibleTarget.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
|
||||
// We are not next to the target - lets fix that
|
||||
if (target.Type != TargetType.Invalid && !move.CanEnterTargetNow(self, target))
|
||||
{
|
||||
lastState = EnterState.Approaching;
|
||||
|
||||
// Target lines are managed by this trait, so we do not pass targetLineColor
|
||||
var initialTargetPosition = (useLastVisibleTarget ? lastVisibleTarget : target).CenterPosition;
|
||||
moveActivity = ActivityUtils.RunActivity(self, move.MoveToTarget(self, target, initialTargetPosition));
|
||||
break;
|
||||
}
|
||||
|
||||
// We are next to where we thought the target should be, but it isn't here
|
||||
// There's not much more we can do here
|
||||
if (useLastVisibleTarget || target.Type != TargetType.Actor)
|
||||
return NextActivity;
|
||||
|
||||
// Are we ready to move into the target?
|
||||
if (TryStartEnter(self, target.Actor))
|
||||
{
|
||||
lastState = EnterState.Entering;
|
||||
moveActivity = ActivityUtils.RunActivity(self, move.MoveIntoTarget(self, target));
|
||||
return this;
|
||||
}
|
||||
|
||||
// Subclasses can cancel the activity during TryStartEnter
|
||||
// Return immediately to avoid an extra tick's delay
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
lastState = EnterState.Waiting;
|
||||
break;
|
||||
}
|
||||
|
||||
var candidate = candidates.FirstOrDefault();
|
||||
if (candidate == null)
|
||||
return false;
|
||||
target = Target.FromActor(candidate);
|
||||
update(target);
|
||||
return true;
|
||||
}
|
||||
case EnterState.Entering:
|
||||
{
|
||||
// Check that we reached the requested position
|
||||
var targetPos = target.Positions.PositionClosestTo(self.CenterPosition);
|
||||
if (!IsCanceled && self.CenterPosition == targetPos && target.Type == TargetType.Actor)
|
||||
OnEnterComplete(self, target.Actor);
|
||||
|
||||
// Called when inner activity is this and returns inner activity for next tick.
|
||||
protected virtual Activity InsideTick(Actor self) { return null; }
|
||||
lastState = EnterState.Exiting;
|
||||
moveActivity = ActivityUtils.RunActivity(self, move.MoveIntoWorld(self, self.Location));
|
||||
break;
|
||||
}
|
||||
|
||||
// Abort entering and/or leave if necessary
|
||||
protected virtual void AbortOrExit(Actor self)
|
||||
{
|
||||
if (nextState == EnterState.Done)
|
||||
return;
|
||||
nextState = isEnteringOrInside ? EnterState.Exiting : EnterState.Done;
|
||||
if (inner == this)
|
||||
inner = null;
|
||||
else if (inner != null)
|
||||
inner.Cancel(self);
|
||||
if (isEnteringOrInside)
|
||||
Unreserve(self, true);
|
||||
}
|
||||
case EnterState.Exiting:
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
// Cancel inner activity and mark as done unless already leaving or done
|
||||
protected void Done(Actor self)
|
||||
{
|
||||
if (nextState == EnterState.Done)
|
||||
return;
|
||||
nextState = EnterState.Done;
|
||||
if (inner == this)
|
||||
inner = null;
|
||||
else if (inner != null)
|
||||
inner.Cancel(self);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
AbortOrExit(self);
|
||||
if (nextState < EnterState.Exiting)
|
||||
return base.Cancel(self);
|
||||
else
|
||||
NextActivity = null;
|
||||
OnCancel(self);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (!IsCanceled && moveActivity != null && !moveActivity.Cancel(self))
|
||||
return false;
|
||||
|
||||
ReserveStatus TryReserveElseTryAlternateReserve(Actor self)
|
||||
{
|
||||
for (var tries = 0;;)
|
||||
{
|
||||
switch (Reserve(self))
|
||||
{
|
||||
case ReserveStatus.None:
|
||||
if (++tries > maxTries || !TryGetAlternateTarget(self, tries, ref target))
|
||||
return ReserveStatus.None;
|
||||
continue;
|
||||
case ReserveStatus.TooFar:
|
||||
// Always goto to transport on first approach
|
||||
if (firstApproach)
|
||||
{
|
||||
firstApproach = false;
|
||||
return ReserveStatus.TooFar;
|
||||
}
|
||||
|
||||
if (++tries > maxTries)
|
||||
return ReserveStatus.TooFar;
|
||||
Target t = target;
|
||||
if (!TryGetAlternateTarget(self, tries, ref t))
|
||||
return ReserveStatus.TooFar;
|
||||
|
||||
var targetPosition = target.Positions.PositionClosestTo(self.CenterPosition);
|
||||
var alternatePosition = t.Positions.PositionClosestTo(self.CenterPosition);
|
||||
if ((targetPosition - self.CenterPosition).HorizontalLengthSquared <= (alternatePosition - self.CenterPosition).HorizontalLengthSquared)
|
||||
return ReserveStatus.TooFar;
|
||||
target = t;
|
||||
continue;
|
||||
case ReserveStatus.Pending:
|
||||
return ReserveStatus.Pending;
|
||||
case ReserveStatus.Ready:
|
||||
return ReserveStatus.Ready;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnterState FindAndTransitionToNextState(Actor self)
|
||||
{
|
||||
switch (nextState)
|
||||
{
|
||||
case EnterState.ApproachingOrEntering:
|
||||
|
||||
// Reserve to enter or approach
|
||||
isEnteringOrInside = false;
|
||||
switch (TryReserveElseTryAlternateReserve(self))
|
||||
{
|
||||
case ReserveStatus.None:
|
||||
return EnterState.Done; // No available target -> abort to next activity
|
||||
case ReserveStatus.TooFar:
|
||||
{
|
||||
var moveTarget = repathWhileMoving ? target : Target.FromPos(target.Positions.PositionClosestTo(self.CenterPosition));
|
||||
inner = move.MoveToTarget(self, moveTarget); // Approach
|
||||
return EnterState.ApproachingOrEntering;
|
||||
}
|
||||
|
||||
case ReserveStatus.Pending:
|
||||
return EnterState.ApproachingOrEntering; // Retry next tick
|
||||
case ReserveStatus.Ready:
|
||||
break; // Reserved target -> start entering target
|
||||
}
|
||||
|
||||
// Can we enter yet?
|
||||
if (!TryStartEnter(self))
|
||||
return EnterState.WaitingToEnter;
|
||||
|
||||
// Entering
|
||||
isEnteringOrInside = true;
|
||||
savedPos = self.CenterPosition; // Save position of self, before entering, for returning on exit
|
||||
|
||||
inner = move.MoveIntoTarget(self, target); // Enter
|
||||
|
||||
if (inner != null)
|
||||
{
|
||||
nextState = EnterState.Inside; // Should be inside once inner activity is null
|
||||
return EnterState.ApproachingOrEntering;
|
||||
}
|
||||
|
||||
// Can enter but there is no activity for it, so go inside without one
|
||||
goto case EnterState.Inside;
|
||||
|
||||
case EnterState.Inside:
|
||||
// Might as well teleport into target if there is no MoveIntoTarget activity
|
||||
if (nextState == EnterState.ApproachingOrEntering)
|
||||
nextState = EnterState.Inside;
|
||||
|
||||
// Otherwise, try to recover from moving target
|
||||
else if (target.Positions.PositionClosestTo(self.CenterPosition) != self.CenterPosition)
|
||||
{
|
||||
nextState = EnterState.ApproachingOrEntering;
|
||||
Unreserve(self, false);
|
||||
if (Reserve(self) == ReserveStatus.Ready)
|
||||
{
|
||||
inner = move.MoveIntoTarget(self, target); // Enter
|
||||
if (inner != null)
|
||||
return EnterState.ApproachingOrEntering;
|
||||
|
||||
nextState = EnterState.ApproachingOrEntering;
|
||||
goto case EnterState.ApproachingOrEntering;
|
||||
}
|
||||
|
||||
nextState = EnterState.ApproachingOrEntering;
|
||||
isEnteringOrInside = false;
|
||||
inner = move.MoveIntoWorld(self, self.World.Map.CellContaining(savedPos));
|
||||
|
||||
return EnterState.ApproachingOrEntering;
|
||||
}
|
||||
|
||||
OnInside(self);
|
||||
|
||||
if (enterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
else if (enterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
|
||||
// Return if Abort(Actor) or Done(self) was called from OnInside.
|
||||
if (nextState >= EnterState.Exiting)
|
||||
return EnterState.Inside;
|
||||
|
||||
inner = this; // Start inside activity
|
||||
nextState = EnterState.Exiting; // Exit once inner activity is null (unless Done(self) is called)
|
||||
return EnterState.Inside;
|
||||
|
||||
// TODO: Handle target moved while inside or always call done for movable targets and use a separate exit activity
|
||||
case EnterState.Exiting:
|
||||
inner = move.MoveIntoWorld(self, self.World.Map.CellContaining(savedPos));
|
||||
|
||||
// If not successfully exiting, retry on next tick
|
||||
if (inner == null)
|
||||
return EnterState.Exiting;
|
||||
isEnteringOrInside = false;
|
||||
nextState = EnterState.Done;
|
||||
return EnterState.Exiting;
|
||||
|
||||
case EnterState.Done:
|
||||
return EnterState.Done;
|
||||
}
|
||||
|
||||
return EnterState.Done; // dummy to quiet dumb compiler
|
||||
}
|
||||
|
||||
Activity CanceledTick(Actor self)
|
||||
{
|
||||
if (inner == null)
|
||||
return ActivityUtils.RunActivity(self, NextActivity);
|
||||
inner.Cancel(self);
|
||||
inner.Queue(NextActivity);
|
||||
return ActivityUtils.RunActivity(self, inner);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return CanceledTick(self);
|
||||
|
||||
// Check target validity if not exiting or done
|
||||
if (nextState != EnterState.Done && (target.Type != TargetType.Actor || !target.IsValidFor(self)))
|
||||
AbortOrExit(self);
|
||||
|
||||
// If no current activity, tick next activity
|
||||
if (inner == null && FindAndTransitionToNextState(self) == EnterState.Done)
|
||||
return CanceledTick(self);
|
||||
|
||||
// Run inner activity/InsideTick
|
||||
inner = inner == this ? InsideTick(self) : ActivityUtils.RunActivity(self, inner);
|
||||
|
||||
// If we are finished, move on to next activity
|
||||
if (inner == null && nextState == EnterState.Done)
|
||||
return NextActivity;
|
||||
|
||||
return this;
|
||||
return base.Cancel(self, keepQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -18,59 +21,124 @@ namespace OpenRA.Mods.Common.Activities
|
||||
class EnterTransport : Enter
|
||||
{
|
||||
readonly Passenger passenger;
|
||||
readonly int maxTries;
|
||||
Actor transport;
|
||||
Cargo cargo;
|
||||
|
||||
public EnterTransport(Actor self, Actor transport, int maxTries = 0, bool repathWhileMoving = true)
|
||||
: base(self, transport, EnterBehaviour.Exit, maxTries, repathWhileMoving)
|
||||
Actor enterActor;
|
||||
Cargo enterCargo;
|
||||
|
||||
public EnterTransport(Actor self, Target target)
|
||||
: base(self, target, Color.Green)
|
||||
{
|
||||
this.transport = transport;
|
||||
this.maxTries = maxTries;
|
||||
cargo = transport.Trait<Cargo>();
|
||||
passenger = self.Trait<Passenger>();
|
||||
}
|
||||
|
||||
protected override void Unreserve(Actor self, bool abort) { passenger.Unreserve(self); }
|
||||
protected override bool CanReserve(Actor self) { return cargo.Unloading || cargo.CanLoad(transport, self); }
|
||||
protected override ReserveStatus Reserve(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
var status = base.Reserve(self);
|
||||
if (status != ReserveStatus.Ready)
|
||||
return status;
|
||||
if (passenger.Reserve(self, cargo))
|
||||
return ReserveStatus.Ready;
|
||||
return ReserveStatus.Pending;
|
||||
enterActor = targetActor;
|
||||
enterCargo = targetActor.TraitOrDefault<Cargo>();
|
||||
|
||||
// Make sure we can still enter the transport
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (enterCargo == null || !passenger.Reserve(self, enterCargo))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (self.IsDead || transport.IsDead || !cargo.CanLoad(transport, self))
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
cargo.Load(transport, self);
|
||||
if (!enterCargo.CanLoad(enterActor, self))
|
||||
return;
|
||||
|
||||
enterCargo.Load(enterActor, self);
|
||||
w.Remove(self);
|
||||
|
||||
// Preemptively cancel any activities to avoid an edge-case where successively queued
|
||||
// EnterTransports corrupt the actor state. Activities are cancelled again on unload
|
||||
self.CancelActivity();
|
||||
});
|
||||
|
||||
Done(self);
|
||||
|
||||
// Preemptively cancel any activities to avoid an edge-case where successively queued
|
||||
// EnterTransports corrupt the actor state. Activities are cancelled again on unload
|
||||
self.CancelActivity();
|
||||
}
|
||||
|
||||
protected override bool TryGetAlternateTarget(Actor self, int tries, ref Target target)
|
||||
protected override void OnCancel(Actor self)
|
||||
{
|
||||
if (tries > maxTries)
|
||||
passenger.Unreserve(self);
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
passenger.Unreserve(self);
|
||||
}
|
||||
}
|
||||
|
||||
class EnterTransports : Activity
|
||||
{
|
||||
readonly string type;
|
||||
readonly Passenger passenger;
|
||||
|
||||
Activity enterTransport;
|
||||
|
||||
public EnterTransports(Actor self, Target primaryTarget)
|
||||
{
|
||||
passenger = self.Trait<Passenger>();
|
||||
if (primaryTarget.Type == TargetType.Actor)
|
||||
type = primaryTarget.Actor.Info.Name;
|
||||
|
||||
enterTransport = new EnterTransport(self, primaryTarget);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (enterTransport != null)
|
||||
{
|
||||
enterTransport = ActivityUtils.RunActivity(self, enterTransport);
|
||||
if (enterTransport != null)
|
||||
return this;
|
||||
}
|
||||
|
||||
// Try and find a new transport nearby
|
||||
if (IsCanceled || string.IsNullOrEmpty(type))
|
||||
return NextActivity;
|
||||
|
||||
Func<Actor, bool> isValidTransport = a =>
|
||||
{
|
||||
var c = a.TraitOrDefault<Cargo>();
|
||||
return c != null && c.Info.Types.Contains(passenger.Info.CargoType) &&
|
||||
(c.Unloading || c.CanLoad(a, self));
|
||||
};
|
||||
|
||||
var candidates = self.World.FindActorsInCircle(self.CenterPosition, passenger.Info.AlternateTransportScanRange)
|
||||
.Where(isValidTransport)
|
||||
.ToList();
|
||||
|
||||
// Prefer transports of the same type as the primary
|
||||
var transport = candidates.Where(a => a.Info.Name == type).ClosestTo(self);
|
||||
if (transport == null)
|
||||
transport = candidates.ClosestTo(self);
|
||||
|
||||
if (transport != null)
|
||||
{
|
||||
enterTransport = ActivityUtils.RunActivity(self, new EnterTransport(self, Target.FromActor(transport)));
|
||||
return this;
|
||||
}
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
public override bool Cancel(Actor self, bool keepQueue = false)
|
||||
{
|
||||
if (!IsCanceled && enterTransport != null && !enterTransport.Cancel(self))
|
||||
return false;
|
||||
var type = target.Actor.Info.Name;
|
||||
return TryGetAlternateTargetInCircle(
|
||||
self, passenger.Info.AlternateTransportScanRange,
|
||||
t => { transport = t.Actor; cargo = t.Actor.Trait<Cargo>(); }, // update transport and cargo
|
||||
a => { var c = a.TraitOrDefault<Cargo>(); return c != null && c.Info.Types.Contains(passenger.Info.CargoType) && (c.Unloading || c.CanLoad(a, self)); },
|
||||
new Func<Actor, bool>[] { a => a.Info.Name == type }); // Prefer transports of the same type
|
||||
|
||||
return base.Cancel(self, keepQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,10 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return NextActivity;
|
||||
|
||||
if (harv.IsFull)
|
||||
return ActivityUtils.SequenceActivities(new DeliverResources(self), NextActivity);
|
||||
{
|
||||
// HACK: DeliverResources is ignored if there are queued activities, so discard NextActivity
|
||||
return ActivityUtils.SequenceActivities(new DeliverResources(self));
|
||||
}
|
||||
|
||||
var closestHarvestablePosition = ClosestHarvestablePos(self);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -17,36 +18,74 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Follow : Activity
|
||||
{
|
||||
readonly Target target;
|
||||
readonly WDist minRange;
|
||||
readonly WDist maxRange;
|
||||
readonly IMove move;
|
||||
readonly Color? targetLineColor;
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
bool wasMovingWithinRange;
|
||||
|
||||
public Follow(Actor self, Target target, WDist minRange, WDist maxRange)
|
||||
public Follow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition, Color? targetLineColor = null)
|
||||
{
|
||||
this.target = target;
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
|
||||
this.targetLineColor = targetLineColor;
|
||||
move = self.Trait<IMove>();
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
else if (initialTargetPosition.HasValue)
|
||||
lastVisibleTarget = Target.FromPos(initialTargetPosition.Value);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var cachedPosition = target.CenterPosition;
|
||||
var path = move.MoveWithinRange(target, minRange, maxRange);
|
||||
bool targetIsHiddenActor;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
|
||||
// We are already in range, so wait until the target moves before doing anything
|
||||
if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange))
|
||||
{
|
||||
var wait = new WaitFor(() => !target.IsValidFor(self) || target.CenterPosition != cachedPosition);
|
||||
return ActivityUtils.SequenceActivities(wait, path, this);
|
||||
}
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
return ActivityUtils.SequenceActivities(path, this);
|
||||
// If we are ticking again after previously sequencing a MoveWithRange then that move must have completed
|
||||
// Either we are in range and can see the target, or we've lost track of it and should give up
|
||||
if (wasMovingWithinRange && targetIsHiddenActor)
|
||||
return NextActivity;
|
||||
|
||||
wasMovingWithinRange = false;
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// We've reached the required range - if the target is visible and valid then we wait
|
||||
// otherwise if it is hidden or dead we give up
|
||||
if (checkTarget.IsInRange(pos, maxRange) && !checkTarget.IsInRange(pos, minRange))
|
||||
return useLastVisibleTarget ? NextActivity : this;
|
||||
|
||||
// Move into range
|
||||
wasMovingWithinRange = true;
|
||||
return ActivityUtils.SequenceActivities(
|
||||
move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, targetLineColor),
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +386,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (Move.mobile.IsTraitDisabled)
|
||||
return this;
|
||||
|
||||
var ret = InnerTick(self, Move.mobile);
|
||||
Move.mobile.IsMoving = ret is MovePart;
|
||||
|
||||
|
||||
@@ -26,38 +26,45 @@ namespace OpenRA.Mods.Common.Activities
|
||||
protected readonly Mobile Mobile;
|
||||
readonly IPathFinder pathFinder;
|
||||
readonly DomainIndex domainIndex;
|
||||
readonly Color? targetLineColor;
|
||||
|
||||
Target target;
|
||||
bool canHideUnderFog;
|
||||
protected Target Target
|
||||
{
|
||||
get
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
target = value;
|
||||
if (target.Type == TargetType.Actor)
|
||||
canHideUnderFog = target.Actor.Info.HasTraitInfo<HiddenUnderFogInfo>();
|
||||
return useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
}
|
||||
}
|
||||
|
||||
protected CPos targetPosition;
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
protected CPos lastVisibleTargetLocation;
|
||||
bool useLastVisibleTarget;
|
||||
|
||||
Activity inner;
|
||||
bool repath;
|
||||
|
||||
public MoveAdjacentTo(Actor self, Target target)
|
||||
public MoveAdjacentTo(Actor self, Target target, WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
Target = target;
|
||||
|
||||
this.target = target;
|
||||
this.targetLineColor = targetLineColor;
|
||||
Mobile = self.Trait<Mobile>();
|
||||
pathFinder = self.World.WorldActor.Trait<IPathFinder>();
|
||||
domainIndex = self.World.WorldActor.Trait<DomainIndex>();
|
||||
|
||||
if (target.IsValidFor(self))
|
||||
targetPosition = self.World.Map.CellContaining(target.CenterPosition);
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition);
|
||||
}
|
||||
else if (initialTargetPosition.HasValue)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(initialTargetPosition.Value);
|
||||
lastVisibleTargetLocation = self.World.Map.CellContaining(initialTargetPosition.Value);
|
||||
}
|
||||
|
||||
repath = true;
|
||||
}
|
||||
@@ -67,9 +74,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldRepath(Actor self, CPos oldTargetPosition)
|
||||
protected virtual bool ShouldRepath(Actor self, CPos targetLocation)
|
||||
{
|
||||
return targetPosition != oldTargetPosition;
|
||||
return lastVisibleTargetLocation != targetLocation;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<CPos> CandidateMovementCells(Actor self)
|
||||
@@ -79,19 +86,30 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var targetIsValid = Target.IsValidFor(self);
|
||||
|
||||
// Target moved under the fog. Move to its last known position.
|
||||
if (Target.Type == TargetType.Actor && canHideUnderFog
|
||||
&& !Target.Actor.CanBeViewedByPlayer(self.Owner))
|
||||
bool targetIsHiddenActor;
|
||||
var oldTargetLocation = lastVisibleTargetLocation;
|
||||
target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
if (inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, targetPosition), Color.Green);
|
||||
return ActivityUtils.RunActivity(self, new AttackMoveActivity(self, Mobile.MoveTo(targetPosition, 0)));
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition);
|
||||
}
|
||||
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
// Target is equivalent to checkTarget variable in other activities
|
||||
// value is either lastVisibleTarget or target based on visibility and validity
|
||||
var targetIsValid = Target.IsValidFor(self);
|
||||
|
||||
// Inner move order has completed.
|
||||
if (inner == null)
|
||||
{
|
||||
@@ -105,26 +123,16 @@ namespace OpenRA.Mods.Common.Activities
|
||||
repath = false;
|
||||
}
|
||||
|
||||
if (targetIsValid)
|
||||
// Cancel the current path if the activity asks to stop, or asks to repath
|
||||
// The repath happens once the move activity stops in the next cell
|
||||
var shouldStop = ShouldStop(self, oldTargetLocation);
|
||||
var shouldRepath = targetIsValid && !repath && ShouldRepath(self, oldTargetLocation);
|
||||
if (shouldStop || shouldRepath)
|
||||
{
|
||||
// Check if the target has moved
|
||||
var oldTargetPosition = targetPosition;
|
||||
targetPosition = self.World.Map.CellContaining(Target.CenterPosition);
|
||||
if (inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
var shouldStop = ShouldStop(self, oldTargetPosition);
|
||||
if (shouldStop || (!repath && ShouldRepath(self, oldTargetPosition)))
|
||||
{
|
||||
// Finish moving into the next cell and then repath.
|
||||
if (inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
repath = !shouldStop;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Target became invalid. Move to its last known position.
|
||||
Target = Target.FromCell(self.World, targetPosition);
|
||||
repath = shouldRepath;
|
||||
}
|
||||
|
||||
// Ticks the inner move activity to actually move the actor.
|
||||
@@ -147,7 +155,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
return NoPath;
|
||||
|
||||
using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Info.LocomotorInfo, self, searchCells, loc, true))
|
||||
using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Info.LocomotorInfo, self, loc, targetPosition, true).Reverse())
|
||||
using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Info.LocomotorInfo, self, loc, lastVisibleTargetLocation, true).Reverse())
|
||||
return pathFinder.FindBidiPath(fromSrc, fromDest);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -20,8 +21,9 @@ namespace OpenRA.Mods.Common.Activities
|
||||
readonly WDist maxRange;
|
||||
readonly WDist minRange;
|
||||
|
||||
public MoveWithinRange(Actor self, Target target, WDist minRange, WDist maxRange)
|
||||
: base(self, target)
|
||||
public MoveWithinRange(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
: base(self, target, initialTargetPosition, targetLineColor)
|
||||
{
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
@@ -36,7 +38,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
|
||||
protected override bool ShouldRepath(Actor self, CPos oldTargetPosition)
|
||||
{
|
||||
return targetPosition != oldTargetPosition && (!AtCorrectRange(self.CenterPosition)
|
||||
return lastVisibleTargetLocation != oldTargetPosition && (!AtCorrectRange(self.CenterPosition)
|
||||
|| !Mobile.CanInteractWithGroundLayer(self));
|
||||
}
|
||||
|
||||
@@ -46,7 +48,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
var maxCells = (maxRange.Length + 1023) / 1024;
|
||||
var minCells = minRange.Length / 1024;
|
||||
|
||||
return map.FindTilesInAnnulus(targetPosition, minCells, maxCells)
|
||||
return map.FindTilesInAnnulus(lastVisibleTargetLocation, minCells, maxCells)
|
||||
.Where(c => AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell)));
|
||||
}
|
||||
|
||||
|
||||
97
OpenRA.Mods.Common/Activities/Move/VisualMoveIntoTarget.cs
Normal file
97
OpenRA.Mods.Common/Activities/Move/VisualMoveIntoTarget.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
#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 System.Collections.Generic;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class VisualMoveIntoTarget : Activity
|
||||
{
|
||||
readonly Mobile mobile;
|
||||
readonly Target target;
|
||||
readonly WDist targetMovementThreshold;
|
||||
WPos targetStartPos;
|
||||
|
||||
public VisualMoveIntoTarget(Actor self, Target target, WDist targetMovementThreshold)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
this.target = target;
|
||||
this.targetMovementThreshold = targetMovementThreshold;
|
||||
}
|
||||
|
||||
protected override void OnFirstRun(Actor self)
|
||||
{
|
||||
targetStartPos = target.Positions.PositionClosestTo(self.CenterPosition);
|
||||
mobile.IsMoving = true;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (ChildActivity != null)
|
||||
{
|
||||
ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
|
||||
if (ChildActivity != null)
|
||||
return this;
|
||||
}
|
||||
|
||||
if (IsCanceled || target.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
|
||||
if (mobile.IsTraitDisabled)
|
||||
return this;
|
||||
|
||||
var currentPos = self.CenterPosition;
|
||||
var targetPos = target.Positions.PositionClosestTo(currentPos);
|
||||
|
||||
// Give up if the target has moved too far
|
||||
if (targetMovementThreshold > WDist.Zero && (targetPos - targetStartPos).LengthSquared > targetMovementThreshold.LengthSquared)
|
||||
return NextActivity;
|
||||
|
||||
// Turn if required
|
||||
var delta = targetPos - currentPos;
|
||||
var facing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : mobile.Facing;
|
||||
if (facing != mobile.Facing)
|
||||
{
|
||||
var turn = ActivityUtils.RunActivity(self, new Turn(self, facing));
|
||||
if (turn != null)
|
||||
QueueChild(turn);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Can complete the move in this step
|
||||
var speed = mobile.MovementSpeedForCell(self, self.Location);
|
||||
if (delta.LengthSquared <= speed * speed)
|
||||
{
|
||||
mobile.SetVisualPosition(self, targetPos);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
// Move towards the target
|
||||
mobile.SetVisualPosition(self, currentPos + delta * speed / delta.Length);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
mobile.IsMoving = false;
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
@@ -72,7 +73,8 @@ namespace OpenRA.Mods.Common.Activities
|
||||
switch (state)
|
||||
{
|
||||
case PickupState.Intercept:
|
||||
innerActivity = movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4));
|
||||
innerActivity = movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4),
|
||||
targetLineColor: Color.Yellow);
|
||||
state = PickupState.LockCarryable;
|
||||
return this;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -16,49 +17,70 @@ namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class RepairBridge : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly LegacyBridgeHut legacyHut;
|
||||
readonly BridgeHut hut;
|
||||
readonly EnterBehaviour enterBehaviour;
|
||||
readonly string notification;
|
||||
|
||||
public RepairBridge(Actor self, Actor target, EnterBehaviour enterBehaviour, string notification)
|
||||
: base(self, target, enterBehaviour)
|
||||
Actor enterActor;
|
||||
BridgeHut enterHut;
|
||||
LegacyBridgeHut enterLegacyHut;
|
||||
|
||||
public RepairBridge(Actor self, Target target, EnterBehaviour enterBehaviour, string notification)
|
||||
: base(self, target, Color.Yellow)
|
||||
{
|
||||
this.target = target;
|
||||
legacyHut = target.TraitOrDefault<LegacyBridgeHut>();
|
||||
hut = target.TraitOrDefault<BridgeHut>();
|
||||
this.enterBehaviour = enterBehaviour;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
protected override bool CanReserve(Actor self)
|
||||
bool CanEnterHut()
|
||||
{
|
||||
if (legacyHut != null)
|
||||
return legacyHut.BridgeDamageState != DamageState.Undamaged && !legacyHut.Repairing && legacyHut.Bridge.GetHut(0) != null && legacyHut.Bridge.GetHut(1) != null;
|
||||
if (enterLegacyHut != null)
|
||||
return enterLegacyHut.BridgeDamageState != DamageState.Undamaged && !enterLegacyHut.Repairing &&
|
||||
enterLegacyHut.Bridge.GetHut(0) != null && enterLegacyHut.Bridge.GetHut(1) != null;
|
||||
|
||||
if (hut != null)
|
||||
return hut.BridgeDamageState != DamageState.Undamaged && !hut.Repairing;
|
||||
if (enterHut != null)
|
||||
return enterHut.BridgeDamageState != DamageState.Undamaged && !enterHut.Repairing;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
if (legacyHut != null)
|
||||
{
|
||||
if (legacyHut.BridgeDamageState == DamageState.Undamaged || legacyHut.Repairing || legacyHut.Bridge.GetHut(0) == null || legacyHut.Bridge.GetHut(1) == null)
|
||||
return;
|
||||
enterActor = targetActor;
|
||||
enterLegacyHut = enterActor.TraitOrDefault<LegacyBridgeHut>();
|
||||
enterHut = enterActor.TraitOrDefault<BridgeHut>();
|
||||
|
||||
legacyHut.Repair(self);
|
||||
}
|
||||
else if (hut != null)
|
||||
// Make sure we can still repair the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
if (!CanEnterHut())
|
||||
{
|
||||
if (hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing)
|
||||
return;
|
||||
|
||||
hut.Repair(target, self);
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
if (!CanEnterHut())
|
||||
return;
|
||||
|
||||
if (enterLegacyHut != null)
|
||||
enterLegacyHut.Repair(self);
|
||||
else if (enterHut != null)
|
||||
enterHut.Repair(enterActor, self);
|
||||
|
||||
Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", notification, self.Owner.Faction.InternalName);
|
||||
|
||||
if (enterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
else if (enterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,39 +9,66 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class RepairBuilding : Enter
|
||||
{
|
||||
readonly Actor target;
|
||||
readonly IHealth health;
|
||||
readonly EnterBehaviour enterBehaviour;
|
||||
readonly Stance validStances;
|
||||
|
||||
public RepairBuilding(Actor self, Actor target, EnterBehaviour enterBehaviour, Stance validStances)
|
||||
: base(self, target, enterBehaviour)
|
||||
Actor enterActor;
|
||||
IHealth enterHealth;
|
||||
|
||||
public RepairBuilding(Actor self, Target target, EnterBehaviour enterBehaviour, Stance validStances)
|
||||
: base(self, target, Color.Yellow)
|
||||
{
|
||||
this.target = target;
|
||||
this.enterBehaviour = enterBehaviour;
|
||||
this.validStances = validStances;
|
||||
health = target.Trait<IHealth>();
|
||||
}
|
||||
|
||||
protected override bool CanReserve(Actor self)
|
||||
protected override bool TryStartEnter(Actor self, Actor targetActor)
|
||||
{
|
||||
return health.DamageState != DamageState.Undamaged;
|
||||
enterActor = targetActor;
|
||||
enterHealth = targetActor.TraitOrDefault<IHealth>();
|
||||
|
||||
// Make sure we can still repair the target before entering
|
||||
// (but not before, because this may stop the actor in the middle of nowhere)
|
||||
var stance = self.Owner.Stances[enterActor.Owner];
|
||||
if (enterHealth == null || enterHealth.DamageState == DamageState.Undamaged || !stance.HasStance(validStances))
|
||||
{
|
||||
Cancel(self, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
protected override void OnEnterComplete(Actor self, Actor targetActor)
|
||||
{
|
||||
var stance = self.Owner.Stances[target.Owner];
|
||||
// Make sure the target hasn't changed while entering
|
||||
// OnEnterComplete is only called if targetActor is alive
|
||||
if (targetActor != enterActor)
|
||||
return;
|
||||
|
||||
if (enterHealth.DamageState == DamageState.Undamaged)
|
||||
return;
|
||||
|
||||
var stance = self.Owner.Stances[enterActor.Owner];
|
||||
if (!stance.HasStance(validStances))
|
||||
return;
|
||||
|
||||
if (health.DamageState == DamageState.Undamaged)
|
||||
if (enterHealth.DamageState == DamageState.Undamaged)
|
||||
return;
|
||||
|
||||
target.InflictDamage(self, new Damage(-health.MaxHP));
|
||||
enterActor.InflictDamage(self, new Damage(-enterHealth.MaxHP));
|
||||
|
||||
if (enterBehaviour == EnterBehaviour.Dispose)
|
||||
self.Dispose();
|
||||
else if (enterBehaviour == EnterBehaviour.Suicide)
|
||||
self.Kill(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
self.ReplacedByActor = a;
|
||||
|
||||
if (selected)
|
||||
w.Selection.Add(w, a);
|
||||
w.Selection.Add(a);
|
||||
|
||||
if (controlgroup.HasValue)
|
||||
w.Selection.AddToControlGroup(a, controlgroup.Value);
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Activities
|
||||
protected override void OnLastRun(Actor self)
|
||||
{
|
||||
// If Mobile.IsMoving was set to 'true' earlier, we want to reset it to 'false' before the next tick.
|
||||
if (mobile != null && mobile.IsMoving)
|
||||
if (setIsMoving && mobile != null && mobile.IsMoving)
|
||||
mobile.IsMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,42 +59,6 @@ namespace OpenRA.Mods.Common
|
||||
return stance == Stance.Enemy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DEPRECATED: Write code that can handle FrozenActors correctly instead.
|
||||
/// </summary>
|
||||
public static Target ResolveFrozenActorOrder(this Actor self, Order order, Color targetLine)
|
||||
{
|
||||
// Not targeting a frozen actor
|
||||
if (order.Target.Type != TargetType.FrozenActor)
|
||||
return order.Target;
|
||||
|
||||
var frozen = order.Target.FrozenActor;
|
||||
|
||||
self.SetTargetLine(order.Target, targetLine, true);
|
||||
|
||||
// Target is still alive - resolve the real order
|
||||
if (frozen.Actor != null && frozen.Actor.IsInWorld)
|
||||
return Target.FromActor(frozen.Actor);
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
var move = self.TraitOrDefault<IMove>();
|
||||
if (move != null)
|
||||
{
|
||||
// Move within sight range of the frozen actor
|
||||
var range = self.TraitsImplementing<RevealsShroud>()
|
||||
.Where(s => !s.IsTraitDisabled)
|
||||
.Select(s => s.Range)
|
||||
.Append(WDist.FromCells(2))
|
||||
.Max();
|
||||
|
||||
self.QueueActivity(move.MoveWithinRange(Target.FromPos(frozen.CenterPosition), range));
|
||||
}
|
||||
|
||||
return Target.Invalid;
|
||||
}
|
||||
|
||||
public static void NotifyBlocker(this Actor self, IEnumerable<Actor> blockers)
|
||||
{
|
||||
foreach (var blocker in blockers)
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
<Compile Include="Activities\Move\Move.cs" />
|
||||
<Compile Include="Activities\Move\MoveAdjacentTo.cs" />
|
||||
<Compile Include="Activities\Move\MoveWithinRange.cs" />
|
||||
<Compile Include="Activities\Move\VisualMoveIntoTarget.cs" />
|
||||
<Compile Include="Activities\Parachute.cs" />
|
||||
<Compile Include="Activities\Rearm.cs" />
|
||||
<Compile Include="Activities\RemoveSelf.cs" />
|
||||
@@ -120,6 +121,7 @@
|
||||
<Compile Include="Activities\WaitForTransport.cs" />
|
||||
<Compile Include="ActorExts.cs" />
|
||||
<Compile Include="AIUtils.cs" />
|
||||
<Compile Include="TargetExtensions.cs" />
|
||||
<Compile Include="Traits\BotModules\Squads\AttackOrFleeFuzzy.cs" />
|
||||
<Compile Include="Traits\BotModules\BotModuleLogic\BaseBuilderQueueManager.cs" />
|
||||
<Compile Include="Traits\Player\ModularBot.cs" />
|
||||
@@ -288,7 +290,6 @@
|
||||
<Compile Include="Traits\Attack\AttackFrontal.cs" />
|
||||
<Compile Include="Traits\Attack\AttackGarrisoned.cs" />
|
||||
<Compile Include="Traits\Attack\AttackOmni.cs" />
|
||||
<Compile Include="Traits\Attack\AttackSuicides.cs" />
|
||||
<Compile Include="Traits\Attack\AttackTurreted.cs" />
|
||||
<Compile Include="Traits\AttackWander.cs" />
|
||||
<Compile Include="Traits\AutoTarget.cs" />
|
||||
@@ -599,6 +600,7 @@
|
||||
<Compile Include="Traits\World\ActorSpawnManager.cs" />
|
||||
<Compile Include="Traits\ActorSpawner.cs" />
|
||||
<Compile Include="UpdateRules\Rules\20181215\RemoveAttackIgnoresVisibility.cs" />
|
||||
<Compile Include="UpdateRules\Rules\20181215\RemoveAttackSuicides.cs" />
|
||||
<Compile Include="UpdateRules\Rules\20181215\RemovedDemolishLocking.cs" />
|
||||
<Compile Include="UpdateRules\Rules\20181215\RemoveNegativeDamageFullHealthCheck.cs" />
|
||||
<Compile Include="UpdateRules\Rules\20181215\RemoveResourceExplodeModifier.cs" />
|
||||
|
||||
@@ -211,8 +211,27 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
return;
|
||||
|
||||
if (!group.Any())
|
||||
using (f)
|
||||
f.Call().Dispose();
|
||||
{
|
||||
// Functions can only be .Call()ed once, so operate on a copy so we can reuse it later
|
||||
var temp = (LuaFunction)f.CopyReference();
|
||||
using (temp)
|
||||
temp.Call().Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Context.FatalError(e.Message);
|
||||
}
|
||||
};
|
||||
|
||||
Action<Actor> onMemberAdded = m =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!actors.Contains(m) || group.Contains(m))
|
||||
return;
|
||||
|
||||
group.Add(m);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -221,7 +240,10 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
};
|
||||
|
||||
foreach (var a in group)
|
||||
{
|
||||
GetScriptTriggers(a).OnRemovedInternal += onMemberRemoved;
|
||||
GetScriptTriggers(a).OnAddedInternal += onMemberAdded;
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("Call a function when this actor is captured. The callback function " +
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
if (targetManager == null || !targetManager.CanBeTargetedBy(target, Self, captureManager))
|
||||
throw new LuaException("Actor '{0}' cannot capture actor '{1}'!".F(Self, target));
|
||||
|
||||
Self.QueueActivity(new CaptureActor(Self, target));
|
||||
Self.QueueActivity(new CaptureActor(Self, Target.FromActor(target)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
[Desc("Deliver cash to the target actor.")]
|
||||
public void DeliverCash(Actor target)
|
||||
{
|
||||
Self.SetTargetLine(Target.FromActor(target), Color.Yellow);
|
||||
Self.QueueActivity(new DonateCash(Self, target, info.Payload, info.PlayerExperience));
|
||||
var t = Target.FromActor(target);
|
||||
Self.SetTargetLine(t, Color.Yellow);
|
||||
Self.QueueActivity(new DonateCash(Self, t, info.Payload, info.PlayerExperience));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +65,9 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
|
||||
var level = gainsExperience.Level;
|
||||
|
||||
Self.SetTargetLine(Target.FromActor(target), Color.Yellow);
|
||||
Self.QueueActivity(new DonateExperience(Self, target, level, deliversExperience.PlayerExperience, targetGainsExperience));
|
||||
var t = Target.FromActor(target);
|
||||
Self.SetTargetLine(t, Color.Yellow);
|
||||
Self.QueueActivity(new DonateExperience(Self, t, level, deliversExperience.PlayerExperience));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
[Desc("Demolish the target actor.")]
|
||||
public void Demolish(Actor target)
|
||||
{
|
||||
Self.QueueActivity(new Demolish(Self, target, info.EnterBehaviour, info.DetonationDelay,
|
||||
Self.QueueActivity(new Demolish(Self, Target.FromActor(target), info.EnterBehaviour, info.DetonationDelay,
|
||||
info.Flashes, info.FlashesDelay, info.FlashInterval));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
[Desc("Move to and enter the transport.")]
|
||||
public void EnterTransport(Actor transport)
|
||||
{
|
||||
Self.QueueActivity(new EnterTransport(Self, transport, 1, false));
|
||||
Self.QueueActivity(new EnterTransport(Self, Target.FromActor(transport)));
|
||||
}
|
||||
|
||||
[Desc("Whether the actor can move (false if immobilized).")]
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
public event Action<Actor> OnKilledInternal = _ => { };
|
||||
public event Action<Actor> OnCapturedInternal = _ => { };
|
||||
public event Action<Actor> OnRemovedInternal = _ => { };
|
||||
public event Action<Actor> OnAddedInternal = _ => { };
|
||||
public event Action<Actor, Actor> OnProducedInternal = (a, b) => { };
|
||||
public event Action<Actor, Actor> OnOtherProducedInternal = (a, b) => { };
|
||||
|
||||
@@ -340,6 +341,9 @@ namespace OpenRA.Mods.Common.Scripting
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Run any internally bound callbacks
|
||||
OnAddedInternal(self);
|
||||
}
|
||||
|
||||
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self)
|
||||
|
||||
@@ -654,6 +654,7 @@ namespace OpenRA.Mods.Common.Server
|
||||
targetClient.SpawnPoint = 0;
|
||||
targetClient.Team = 0;
|
||||
targetClient.Color = HSLColor.FromRGB(255, 255, 255);
|
||||
targetClient.State = Session.ClientState.NotReady;
|
||||
server.SendMessage("{0} moved {1} to spectators.".F(client.Name, targetClient.Name));
|
||||
Log.Write("server", "{0} moved {1} to spectators.".F(client.Name, targetClient.Name));
|
||||
server.SyncLobbyClients();
|
||||
|
||||
79
OpenRA.Mods.Common/TargetExtensions.cs
Normal file
79
OpenRA.Mods.Common/TargetExtensions.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#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
|
||||
{
|
||||
public static class TargetExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Update (Frozen)Actor targets to account for visibility changes or actor replacement.
|
||||
/// If the target actor becomes hidden without a FrozenActor, the target is invalidated.
|
||||
/// /// </summary>
|
||||
public static Target RecalculateInvalidatingHiddenTargets(this Target t, Player viewer)
|
||||
{
|
||||
bool targetIsHiddenActor;
|
||||
var updated = t.Recalculate(viewer, out targetIsHiddenActor);
|
||||
return targetIsHiddenActor ? Target.Invalid : updated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update (Frozen)Actor targets to account for visibility changes or actor replacement.
|
||||
/// If the target actor becomes hidden without a FrozenActor, the target actor is kept
|
||||
/// and the actorHidden flag is set to true.
|
||||
/// </summary>
|
||||
public static Target Recalculate(this Target t, Player viewer, out bool targetIsHiddenActor)
|
||||
{
|
||||
targetIsHiddenActor = false;
|
||||
|
||||
// Check whether the target has transformed into something else
|
||||
// HACK: This relies on knowing the internal implementation details of Target
|
||||
if (t.Type == TargetType.Invalid && t.Actor != null && t.Actor.ReplacedByActor != null)
|
||||
t = Target.FromActor(t.Actor.ReplacedByActor);
|
||||
|
||||
// Bot-controlled units aren't yet capable of understanding visibility changes
|
||||
if (viewer.IsBot)
|
||||
return t;
|
||||
|
||||
if (t.Type == TargetType.Actor)
|
||||
{
|
||||
// Actor has been hidden under the fog
|
||||
if (!t.Actor.CanBeViewedByPlayer(viewer))
|
||||
{
|
||||
// Replace with FrozenActor if applicable, otherwise return target unmodified
|
||||
var frozen = viewer.FrozenActorLayer.FromID(t.Actor.ActorID);
|
||||
if (frozen != null)
|
||||
return Target.FromFrozenActor(frozen);
|
||||
|
||||
targetIsHiddenActor = true;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
else if (t.Type == TargetType.FrozenActor)
|
||||
{
|
||||
// Frozen actor has been revealed
|
||||
if (!t.FrozenActor.Visible || !t.FrozenActor.IsValid)
|
||||
{
|
||||
// Original actor is still alive
|
||||
if (t.FrozenActor.Actor != null && t.FrozenActor.Actor.CanBeViewedByPlayer(viewer))
|
||||
return Target.FromActor(t.FrozenActor.Actor);
|
||||
|
||||
// Original actor was killed while hidden
|
||||
if (t.Actor == null)
|
||||
return Target.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,6 +174,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
RepairableInfo repairableInfo;
|
||||
RearmableInfo rearmableInfo;
|
||||
AttackMove attackMove;
|
||||
ConditionManager conditionManager;
|
||||
IDisposable reservation;
|
||||
IEnumerable<int> speedModifiers;
|
||||
@@ -231,6 +232,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
repairableInfo = self.Info.TraitInfoOrDefault<RepairableInfo>();
|
||||
rearmableInfo = self.Info.TraitInfoOrDefault<RearmableInfo>();
|
||||
attackMove = self.TraitOrDefault<AttackMove>();
|
||||
conditionManager = self.TraitOrDefault<ConditionManager>();
|
||||
speedModifiers = self.TraitsImplementing<ISpeedModifier>().ToArray().Select(sm => sm.GetSpeedModifier());
|
||||
cachedPosition = self.CenterPosition;
|
||||
@@ -522,7 +524,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void INotifyBecomingIdle.OnBecomingIdle(Actor self)
|
||||
{
|
||||
OnBecomingIdle(self);
|
||||
// HACK: Work around AttackMove relying on INotifyIdle.TickIdle to continue its path
|
||||
// AttackMoveActivity needs to be rewritten to use child activities instead!
|
||||
if (attackMove != null && attackMove.TargetLocation.HasValue)
|
||||
((INotifyIdle)attackMove).TickIdle(self);
|
||||
else
|
||||
OnBecomingIdle(self);
|
||||
}
|
||||
|
||||
protected virtual void OnBecomingIdle(Actor self)
|
||||
@@ -605,28 +612,35 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return new HeliFly(self, Target.FromCell(self.World, cell));
|
||||
}
|
||||
|
||||
public Activity MoveWithinRange(Target target, WDist range)
|
||||
public Activity MoveWithinRange(Target target, WDist range,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
if (!Info.CanHover)
|
||||
return new Fly(self, target, WDist.Zero, range);
|
||||
return new Fly(self, target, WDist.Zero, range, initialTargetPosition, targetLineColor);
|
||||
|
||||
return new HeliFly(self, target, WDist.Zero, range);
|
||||
return new HeliFly(self, target, WDist.Zero, range, initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange)
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
if (!Info.CanHover)
|
||||
return new Fly(self, target, minRange, maxRange);
|
||||
return new Fly(self, target, minRange, maxRange,
|
||||
initialTargetPosition, targetLineColor);
|
||||
|
||||
return new HeliFly(self, target, minRange, maxRange);
|
||||
return new HeliFly(self, target, minRange, maxRange,
|
||||
initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange)
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
if (!Info.CanHover)
|
||||
return new FlyFollow(self, target, minRange, maxRange);
|
||||
return new FlyFollow(self, target, minRange, maxRange,
|
||||
initialTargetPosition, targetLineColor);
|
||||
|
||||
return new Follow(self, target, minRange, maxRange);
|
||||
return new Follow(self, target, minRange, maxRange,
|
||||
initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
@@ -637,12 +651,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return new HeliFly(self, Target.FromCell(self.World, cell, subCell));
|
||||
}
|
||||
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
public Activity MoveToTarget(Actor self, Target target,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
if (!Info.CanHover)
|
||||
return new Fly(self, target, WDist.FromCells(3), WDist.FromCells(5));
|
||||
return new Fly(self, target, WDist.FromCells(3), WDist.FromCells(5),
|
||||
initialTargetPosition, targetLineColor);
|
||||
|
||||
return ActivityUtils.SequenceActivities(new HeliFly(self, target), new Turn(self, Info.InitialFacing));
|
||||
return ActivityUtils.SequenceActivities(
|
||||
new HeliFly(self, target, initialTargetPosition, targetLineColor),
|
||||
new Turn(self, Info.InitialFacing));
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target)
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Allow firing into the fog to target frozen actors without requiring force-fire.")]
|
||||
public readonly bool TargetFrozenActors = false;
|
||||
|
||||
[Desc("Force-fire mode ignores actors and targets the ground instead.")]
|
||||
public readonly bool ForceFireIgnoresActors = false;
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
public override abstract object Create(ActorInitializer init);
|
||||
@@ -139,8 +142,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (armament == null)
|
||||
yield break;
|
||||
|
||||
var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0;
|
||||
yield return new AttackOrderTargeter(this, 6, negativeDamage);
|
||||
yield return new AttackOrderTargeter(this, 6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,8 +360,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.CancelActivity();
|
||||
|
||||
self.QueueActivity(GetAttackActivity(self, target, allowMove, forceAttack));
|
||||
OnQueueAttackActivity(self, target, queued, allowMove, forceAttack);
|
||||
}
|
||||
|
||||
public virtual void OnQueueAttackActivity(Actor self, Target target, bool queued, bool allowMove, bool forceAttack) { }
|
||||
|
||||
public bool IsReachableTarget(Target target, bool allowMove)
|
||||
{
|
||||
return HasAnyValidWeapons(target)
|
||||
@@ -381,7 +386,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly AttackBase ab;
|
||||
|
||||
public AttackOrderTargeter(AttackBase ab, int priority, bool negativeDamage)
|
||||
public AttackOrderTargeter(AttackBase ab, int priority)
|
||||
{
|
||||
this.ab = ab;
|
||||
OrderID = ab.attackOrderName;
|
||||
@@ -399,6 +404,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
|
||||
if (ab.Info.ForceFireIgnoresActors && modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return false;
|
||||
|
||||
// Disguised actors are revealed by the attack cursor
|
||||
// HACK: works around limitations in the targeting code that force the
|
||||
// targeting and attacking logic (which should be logically separate)
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
@@ -20,27 +20,92 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[Desc("Actor will follow units until in range to attack them.")]
|
||||
public class AttackFollowInfo : AttackBaseInfo
|
||||
{
|
||||
[Desc("Automatically acquire and fire on targets of opportunity when not actively attacking.")]
|
||||
public readonly bool OpportunityFire = true;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackFollow(init.Self, this); }
|
||||
}
|
||||
|
||||
public class AttackFollow : AttackBase, INotifyOwnerChanged
|
||||
{
|
||||
public Target Target { get; protected set; }
|
||||
protected Target requestedTarget;
|
||||
protected bool requestedForceAttack;
|
||||
protected int requestedTargetLastTick;
|
||||
protected Target opportunityTarget;
|
||||
protected bool opportunityForceAttack;
|
||||
Mobile mobile;
|
||||
AutoTarget autoTarget;
|
||||
|
||||
public AttackFollow(Actor self, AttackFollowInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
mobile = self.TraitOrDefault<Mobile>();
|
||||
autoTarget = self.TraitOrDefault<AutoTarget>();
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
protected bool CanAimAtTarget(Actor self, Target target, bool forceAttack)
|
||||
{
|
||||
if (target.Type == TargetType.Actor && !target.Actor.CanBeViewedByPlayer(self.Owner))
|
||||
return false;
|
||||
|
||||
if (target.Type == TargetType.FrozenActor && !target.FrozenActor.IsValid)
|
||||
return false;
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var armaments = ChooseArmamentsForTarget(target, forceAttack);
|
||||
foreach (var a in armaments)
|
||||
if (target.IsInRange(pos, a.MaxRange()) && (a.Weapon.MinRange == WDist.Zero || !target.IsInRange(pos, a.Weapon.MinRange)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void Tick(Actor self)
|
||||
{
|
||||
Target = Target.Recalculate(self.Owner);
|
||||
if (IsTraitDisabled)
|
||||
requestedTarget = opportunityTarget = Target.Invalid;
|
||||
|
||||
if (requestedTargetLastTick != self.World.WorldTick)
|
||||
{
|
||||
Target = Target.Invalid;
|
||||
return;
|
||||
// Activities tick before traits, so if we are here it means the activity didn't run
|
||||
// (either queued next or already cancelled) and we need to recalculate the target ourself
|
||||
bool targetIsHiddenActor;
|
||||
requestedTarget = requestedTarget.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
}
|
||||
|
||||
DoAttack(self, Target);
|
||||
IsAiming = Target.IsValidFor(self);
|
||||
// Can't fire on anything
|
||||
if (mobile != null && !mobile.CanInteractWithGroundLayer(self))
|
||||
return;
|
||||
|
||||
if (requestedTarget.Type != TargetType.Invalid)
|
||||
{
|
||||
IsAiming = CanAimAtTarget(self, requestedTarget, requestedForceAttack);
|
||||
if (IsAiming)
|
||||
DoAttack(self, requestedTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsAiming = false;
|
||||
|
||||
if (opportunityTarget.Type != TargetType.Invalid)
|
||||
IsAiming = CanAimAtTarget(self, opportunityTarget, opportunityForceAttack);
|
||||
|
||||
if (!IsAiming && ((AttackFollowInfo)Info).OpportunityFire && autoTarget != null &&
|
||||
!autoTarget.IsTraitDisabled && autoTarget.Stance >= UnitStance.Defend)
|
||||
{
|
||||
opportunityTarget = autoTarget.ScanForTarget(self, false);
|
||||
opportunityForceAttack = false;
|
||||
|
||||
if (opportunityTarget.Type != TargetType.Invalid)
|
||||
IsAiming = CanAimAtTarget(self, opportunityTarget, opportunityForceAttack);
|
||||
}
|
||||
|
||||
if (IsAiming)
|
||||
DoAttack(self, opportunityTarget);
|
||||
}
|
||||
|
||||
base.Tick(self);
|
||||
}
|
||||
@@ -50,15 +115,28 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return new AttackActivity(self, newTarget, allowMove, forceAttack);
|
||||
}
|
||||
|
||||
public override void OnQueueAttackActivity(Actor self, Target target, bool queued, bool allowMove, bool forceAttack)
|
||||
{
|
||||
// If not queued we know that the attack activity will run next
|
||||
// We can improve responsiveness for turreted actors by preempting
|
||||
// the last order (usually a move) and set the target immediately
|
||||
if (!queued)
|
||||
{
|
||||
requestedTarget = target;
|
||||
requestedForceAttack = forceAttack;
|
||||
requestedTargetLastTick = self.World.WorldTick;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopOrder(Actor self)
|
||||
{
|
||||
Target = Target.Invalid;
|
||||
requestedTarget = opportunityTarget = Target.Invalid;
|
||||
base.OnStopOrder(self);
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
Target = Target.Invalid;
|
||||
requestedTarget = opportunityTarget = Target.Invalid;
|
||||
}
|
||||
|
||||
class AttackActivity : Activity
|
||||
@@ -67,7 +145,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
readonly RevealsShroud[] revealsShroud;
|
||||
readonly IMove move;
|
||||
readonly bool forceAttack;
|
||||
|
||||
Target target;
|
||||
Target lastVisibleTarget;
|
||||
bool useLastVisibleTarget;
|
||||
WDist lastVisibleMaximumRange;
|
||||
WDist lastVisibleMinimumRange;
|
||||
bool wasMovingWithinRange;
|
||||
bool hasTicked;
|
||||
|
||||
public AttackActivity(Actor self, Target target, bool allowMove, bool forceAttack)
|
||||
@@ -78,59 +162,122 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
this.target = target;
|
||||
this.forceAttack = forceAttack;
|
||||
|
||||
// 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))
|
||||
|| target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
|
||||
{
|
||||
lastVisibleTarget = Target.FromPos(target.CenterPosition);
|
||||
lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
lastVisibleMinimumRange = attack.GetMinimumRangeVersusTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
target = target.Recalculate(self.Owner);
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
if (IsCanceled)
|
||||
{
|
||||
// Cancel the requested target, but keep firing on it while in range
|
||||
attack.opportunityTarget = attack.requestedTarget;
|
||||
attack.opportunityForceAttack = attack.requestedForceAttack;
|
||||
attack.requestedTarget = Target.Invalid;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
// Check that AttackFollow hasn't cancelled the target by modifying attack.Target
|
||||
// Having both this and AttackFollow modify that field is a horrible hack.
|
||||
if (hasTicked && attack.requestedTarget.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
|
||||
if (attack.IsTraitPaused)
|
||||
return this;
|
||||
|
||||
var weapon = attack.ChooseArmamentsForTarget(target, forceAttack).FirstOrDefault();
|
||||
if (weapon != null)
|
||||
bool targetIsHiddenActor;
|
||||
attack.requestedForceAttack = forceAttack;
|
||||
attack.requestedTarget = target = target.Recalculate(self.Owner, out targetIsHiddenActor);
|
||||
attack.requestedTargetLastTick = self.World.WorldTick;
|
||||
hasTicked = true;
|
||||
|
||||
if (!targetIsHiddenActor && target.Type == TargetType.Actor)
|
||||
{
|
||||
// Check that AttackFollow hasn't cancelled the target by modifying attack.Target
|
||||
// Having both this and AttackFollow modify that field is a horrible hack.
|
||||
if (hasTicked && attack.Target.Type == TargetType.Invalid)
|
||||
return NextActivity;
|
||||
lastVisibleTarget = Target.FromTargetPositions(target);
|
||||
lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target);
|
||||
lastVisibleMinimumRange = attack.GetMinimumRange();
|
||||
|
||||
var targetIsMobile = (target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<IMoveInfo>())
|
||||
|| (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.HasTraitInfo<IMoveInfo>());
|
||||
|
||||
// Try and sit at least one cell closer than the max range to give some leeway if the target starts moving.
|
||||
var modifiedRange = weapon.MaxRange();
|
||||
var maxRange = targetIsMobile ? new WDist(Math.Max(weapon.Weapon.MinRange.Length, modifiedRange.Length - 1024))
|
||||
: modifiedRange;
|
||||
|
||||
// Most actors want to be able to see their target before shooting
|
||||
if (!attack.Info.TargetFrozenActors && !forceAttack && target.Type == TargetType.FrozenActor)
|
||||
// Try and sit at least one cell away from the min or max ranges to give some leeway if the target starts moving.
|
||||
if (move != null && target.Actor.Info.HasTraitInfo<IMoveInfo>())
|
||||
{
|
||||
var rs = revealsShroud
|
||||
.Where(Exts.IsTraitEnabled)
|
||||
.MaxByOrDefault(s => s.Range);
|
||||
|
||||
// Default to 2 cells if there are no active traits
|
||||
var sightRange = rs != null ? rs.Range : WDist.FromCells(2);
|
||||
if (sightRange < maxRange)
|
||||
maxRange = sightRange;
|
||||
var preferMinRange = Math.Min(lastVisibleMinimumRange.Length + 1024, lastVisibleMaximumRange.Length);
|
||||
var preferMaxRange = Math.Max(lastVisibleMaximumRange.Length - 1024, lastVisibleMinimumRange.Length);
|
||||
lastVisibleMaximumRange = new WDist((lastVisibleMaximumRange.Length - 1024).Clamp(preferMinRange, preferMaxRange));
|
||||
}
|
||||
|
||||
attack.Target = target;
|
||||
hasTicked = true;
|
||||
|
||||
if (move != null)
|
||||
return ActivityUtils.SequenceActivities(move.MoveFollow(self, target, weapon.Weapon.MinRange, maxRange), this);
|
||||
if (target.IsInRange(self.CenterPosition, weapon.MaxRange()) &&
|
||||
!target.IsInRange(self.CenterPosition, weapon.Weapon.MinRange))
|
||||
return this;
|
||||
}
|
||||
|
||||
attack.Target = Target.Invalid;
|
||||
var oldUseLastVisibleTarget = useLastVisibleTarget;
|
||||
var maxRange = lastVisibleMaximumRange;
|
||||
var minRange = lastVisibleMinimumRange;
|
||||
useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self);
|
||||
|
||||
return NextActivity;
|
||||
// Most actors want to be able to see their target before shooting
|
||||
if (target.Type == TargetType.FrozenActor && !attack.Info.TargetFrozenActors && !forceAttack)
|
||||
{
|
||||
var rs = revealsShroud
|
||||
.Where(Exts.IsTraitEnabled)
|
||||
.MaxByOrDefault(s => s.Range);
|
||||
|
||||
// Default to 2 cells if there are no active traits
|
||||
var sightRange = rs != null ? rs.Range : WDist.FromCells(2);
|
||||
if (sightRange < maxRange)
|
||||
maxRange = sightRange;
|
||||
}
|
||||
|
||||
// If we are ticking again after previously sequencing a MoveWithRange then that move must have completed
|
||||
// Either we are in range and can see the target, or we've lost track of it and should give up
|
||||
if (wasMovingWithinRange && targetIsHiddenActor)
|
||||
{
|
||||
attack.requestedTarget = Target.Invalid;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
// Update target lines if required
|
||||
if (useLastVisibleTarget != oldUseLastVisibleTarget)
|
||||
self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false);
|
||||
|
||||
// Target is hidden or dead, and we don't have a fallback position to move towards
|
||||
if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self))
|
||||
{
|
||||
attack.requestedTarget = Target.Invalid;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
var pos = self.CenterPosition;
|
||||
var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target;
|
||||
|
||||
// We've reached the required range - if the target is visible and valid then we wait
|
||||
// otherwise if it is hidden or dead we give up
|
||||
if (checkTarget.IsInRange(pos, maxRange) && !checkTarget.IsInRange(pos, minRange))
|
||||
{
|
||||
if (useLastVisibleTarget)
|
||||
{
|
||||
attack.requestedTarget = Target.Invalid;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// We can't move into range, so give up
|
||||
if (move == null || maxRange == WDist.Zero || maxRange < minRange)
|
||||
{
|
||||
attack.requestedTarget = Target.Invalid;
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
wasMovingWithinRange = true;
|
||||
return ActivityUtils.SequenceActivities(
|
||||
move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, Color.Red),
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
target = target.Recalculate(self.Owner);
|
||||
// This activity can't move to reacquire hidden targets, so use the
|
||||
// Recalculate overload that invalidates hidden targets.
|
||||
target = target.RecalculateInvalidatingHiddenTargets(self.Owner);
|
||||
if (IsCanceled || !target.IsValidFor(self) || !attack.IsReachableTarget(target, allowMove))
|
||||
return NextActivity;
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Does a suicide attack where it moves next to the target when used in combination with `Explodes`.")]
|
||||
class AttackSuicidesInfo : ConditionalTraitInfo, Requires<IMoveInfo>
|
||||
{
|
||||
[Desc("Types defined by `Targetable:` trait that this actor can target.")]
|
||||
public readonly BitSet<TargetableType> TargetTypes = new BitSet<TargetableType>("DetonateAttack");
|
||||
|
||||
[Desc("Types of damage that this trait causes to self while suiciding. Leave empty for no damage types.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
|
||||
|
||||
[VoiceReference] public readonly string Voice = "Action";
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackSuicides(init.Self, this); }
|
||||
}
|
||||
|
||||
class AttackSuicides : ConditionalTrait<AttackSuicidesInfo>, IIssueOrder, IResolveOrder, IOrderVoice, IIssueDeployOrder
|
||||
{
|
||||
readonly IMove move;
|
||||
|
||||
public AttackSuicides(Actor self, AttackSuicidesInfo info)
|
||||
: base(info)
|
||||
{
|
||||
move = self.Trait<IMove>();
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsTraitDisabled)
|
||||
yield break;
|
||||
|
||||
yield return new TargetTypeOrderTargeter(Info.TargetTypes, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
||||
yield return new DeployOrderTargeter("Detonate", 5);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID != "DetonateAttack" && order.OrderID != "Detonate")
|
||||
return null;
|
||||
|
||||
return new Order(order.OrderID, self, target, queued);
|
||||
}
|
||||
|
||||
Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued)
|
||||
{
|
||||
return new Order("Detonate", self, queued);
|
||||
}
|
||||
|
||||
bool IIssueDeployOrder.CanIssueDeployOrder(Actor self) { return true; }
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return Info.Voice;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "DetonateAttack")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
|
||||
self.QueueActivity(move.MoveToTarget(self, target));
|
||||
|
||||
self.QueueActivity(new CallFunc(() => self.Kill(self, Info.DamageTypes)));
|
||||
}
|
||||
else if (order.OrderString == "Detonate")
|
||||
self.Kill(self, Info.DamageTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,10 +109,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyCreated
|
||||
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyCreated, INotifyOwnerChanged
|
||||
{
|
||||
readonly IEnumerable<AttackBase> activeAttackBases;
|
||||
readonly AttackFollow[] attackFollows;
|
||||
[Sync] int nextScanTime = 0;
|
||||
|
||||
public UnitStance Stance { get { return stance; } }
|
||||
@@ -161,7 +160,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
stance = self.Owner.IsBot || !self.Owner.Playable ? info.InitialStanceAI : info.InitialStance;
|
||||
|
||||
PredictedStance = stance;
|
||||
attackFollows = self.TraitsImplementing<AttackFollow>().ToArray();
|
||||
}
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
@@ -177,6 +175,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
ApplyStanceCondition(self);
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
PredictedStance = self.Owner.IsBot || !self.Owner.Playable ? Info.InitialStanceAI : Info.InitialStance;
|
||||
SetStance(self, PredictedStance);
|
||||
}
|
||||
|
||||
void IResolveOrder.ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "SetUnitStance" && Info.EnableStances)
|
||||
@@ -215,9 +219,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
Aggressor = attacker;
|
||||
|
||||
bool allowMove;
|
||||
if (ShouldAttack(out allowMove))
|
||||
Attack(self, Target.FromActor(Aggressor), allowMove);
|
||||
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
|
||||
Attack(self, Target.FromActor(Aggressor), allowMove);
|
||||
}
|
||||
|
||||
void INotifyIdle.TickIdle(Actor self)
|
||||
@@ -225,21 +228,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (IsTraitDisabled || Stance < UnitStance.Defend)
|
||||
return;
|
||||
|
||||
bool allowMove;
|
||||
if (ShouldAttack(out allowMove))
|
||||
ScanAndAttack(self, allowMove);
|
||||
}
|
||||
|
||||
bool ShouldAttack(out bool allowMove)
|
||||
{
|
||||
allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
|
||||
|
||||
// PERF: Avoid LINQ.
|
||||
foreach (var attackFollow in attackFollows)
|
||||
if (!attackFollow.IsTraitDisabled && attackFollow.IsReachableTarget(attackFollow.Target, allowMove))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
|
||||
ScanAndAttack(self, allowMove);
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
void IBotRespondToAttack.RespondToAttack(IBot bot, Actor self, AttackInfo e)
|
||||
{
|
||||
// HACK: We don't want D2k bots to repair all their buildings on placement
|
||||
// where half their HP is removed via neutral terrain damage.
|
||||
// TODO: Implement concrete placement for D2k bots and remove this hack.
|
||||
if (e.Attacker.Owner.Stances[self.Owner] == Stance.Neutral)
|
||||
return;
|
||||
|
||||
var rb = self.TraitOrDefault<RepairableBuilding>();
|
||||
if (rb != null)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
readonly World world;
|
||||
readonly Player player;
|
||||
readonly Func<Actor, bool> isEnemyUnit;
|
||||
readonly Predicate<Actor> unitCannotBeOrdered;
|
||||
readonly Predicate<Actor> unitCannotBeOrderedOrIsIdle;
|
||||
readonly int maximumCaptureTargetOptions;
|
||||
int minCaptureDelayTicks;
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
&& !unit.Info.HasTraitInfo<HuskInfo>()
|
||||
&& unit.Info.HasTraitInfo<ITargetableInfo>();
|
||||
|
||||
unitCannotBeOrdered = a => a.Owner != player || a.IsDead || !a.IsInWorld;
|
||||
unitCannotBeOrderedOrIsIdle = a => a.Owner != player || a.IsDead || !a.IsInWorld || a.IsIdle;
|
||||
|
||||
maximumCaptureTargetOptions = Math.Max(1, Info.MaximumCaptureTargetOptions);
|
||||
}
|
||||
@@ -118,13 +118,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!Info.CapturingActorTypes.Any() || player.WinState != WinState.Undefined)
|
||||
return;
|
||||
|
||||
activeCapturers.RemoveAll(unitCannotBeOrdered);
|
||||
activeCapturers.RemoveAll(unitCannotBeOrderedOrIsIdle);
|
||||
|
||||
var newUnits = world.ActorsHavingTrait<IPositionable>()
|
||||
.Where(a => a.Owner == player && !activeCapturers.Contains(a));
|
||||
|
||||
var capturers = newUnits
|
||||
.Where(a => a.IsIdle && Info.CapturingActorTypes.Contains(a.Info.Name))
|
||||
.Where(a => a.IsIdle && Info.CapturingActorTypes.Contains(a.Info.Name) && a.Info.HasTraitInfo<CapturesInfo>())
|
||||
.Select(a => new TraitPair<CaptureManager>(a, a.TraitOrDefault<CaptureManager>()))
|
||||
.Where(tp => tp.Trait != null)
|
||||
.ToArray();
|
||||
@@ -140,52 +140,33 @@ namespace OpenRA.Mods.Common.Traits
|
||||
: GetActorsThatCanBeOrderedByPlayer(randPlayer);
|
||||
|
||||
var capturableTargetOptions = targetOptions
|
||||
.Select(a => new CaptureTarget<CapturableInfo>(a, "CaptureActor"))
|
||||
.Where(target =>
|
||||
{
|
||||
if (target.Info == null)
|
||||
return false;
|
||||
|
||||
var captureManager = target.Actor.TraitOrDefault<CaptureManager>();
|
||||
var captureManager = target.TraitOrDefault<CaptureManager>();
|
||||
if (captureManager == null)
|
||||
return false;
|
||||
|
||||
return capturers.Any(tp => captureManager.CanBeTargetedBy(target.Actor, tp.Actor, tp.Trait));
|
||||
return capturers.Any(tp => captureManager.CanBeTargetedBy(target, tp.Actor, tp.Trait));
|
||||
})
|
||||
.OrderByDescending(target => target.Actor.GetSellValue())
|
||||
.OrderByDescending(target => target.GetSellValue())
|
||||
.Take(maximumCaptureTargetOptions);
|
||||
|
||||
if (Info.CapturableActorTypes.Any())
|
||||
capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Actor.Info.Name.ToLowerInvariant()));
|
||||
capturableTargetOptions = capturableTargetOptions.Where(target => Info.CapturableActorTypes.Contains(target.Info.Name.ToLowerInvariant()));
|
||||
|
||||
if (!capturableTargetOptions.Any())
|
||||
return;
|
||||
|
||||
var capturesCapturers = capturers.Where(tp => tp.Actor.Info.HasTraitInfo<CapturesInfo>());
|
||||
foreach (var tp in capturesCapturers)
|
||||
QueueCaptureOrderFor(bot, tp.Actor, GetCapturerTargetClosestToOrDefault(tp.Actor, capturableTargetOptions));
|
||||
}
|
||||
foreach (var capturer in capturers)
|
||||
{
|
||||
var targetActor = capturableTargetOptions.MinByOrDefault(target => (target.CenterPosition - capturer.Actor.CenterPosition).LengthSquared);
|
||||
if (targetActor == null)
|
||||
continue;
|
||||
|
||||
void QueueCaptureOrderFor<TTargetType>(IBot bot, Actor capturer, CaptureTarget<TTargetType> target) where TTargetType : class, ITraitInfoInterface
|
||||
{
|
||||
if (capturer == null)
|
||||
return;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (target.Actor == null)
|
||||
return;
|
||||
|
||||
bot.QueueOrder(new Order(target.OrderString, capturer, Target.FromActor(target.Actor), true));
|
||||
AIUtils.BotDebug("AI ({0}): Ordered {1} to capture {2}", player.ClientIndex, capturer, target.Actor);
|
||||
activeCapturers.Add(capturer);
|
||||
}
|
||||
|
||||
CaptureTarget<TTargetType> GetCapturerTargetClosestToOrDefault<TTargetType>(Actor capturer, IEnumerable<CaptureTarget<TTargetType>> targets)
|
||||
where TTargetType : class, ITraitInfoInterface
|
||||
{
|
||||
return targets.MinByOrDefault(target => (target.Actor.CenterPosition - capturer.CenterPosition).LengthSquared);
|
||||
bot.QueueOrder(new Order("CaptureActor", capturer.Actor, Target.FromActor(targetActor), true));
|
||||
AIUtils.BotDebug("AI ({0}): Ordered {1} to capture {2}", player.ClientIndex, capturer.Actor, targetActor);
|
||||
activeCapturers.Add(capturer.Actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
: base(info)
|
||||
{
|
||||
self = init.Self;
|
||||
OpenPosition = Info.TransitionDelay;
|
||||
Position = OpenPosition = Info.TransitionDelay;
|
||||
building = self.Trait<Building>();
|
||||
blockedPositions = building.Info.Tiles(self.Location);
|
||||
Footprint = blockedPositions;
|
||||
|
||||
@@ -95,15 +95,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (order.OrderString != "CaptureActor" || IsTraitDisabled)
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new CaptureActor(self, target.Actor));
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new CaptureActor(self, order.Target));
|
||||
}
|
||||
|
||||
protected override void TraitEnabled(Actor self) { captureManager.RefreshCaptures(self); }
|
||||
|
||||
@@ -265,18 +265,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
if (order.OrderString == "PickupUnit")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Yellow);
|
||||
if (target.Type != TargetType.Actor)
|
||||
if (order.Target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!ReserveCarryable(self, target.Actor))
|
||||
if (!ReserveCarryable(self, order.Target.Actor))
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
self.QueueActivity(order.Queued, new PickupUnit(self, target.Actor, Info.LoadingDelay));
|
||||
self.SetTargetLine(order.Target, Color.Yellow);
|
||||
self.QueueActivity(order.Queued, new PickupUnit(self, order.Target.Actor, Info.LoadingDelay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,15 +69,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (order.OrderString != "DeliverCash")
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Yellow);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
self.QueueActivity(new DonateCash(self, target.Actor, info.Payload, info.PlayerExperience));
|
||||
self.SetTargetLine(order.Target, Color.Yellow);
|
||||
self.QueueActivity(new DonateCash(self, order.Target, info.Payload, info.PlayerExperience));
|
||||
}
|
||||
|
||||
void INotifyCashTransfer.OnAcceptingCash(Actor self, Actor donor) { }
|
||||
|
||||
@@ -71,21 +71,20 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (order.OrderString != "DeliverExperience")
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Yellow);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
var targetGainsExperience = target.Actor.Trait<GainsExperience>();
|
||||
if (targetGainsExperience.Level == targetGainsExperience.MaxLevel)
|
||||
if (order.Target.Type == TargetType.Actor)
|
||||
{
|
||||
var targetGainsExperience = order.Target.Actor.Trait<GainsExperience>();
|
||||
if (targetGainsExperience.Level == targetGainsExperience.MaxLevel)
|
||||
return;
|
||||
}
|
||||
else if (order.Target.Type != TargetType.FrozenActor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
var level = gainsExperience.Level;
|
||||
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
self.QueueActivity(new DonateExperience(self, target.Actor, level, info.PlayerExperience, targetGainsExperience));
|
||||
self.SetTargetLine(order.Target, Color.Yellow);
|
||||
self.QueueActivity(new DonateExperience(self, order.Target, gainsExperience.Level, info.PlayerExperience));
|
||||
}
|
||||
|
||||
public class DeliversExperienceOrderTargeter : UnitOrderTargeter
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
ConditionManager conditionManager;
|
||||
List<DemolishAction> actions = new List<DemolishAction>();
|
||||
List<DemolishAction> removeActions = new List<DemolishAction>();
|
||||
|
||||
public Demolishable(DemolishableInfo info)
|
||||
: base(info) { }
|
||||
@@ -88,9 +89,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (Util.ApplyPercentageModifiers(100, modifiers) > 0)
|
||||
self.Kill(a.Saboteur);
|
||||
else if (a.Token != ConditionManager.InvalidConditionToken)
|
||||
{
|
||||
conditionManager.RevokeCondition(self, a.Token);
|
||||
removeActions.Add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove expired actions to avoid double-revoking
|
||||
foreach (var a in removeActions)
|
||||
actions.Remove(a);
|
||||
|
||||
removeActions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,19 +75,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (order.OrderString != "C4")
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
var demolishable = target.Actor.TraitOrDefault<IDemolishable>();
|
||||
if (demolishable == null || !demolishable.IsValidTarget(target.Actor, self))
|
||||
return;
|
||||
if (order.Target.Type == TargetType.Actor)
|
||||
{
|
||||
var demolishables = order.Target.Actor.TraitsImplementing<IDemolishable>();
|
||||
if (!demolishables.Any(i => i.IsValidTarget(order.Target.Actor, self)))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
self.QueueActivity(new Demolish(self, target.Actor, info.EnterBehaviour, info.DetonationDelay,
|
||||
self.SetTargetLine(order.Target, Color.Red);
|
||||
self.QueueActivity(new Demolish(self, order.Target, info.EnterBehaviour, info.DetonationDelay,
|
||||
info.Flashes, info.FlashesDelay, info.FlashInterval));
|
||||
}
|
||||
|
||||
|
||||
@@ -76,15 +76,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (order.OrderString != "EngineerRepair" || !IsValidOrder(self, order))
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Yellow);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
self.QueueActivity(new RepairBuilding(self, target.Actor, info.EnterBehaviour, info.ValidStances));
|
||||
self.SetTargetLine(order.Target, Color.Yellow);
|
||||
self.QueueActivity(new RepairBuilding(self, order.Target, info.EnterBehaviour, info.ValidStances));
|
||||
}
|
||||
|
||||
class EngineerRepairOrderTargeter : UnitOrderTargeter
|
||||
|
||||
@@ -62,15 +62,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "EnterTunnel")
|
||||
if (order.OrderString != "EnterTunnel" || order.Target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (target.Type != TargetType.Actor)
|
||||
return;
|
||||
|
||||
var tunnel = target.Actor.TraitOrDefault<TunnelEntrance>();
|
||||
if (!tunnel.Exit.HasValue)
|
||||
var tunnel = order.Target.Actor.TraitOrDefault<TunnelEntrance>();
|
||||
if (tunnel == null || !tunnel.Exit.HasValue)
|
||||
return;
|
||||
|
||||
if (!order.Queued)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Primitives;
|
||||
@@ -35,20 +36,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public override object Create(ActorInitializer init) { return new GivesBounty(this); }
|
||||
}
|
||||
|
||||
class GivesBounty : ConditionalTrait<GivesBountyInfo>, INotifyKilled
|
||||
class GivesBounty : ConditionalTrait<GivesBountyInfo>, INotifyKilled, INotifyPassengerEntered, INotifyPassengerExited
|
||||
{
|
||||
Cargo cargo;
|
||||
Dictionary<Actor, GivesBounty[]> passengerBounties = new Dictionary<Actor, GivesBounty[]>();
|
||||
|
||||
public GivesBounty(GivesBountyInfo info)
|
||||
: base(info) { }
|
||||
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
base.Created(self);
|
||||
|
||||
cargo = self.TraitOrDefault<Cargo>();
|
||||
}
|
||||
|
||||
int GetBountyValue(Actor self)
|
||||
{
|
||||
return self.GetSellValue() * Info.Percentage / 100;
|
||||
@@ -57,15 +51,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
int GetDisplayedBountyValue(Actor self)
|
||||
{
|
||||
var bounty = GetBountyValue(self);
|
||||
if (cargo == null)
|
||||
return bounty;
|
||||
|
||||
foreach (var a in cargo.Passengers)
|
||||
{
|
||||
var givesBounties = a.TraitsImplementing<GivesBounty>().Where(gb => !gb.IsTraitDisabled);
|
||||
foreach (var givesBounty in givesBounties)
|
||||
bounty += givesBounty.GetDisplayedBountyValue(a);
|
||||
}
|
||||
foreach (var pb in passengerBounties)
|
||||
foreach (var b in pb.Value)
|
||||
if (!b.IsTraitDisabled)
|
||||
bounty += b.GetDisplayedBountyValue(pb.Key);
|
||||
|
||||
return bounty;
|
||||
}
|
||||
@@ -87,5 +76,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
e.Attacker.Owner.PlayerActor.Trait<PlayerResources>().ChangeCash(GetBountyValue(self));
|
||||
}
|
||||
|
||||
void INotifyPassengerEntered.OnPassengerEntered(Actor self, Actor passenger)
|
||||
{
|
||||
passengerBounties.Add(passenger, passenger.TraitsImplementing<GivesBounty>().ToArray());
|
||||
}
|
||||
|
||||
void INotifyPassengerExited.OnPassengerExited(Actor self, Actor passenger)
|
||||
{
|
||||
passengerBounties.Remove(passenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
|
||||
var range = target.Actor.Info.TraitInfo<GuardableInfo>().Range;
|
||||
self.QueueActivity(new AttackMoveActivity(self, move.MoveFollow(self, target, WDist.Zero, range)));
|
||||
self.QueueActivity(new AttackMoveActivity(self, move.MoveFollow(self, target, WDist.Zero, range, targetLineColor: Color.Yellow)));
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
|
||||
@@ -457,11 +457,33 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
#region IMove
|
||||
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return new Move(self, cell, WDist.FromCells(nearEnough)); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoreActor) { return new Move(self, cell, WDist.Zero, ignoreActor); }
|
||||
public Activity MoveWithinRange(Target target, WDist range) { return new MoveWithinRange(self, target, WDist.Zero, range); }
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) { return new MoveWithinRange(self, target, minRange, maxRange); }
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { return new Follow(self, target, minRange, maxRange); }
|
||||
public Activity MoveTo(CPos cell, int nearEnough)
|
||||
{
|
||||
return new Move(self, cell, WDist.FromCells(nearEnough));
|
||||
}
|
||||
|
||||
public Activity MoveTo(CPos cell, Actor ignoreActor)
|
||||
{
|
||||
return new Move(self, cell, WDist.Zero, ignoreActor);
|
||||
}
|
||||
|
||||
public Activity MoveWithinRange(Target target, WDist range,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
return new MoveWithinRange(self, target, WDist.Zero, range, initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
return new MoveWithinRange(self, target, minRange, maxRange, initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
return new Follow(self, target, minRange, maxRange, initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
@@ -481,12 +503,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
return VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell);
|
||||
}
|
||||
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
public Activity MoveToTarget(Actor self, Target target,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return new MoveAdjacentTo(self, target);
|
||||
return new MoveAdjacentTo(self, target, initialTargetPosition, targetLineColor);
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target)
|
||||
@@ -494,7 +517,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return VisualMove(self, self.CenterPosition, target.Positions.PositionClosestTo(self.CenterPosition));
|
||||
// Activity cancels if the target moves by more than half a cell
|
||||
// to avoid problems with the cell grid
|
||||
return new VisualMoveIntoTarget(self, target, new WDist(512));
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos)
|
||||
@@ -516,6 +541,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public bool CanEnterTargetNow(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.FrozenActor && !target.FrozenActor.IsValid)
|
||||
return false;
|
||||
|
||||
return self.Location == self.World.Map.CellContaining(target.CenterPosition) || Util.AdjacentCells(self.World, target).Any(c => c == self.Location);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
@@ -27,7 +28,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public object Create(ActorInitializer init) { return new FrozenUnderFog(init, this); }
|
||||
}
|
||||
|
||||
public class FrozenUnderFog : IRenderModifier, IDefaultVisibility, ITick, ITickRender, ISync, INotifyCreated
|
||||
public class FrozenUnderFog : ICreatesFrozenActors, IRenderModifier, IDefaultVisibility, ITick, ITickRender, ISync, INotifyCreated, INotifyOwnerChanged, INotifyActorDisposing
|
||||
{
|
||||
[Sync] public int VisibilityHash;
|
||||
|
||||
@@ -37,6 +38,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
PlayerDictionary<FrozenState> frozenStates;
|
||||
bool isRendering;
|
||||
bool created;
|
||||
|
||||
class FrozenState
|
||||
{
|
||||
@@ -67,14 +69,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
frozenStates = new PlayerDictionary<FrozenState>(self.World, (player, playerIndex) =>
|
||||
{
|
||||
var frozenActor = new FrozenActor(self, footprint, player, startsRevealed);
|
||||
var frozenActor = new FrozenActor(self, this, footprint, player, startsRevealed);
|
||||
player.PlayerActor.Trait<FrozenActorLayer>().Add(frozenActor);
|
||||
return new FrozenState(frozenActor) { IsVisible = startsRevealed };
|
||||
});
|
||||
|
||||
if (startsRevealed)
|
||||
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
|
||||
UpdateFrozenActor(self, frozenStates[playerIndex].FrozenActor, playerIndex);
|
||||
}
|
||||
|
||||
void UpdateFrozenActor(Actor self, FrozenActor frozenActor, int playerIndex)
|
||||
@@ -83,6 +81,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
frozenActor.RefreshState();
|
||||
}
|
||||
|
||||
void ICreatesFrozenActors.OnVisibilityChanged(FrozenActor frozen)
|
||||
{
|
||||
// Ignore callbacks during initial setup
|
||||
if (!created)
|
||||
return;
|
||||
|
||||
// Update state visibility to match the frozen actor to ensure consistency within the tick
|
||||
// The rest of the state will be updated by ITick.Tick below
|
||||
frozenStates[frozen.Viewer].IsVisible = !frozen.Visible;
|
||||
}
|
||||
|
||||
bool IsVisibleInner(Actor self, Player byPlayer)
|
||||
{
|
||||
// If fog is disabled visibility is determined by shroud
|
||||
@@ -106,6 +115,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (self.Disposed)
|
||||
return;
|
||||
|
||||
// Set the initial visibility state
|
||||
// This relies on actor.GetTargetablePositions(), which is not safe to use from Created
|
||||
// so we defer until the first real tick.
|
||||
if (!created && startsRevealed)
|
||||
{
|
||||
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
|
||||
UpdateFrozenActor(self, frozenStates[playerIndex].FrozenActor, playerIndex);
|
||||
|
||||
created = true;
|
||||
return;
|
||||
}
|
||||
|
||||
VisibilityHash = 0;
|
||||
|
||||
for (var playerIndex = 0; playerIndex < frozenStates.Count; playerIndex++)
|
||||
@@ -158,6 +179,20 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
// Force a state update for the old owner so the tooltip etc doesn't show them as the owner
|
||||
var oldOwnerIndex = self.World.Players.IndexOf(oldOwner);
|
||||
UpdateFrozenActor(self, frozenStates[oldOwnerIndex].FrozenActor, oldOwnerIndex);
|
||||
}
|
||||
|
||||
void INotifyActorDisposing.Disposing(Actor self)
|
||||
{
|
||||
// Invalidate the frozen actor (which exists if this actor was captured from an enemy)
|
||||
// for the current owner
|
||||
frozenStates[self.Owner].FrozenActor.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public class HiddenUnderFogInit : IActorInit { }
|
||||
|
||||
@@ -32,9 +32,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
"Default - use force move modifier (Alt) to disable.")]
|
||||
public readonly AlternateTransportsMode AlternateTransportsMode = AlternateTransportsMode.Force;
|
||||
|
||||
[Desc("Number of retries using alternate transports.")]
|
||||
public readonly int MaxAlternateTransportAttempts = 1;
|
||||
|
||||
[Desc("Range from self for looking for an alternate transport (default: 5.5 cells).")]
|
||||
public readonly WDist AlternateTransportScanRange = WDist.FromCells(11) / 2;
|
||||
|
||||
@@ -161,9 +158,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
var transports = order.OrderString == "EnterTransports";
|
||||
self.SetTargetLine(order.Target, Color.Green);
|
||||
self.QueueActivity(new EnterTransport(self, targetActor, transports ? Info.MaxAlternateTransportAttempts : 0, !transports));
|
||||
if (order.OrderString == "EnterTransports")
|
||||
self.QueueActivity(new EnterTransports(self, order.Target));
|
||||
else
|
||||
self.QueueActivity(new EnterTransport(self, order.Target));
|
||||
}
|
||||
|
||||
public bool Reserve(Actor self, Cargo cargo)
|
||||
|
||||
@@ -63,6 +63,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// Called by the host's player creation code
|
||||
public void Activate(Player p)
|
||||
{
|
||||
// Bot logic is not allowed to affect world state, and can only act by issuing orders
|
||||
// These orders are recorded in the replay, so bots shouldn't be enabled during replays
|
||||
if (p.World.IsReplay)
|
||||
return;
|
||||
|
||||
IsEnabled = true;
|
||||
player = p;
|
||||
tickModules = p.PlayerActor.TraitsImplementing<IBotTick>().ToArray();
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
bool IRenderAboveShroudWhenSelected.SpatiallyPartitionable { get { return false; } }
|
||||
|
||||
void INotifyBecomingIdle.OnBecomingIdle(Actor a)
|
||||
void INotifyBecomingIdle.OnBecomingIdle(Actor self)
|
||||
{
|
||||
targets = null;
|
||||
}
|
||||
@@ -115,15 +115,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!self.Owner.IsAlliedWith(self.World.LocalPlayer))
|
||||
return;
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (self.Disposed)
|
||||
return;
|
||||
if (self.Disposed)
|
||||
return;
|
||||
|
||||
var line = self.TraitOrDefault<DrawLineToTarget>();
|
||||
if (line != null)
|
||||
line.SetTarget(self, target, color, display);
|
||||
});
|
||||
var line = self.TraitOrDefault<DrawLineToTarget>();
|
||||
if (line != null)
|
||||
line.SetTarget(self, target, color, display);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(order.Target, Color.Green);
|
||||
self.QueueActivity(new WaitForTransport(self, ActivityUtils.SequenceActivities(movement.MoveToTarget(self, order.Target),
|
||||
new CallFunc(() => AfterReachActivities(self, order, movement)))));
|
||||
|
||||
var activities = ActivityUtils.SequenceActivities(
|
||||
movement.MoveToTarget(self, order.Target, targetLineColor: Color.Green),
|
||||
new CallFunc(() => AfterReachActivities(self, order, movement)));
|
||||
|
||||
self.QueueActivity(new WaitForTransport(self, activities));
|
||||
|
||||
TryCallTransport(self, order.Target, new CallFunc(() => AfterReachActivities(self, order, movement)));
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (!order.Queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.QueueActivity(movement.MoveWithinRange(order.Target, info.CloseEnough));
|
||||
self.QueueActivity(movement.MoveWithinRange(order.Target, info.CloseEnough, targetLineColor: Color.Green));
|
||||
self.QueueActivity(new Repair(self, order.Target.Actor, info.CloseEnough));
|
||||
|
||||
self.SetTargetLine(order.Target, Color.Green, false);
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
// TODO: Add support for FrozenActors
|
||||
// The activity supports it, but still missing way to freeze bridge state on the hut
|
||||
if (order.OrderString == "RepairBridge" && order.Target.Type == TargetType.Actor)
|
||||
{
|
||||
var targetActor = order.Target.Actor;
|
||||
@@ -104,7 +105,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
self.CancelActivity();
|
||||
|
||||
self.SetTargetLine(order.Target, Color.Yellow);
|
||||
self.QueueActivity(new RepairBridge(self, targetActor, info.EnterBehaviour, info.RepairNotification));
|
||||
self.QueueActivity(new RepairBridge(self, order.Target, info.EnterBehaviour, info.RepairNotification));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Action<Actor> onEnterRange = a =>
|
||||
{
|
||||
// Spawn a camera and remove the beacon when the first plane enters the target area
|
||||
if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value))
|
||||
if (info.CameraActor != null && camera == null && !aircraftInRange.Any(kv => kv.Value))
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
@@ -106,6 +106,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
Action<Actor> onRemovedFromWorld = a =>
|
||||
{
|
||||
aircraftInRange[a] = false;
|
||||
|
||||
// Checking for attack range is not relevant here because
|
||||
// aircraft may be shot down before entering. Thus we remove
|
||||
// the camera and beacon only if the whole squad is dead.
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Action<Actor> onEnterRange = a =>
|
||||
{
|
||||
// Spawn a camera and remove the beacon when the first plane enters the target area
|
||||
if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value))
|
||||
if (info.CameraActor != null && camera == null && !aircraftInRange.Any(kv => kv.Value))
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
@@ -133,6 +133,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
Action<Actor> onRemovedFromWorld = a =>
|
||||
{
|
||||
aircraftInRange[a] = false;
|
||||
|
||||
// Checking for attack range is not relevant here because
|
||||
// aircraft may be shot down before entering. Thus we remove
|
||||
// the camera and beacon only if the whole squad is dead.
|
||||
|
||||
@@ -255,12 +255,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (attack != null && attack.IsAiming)
|
||||
attack.OnStopOrder(self);
|
||||
}
|
||||
|
||||
protected override void TraitResumed(Actor self)
|
||||
{
|
||||
if (attack != null)
|
||||
FaceTarget(self, attack.Target);
|
||||
}
|
||||
}
|
||||
|
||||
public class TurretFacingInit : IActorInit<int>
|
||||
|
||||
@@ -283,5 +283,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
foreach (var preview in previewsForCell.Value)
|
||||
destinationBuffer.Add(Pair.New(previewsForCell.Key, preview.Owner.Color.RGB));
|
||||
}
|
||||
|
||||
public EditorActorPreview this[string id]
|
||||
{
|
||||
get { return previews.FirstOrDefault(p => p.ID.ToLowerInvariant() == id); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,18 +29,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
if (subjectClient == null)
|
||||
{
|
||||
Game.Debug("Order sent to {0}: resolved ClientIndex `{1}` doesn't exist", order.Subject.Owner.PlayerName, subjectClientId);
|
||||
Log.Write("debug", "Order sent to {0}: resolved ClientIndex `{1}` doesn't exist", order.Subject.Owner.PlayerName, subjectClientId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var isBotOrder = subjectClient.Bot != null && clientId == subjectClient.BotControllerClientIndex;
|
||||
|
||||
// Drop exploiting orders
|
||||
// Drop orders from players who shouldn't be able to control this actor
|
||||
// This may be because the owner changed within the last net tick,
|
||||
// or, less likely, the client may be trying to do something malicious.
|
||||
if (subjectClientId != clientId && !isBotOrder)
|
||||
{
|
||||
Game.Debug("Detected exploit order from client {0}: {1}", clientId, order.OrderString);
|
||||
return false;
|
||||
}
|
||||
|
||||
return order.Subject.AcceptsOrder(order.OrderString);
|
||||
}
|
||||
|
||||
@@ -397,11 +397,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
Activity MoveTo(CPos cell, int nearEnough);
|
||||
Activity MoveTo(CPos cell, Actor ignoreActor);
|
||||
Activity MoveWithinRange(Target target, WDist range);
|
||||
Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange);
|
||||
Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange);
|
||||
Activity MoveWithinRange(Target target, WDist range,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null);
|
||||
Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null);
|
||||
Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null);
|
||||
Activity MoveToTarget(Actor self, Target target,
|
||||
WPos? initialTargetPosition = null, Color? targetLineColor = null);
|
||||
Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any);
|
||||
Activity MoveToTarget(Actor self, Target target);
|
||||
Activity MoveIntoTarget(Actor self, Target target);
|
||||
Activity VisualMove(Actor self, WPos fromPos, WPos toPos);
|
||||
int EstimatedMoveDuration(Actor self, WPos fromPos, WPos toPos);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Mods.Common.UpdateRules.Rules
|
||||
{
|
||||
public class RemoveAttackSuicides : UpdateRule
|
||||
{
|
||||
public override string Name { get { return "AttackSuicides trait has been removed."; } }
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return "The AttackSuicides trait has been removed, and should be replaced by\n" +
|
||||
"AttackFrontal + GrantConditionOnAttack + GrantConditionOnDeploy + a dummy\n" +
|
||||
"weapon for targeting. Affected actors are listed so that these traits can be defined.";
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<string> locations = new List<string>();
|
||||
|
||||
public override IEnumerable<string> AfterUpdate(ModData modData)
|
||||
{
|
||||
if (locations.Any())
|
||||
yield return "The AttackSuicides trait has been removed from the following actors.\n" +
|
||||
"You must manually define AttackFrontal, GrantConditionOnAttack, GrantConditionOnDeploy\n" +
|
||||
"traits and create a dummy weapon to use for targeting:\n" +
|
||||
UpdateUtils.FormatMessageList(locations);
|
||||
|
||||
locations.Clear();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> UpdateActorNode(ModData modData, MiniYamlNode actorNode)
|
||||
{
|
||||
if (actorNode.RemoveNodes("AttackSuicides") > 0)
|
||||
locations.Add("{0} ({1})".F(actorNode.Key, actorNode.Location.Filename));
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,8 @@ namespace OpenRA.Mods.Common.UpdateRules
|
||||
|
||||
new UpdatePath("release-20180923", "release-20181215", new UpdateRule[0]),
|
||||
|
||||
new UpdatePath("release-20181215", new UpdateRule[]
|
||||
new UpdatePath("release-20181215", "playtest-20190106", new UpdateRule[]
|
||||
{
|
||||
// Bleed only changes here
|
||||
new AddCarryableHarvester(),
|
||||
new RenameEditorTilesetFilter(),
|
||||
new DefineNotificationDefaults(),
|
||||
@@ -113,7 +112,15 @@ namespace OpenRA.Mods.Common.UpdateRules
|
||||
new RemoveAttackIgnoresVisibility(),
|
||||
new ReplacedWithChargeAnimation(),
|
||||
new RefactorResourceLevelAnimating(),
|
||||
})
|
||||
}),
|
||||
|
||||
new UpdatePath("playtest-20190106", "playtest-20190209", new UpdateRule[]
|
||||
{
|
||||
new RemoveAttackSuicides(),
|
||||
}),
|
||||
|
||||
new UpdatePath("playtest-20190209", "playtest-20190302", new UpdateRule[0]),
|
||||
new UpdatePath("playtest-20190302", new UpdateRule[0])
|
||||
};
|
||||
|
||||
public static IEnumerable<UpdateRule> FromSource(ObjectCreator objectCreator, string source, bool chain = true)
|
||||
|
||||
@@ -82,6 +82,9 @@ namespace OpenRA.Mods.Common.Warheads
|
||||
/// <summary>Checks if the warhead is valid against (can do something to) the frozen actor.</summary>
|
||||
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
|
||||
{
|
||||
if (!victim.IsValid)
|
||||
return false;
|
||||
|
||||
// AffectsParent checks do not make sense for FrozenActors, so skip to stance checks
|
||||
var stance = firedBy.Owner.Stances[victim.Owner];
|
||||
if (!ValidStances.HasStance(stance))
|
||||
|
||||
@@ -122,8 +122,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var actorId = actorIDField.Text.ToLowerInvariant();
|
||||
if (CurrentActor.ID.ToLowerInvariant() != actorId)
|
||||
{
|
||||
var found = world.Map.ActorDefinitions.Any(x => x.Key.ToLowerInvariant() == actorId);
|
||||
if (found)
|
||||
if (editorActorLayer[actorId] != null)
|
||||
{
|
||||
nextActorIDStatus = ActorIDStatus.Duplicate;
|
||||
return;
|
||||
@@ -143,8 +142,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
void SetActorID(World world, string actorId)
|
||||
{
|
||||
var actorDef = world.Map.ActorDefinitions.First(x => x.Key == CurrentActor.ID);
|
||||
actorDef.Key = actorId;
|
||||
CurrentActor.ID = actorId;
|
||||
nextActorIDStatus = ActorIDStatus.Normal;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var copypasteButton = widget.GetOrNull<ButtonWidget>("COPYPASTE_BUTTON");
|
||||
if (copypasteButton != null)
|
||||
{
|
||||
// HACK: Replace Ctrl with Cmd on macOS
|
||||
// TODO: Add platform-specific override support to HotkeyManager
|
||||
// and then port the editor hotkeys to this system.
|
||||
var copyPasteKey = copypasteButton.Key.GetValue();
|
||||
if (Platform.CurrentPlatform == PlatformType.OSX && copyPasteKey.Modifiers.HasModifier(Modifiers.Ctrl))
|
||||
{
|
||||
var modified = new Hotkey(copyPasteKey.Key, copyPasteKey.Modifiers & ~Modifiers.Ctrl | Modifiers.Meta);
|
||||
copypasteButton.Key = FieldLoader.GetValue<HotkeyReference>("Key", modified.ToString());
|
||||
}
|
||||
|
||||
copypasteButton.OnClick = () => editorViewport.SetBrush(new EditorCopyPasteBrush(editorViewport, worldRenderer, () => copyFilters));
|
||||
copypasteButton.IsHighlighted = () => editorViewport.CurrentBrush is EditorCopyPasteBrush;
|
||||
}
|
||||
|
||||
@@ -85,8 +85,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
else if (chatTraits != null)
|
||||
{
|
||||
var text = chatText.Text.Trim();
|
||||
var from = world.IsReplay ? null : orderManager.LocalClient.Name;
|
||||
foreach (var trait in chatTraits)
|
||||
trait.OnChat(orderManager.LocalClient.Name, text);
|
||||
trait.OnChat(from, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,11 +70,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
return item;
|
||||
};
|
||||
|
||||
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 167, options, setupItem);
|
||||
dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 180, options, setupItem);
|
||||
}
|
||||
|
||||
public static void ShowPlayerActionDropDown(DropDownButtonWidget dropdown, Session.Slot slot,
|
||||
Session.Client c, OrderManager orderManager, Widget lobby, Action before, Action after)
|
||||
Session.Client c, OrderManager orderManager, Widget lobby, Action before, Action after)
|
||||
{
|
||||
Action<bool> okPressed = tempBan => { orderManager.IssueOrder(Order.Command("kick {0} {1}".F(c.Index, tempBan))); after(); };
|
||||
var onClick = new Action(() =>
|
||||
@@ -194,7 +194,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var options = factions.Where(f => f.Value.Selectable).GroupBy(f => f.Value.Side)
|
||||
.ToDictionary(g => g.Key ?? "", g => g.Select(f => f.Key));
|
||||
|
||||
dropdown.ShowDropDown("FACTION_DROPDOWN_TEMPLATE", 150, options, setupItem);
|
||||
dropdown.ShowDropDown("FACTION_DROPDOWN_TEMPLATE", 154, options, setupItem);
|
||||
}
|
||||
|
||||
public static void ShowColorDropDown(DropDownButtonWidget color, Session.Client client,
|
||||
@@ -419,7 +419,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
}
|
||||
|
||||
public static void SetupPlayerActionWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager,
|
||||
WorldRenderer worldRenderer, Widget lobby, Action before, Action after)
|
||||
WorldRenderer worldRenderer, Widget lobby, Action before, Action after)
|
||||
{
|
||||
var slot = parent.Get<DropDownButtonWidget>("PLAYER_ACTION");
|
||||
slot.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenRA.Graphics;
|
||||
@@ -106,11 +107,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var missionMapPaths = kv.Value.Nodes.Select(n => n.Key).ToList();
|
||||
|
||||
var previews = modData.MapCache
|
||||
.Where(p => p.Status == MapStatus.Available)
|
||||
.Where(p => p.Class == MapClassification.System && p.Status == MapStatus.Available)
|
||||
.Select(p => new
|
||||
{
|
||||
Preview = p,
|
||||
Index = missionMapPaths.IndexOf(Platform.UnresolvePath(p.Package.Name))
|
||||
Index = missionMapPaths.IndexOf(Path.GetFileName(p.Package.Name))
|
||||
})
|
||||
.Where(x => x.Index != -1)
|
||||
.OrderBy(x => x.Index)
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
public readonly int2 IconMargin = int2.Zero;
|
||||
public readonly int2 IconSpriteOffset = int2.Zero;
|
||||
|
||||
public readonly string TabClick = null;
|
||||
public readonly string DisabledTabClick = null;
|
||||
public readonly string ClickSound = ChromeMetrics.Get<string>("ClickSound");
|
||||
public readonly string ClickDisabledSound = ChromeMetrics.Get<string>("ClickDisabledSound");
|
||||
public readonly string TooltipContainer;
|
||||
public readonly string TooltipTemplate = "PRODUCTION_TOOLTIP";
|
||||
|
||||
@@ -267,14 +267,15 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
if (PickUpCompletedBuildingIcon(icon, item))
|
||||
{
|
||||
Game.Sound.Play(SoundType.UI, TabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item != null && item.Paused)
|
||||
{
|
||||
// Resume a paused item
|
||||
Game.Sound.Play(SoundType.UI, TabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.QueuedAudio, World.LocalPlayer.Faction.InternalName);
|
||||
World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, false));
|
||||
return true;
|
||||
}
|
||||
@@ -284,11 +285,11 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
if (buildable != null)
|
||||
{
|
||||
// Queue a new item
|
||||
Game.Sound.Play(SoundType.UI, TabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
string notification;
|
||||
var canQueue = CurrentQueue.CanQueue(buildable, out notification);
|
||||
|
||||
if (!CurrentQueue.AllQueued().Any(qi => qi.Item == icon.Name && !qi.Paused && qi.Infinite))
|
||||
if (!CurrentQueue.AllQueued().Any())
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", notification, World.LocalPlayer.Faction.InternalName);
|
||||
|
||||
if (canQueue)
|
||||
@@ -307,7 +308,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
if (item == null)
|
||||
return false;
|
||||
|
||||
Game.Sound.Play(SoundType.UI, TabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
|
||||
if (CurrentQueue.Info.DisallowPaused || item.Paused || item.Done || item.TotalCost == item.RemainingCost)
|
||||
{
|
||||
@@ -331,7 +332,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
return false;
|
||||
|
||||
// Directly cancel, skipping "on-hold"
|
||||
Game.Sound.Play(SoundType.UI, TabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);
|
||||
World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name, handleCount));
|
||||
|
||||
@@ -351,7 +352,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
: false;
|
||||
|
||||
if (!handled)
|
||||
Game.Sound.Play(SoundType.UI, DisabledTabClick);
|
||||
Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickDisabledSound, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,9 +80,10 @@ namespace OpenRA.Mods.D2k.Traits
|
||||
public SwallowTarget(Actor self, Target target, bool allowMovement, bool forceAttack)
|
||||
: base(self, target, allowMovement, forceAttack) { }
|
||||
|
||||
protected override Target RecalculateTarget(Actor self)
|
||||
protected override Target RecalculateTarget(Actor self, out bool targetIsHiddenActor)
|
||||
{
|
||||
// Worms ignore visibility, so don't need to recalculate targets
|
||||
targetIsHiddenActor = false;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user