Merge pull request #7263 from reaperrr/ra-common26
Moved Armament, Attack, Move, Air features and dependencies to Mods.Common
This commit is contained in:
65
OpenRA.Mods.Common/Activities/Air/FallToEarth.cs
Normal file
65
OpenRA.Mods.Common/Activities/Air/FallToEarth.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FallToEarth : Activity
|
||||
{
|
||||
int acceleration = 0;
|
||||
int spin = 0;
|
||||
FallsToEarthInfo info;
|
||||
|
||||
public FallToEarth(Actor self, FallsToEarthInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
if (info.Spins)
|
||||
acceleration = self.World.SharedRandom.Next(2) * 2 - 1;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var aircraft = self.Trait<Aircraft>();
|
||||
if (self.CenterPosition.Z <= 0)
|
||||
{
|
||||
if (info.Explosion != null)
|
||||
{
|
||||
var weapon = self.World.Map.Rules.Weapons[info.Explosion.ToLowerInvariant()];
|
||||
|
||||
// Use .FromPos since this actor is killed. Cannot use Target.FromActor
|
||||
weapon.Impact(Target.FromPos(self.CenterPosition), self, Enumerable.Empty<int>());
|
||||
}
|
||||
|
||||
self.Destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (info.Spins)
|
||||
{
|
||||
spin += acceleration;
|
||||
aircraft.Facing = (aircraft.Facing + spin) % 256;
|
||||
}
|
||||
|
||||
var move = info.Moves ? aircraft.FlyStep(aircraft.Facing) : WVec.Zero;
|
||||
move -= new WVec(WRange.Zero, WRange.Zero, info.Velocity);
|
||||
aircraft.SetPosition(self, aircraft.CenterPosition + move);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Cannot be cancelled
|
||||
public override void Cancel(Actor self) { }
|
||||
}
|
||||
}
|
||||
87
OpenRA.Mods.Common/Activities/Air/Fly.cs
Normal file
87
OpenRA.Mods.Common/Activities/Air/Fly.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. 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 Fly : Activity
|
||||
{
|
||||
readonly Plane plane;
|
||||
readonly Target target;
|
||||
readonly WRange maxRange;
|
||||
readonly WRange minRange;
|
||||
|
||||
public Fly(Actor self, Target t)
|
||||
{
|
||||
plane = self.Trait<Plane>();
|
||||
target = t;
|
||||
}
|
||||
|
||||
public Fly(Actor self, Target t, WRange minRange, WRange maxRange)
|
||||
: this(self, t)
|
||||
{
|
||||
this.maxRange = maxRange;
|
||||
this.minRange = minRange;
|
||||
}
|
||||
|
||||
public static void FlyToward(Actor self, Plane plane, int desiredFacing, WRange desiredAltitude)
|
||||
{
|
||||
var move = plane.FlyStep(plane.Facing);
|
||||
var altitude = plane.CenterPosition.Z;
|
||||
|
||||
plane.Facing = Util.TickFacing(plane.Facing, desiredFacing, plane.ROT);
|
||||
|
||||
if (altitude != desiredAltitude.Range)
|
||||
{
|
||||
var delta = move.HorizontalLength * plane.Info.MaximumPitch.Tan() / 1024;
|
||||
var dz = (desiredAltitude.Range - altitude).Clamp(-delta, delta);
|
||||
move += new WVec(0, 0, dz);
|
||||
}
|
||||
|
||||
plane.SetPosition(self, plane.CenterPosition + move);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
// Inside the target annulus, so we're done
|
||||
var insideMaxRange = maxRange.Range > 0 && target.IsInRange(plane.CenterPosition, maxRange);
|
||||
var insideMinRange = minRange.Range > 0 && target.IsInRange(plane.CenterPosition, minRange);
|
||||
if (insideMaxRange && !insideMinRange)
|
||||
return NextActivity;
|
||||
|
||||
// Close enough (ported from old code which checked length against sqrt(50) px)
|
||||
var d = target.CenterPosition - self.CenterPosition;
|
||||
if (d.HorizontalLengthSquared < 91022)
|
||||
return NextActivity;
|
||||
|
||||
var desiredFacing = Util.GetFacing(d, plane.Facing);
|
||||
|
||||
// Don't turn until we've reached the cruise altitude
|
||||
if (plane.CenterPosition.Z < plane.Info.CruiseAltitude.Range)
|
||||
desiredFacing = plane.Facing;
|
||||
|
||||
FlyToward(self, plane, desiredFacing, plane.Info.CruiseAltitude);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
OpenRA.Mods.Common/Activities/Air/FlyAttack.cs
Normal file
64
OpenRA.Mods.Common/Activities/Air/FlyAttack.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FlyAttack : Activity
|
||||
{
|
||||
readonly Target target;
|
||||
Activity inner;
|
||||
int ticksUntilTurn = 50;
|
||||
|
||||
public FlyAttack(Target target) { this.target = target; }
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (!target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
||||
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
|
||||
return NextActivity;
|
||||
|
||||
var attack = self.TraitOrDefault<AttackPlane>();
|
||||
if (attack != null)
|
||||
attack.DoAttack(self, target);
|
||||
|
||||
if (inner == null)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (target.IsInRange(self.CenterPosition, attack.Armaments.Select(a => a.Weapon.MinRange).Min()))
|
||||
inner = Util.SequenceActivities(new FlyTimed(ticksUntilTurn), new Fly(self, target), new FlyTimed(ticksUntilTurn));
|
||||
else
|
||||
inner = Util.SequenceActivities(new Fly(self, target), new FlyTimed(ticksUntilTurn));
|
||||
}
|
||||
|
||||
inner = Util.RunActivity(self, inner);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
if (!IsCanceled && inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
// NextActivity must always be set to null:
|
||||
base.Cancel(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
OpenRA.Mods.Common/Activities/Air/FlyCircle.cs
Normal file
33
OpenRA.Mods.Common/Activities/Air/FlyCircle.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FlyCircle : Activity
|
||||
{
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var plane = self.Trait<Plane>();
|
||||
|
||||
// We can't possibly turn this fast
|
||||
var desiredFacing = plane.Facing + 64;
|
||||
Fly.FlyToward(self, plane, desiredFacing, plane.Info.CruiseAltitude);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
OpenRA.Mods.Common/Activities/Air/FlyFollow.cs
Normal file
46
OpenRA.Mods.Common/Activities/Air/FlyFollow.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FlyFollow : Activity
|
||||
{
|
||||
Target target;
|
||||
Plane plane;
|
||||
WRange minRange;
|
||||
WRange maxRange;
|
||||
|
||||
public FlyFollow(Actor self, Target target, WRange minRange, WRange maxRange)
|
||||
{
|
||||
this.target = target;
|
||||
plane = self.Trait<Plane>();
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange))
|
||||
{
|
||||
Fly.FlyToward(self, plane, plane.Facing, plane.Info.CruiseAltitude);
|
||||
return this;
|
||||
}
|
||||
|
||||
return Util.SequenceActivities(new Fly(self, target, minRange, maxRange), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
OpenRA.Mods.Common/Activities/Air/FlyTimed.cs
Normal file
52
OpenRA.Mods.Common/Activities/Air/FlyTimed.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class FlyTimed : Activity
|
||||
{
|
||||
int remainingTicks;
|
||||
|
||||
public FlyTimed(int ticks) { remainingTicks = ticks; }
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || remainingTicks-- == 0)
|
||||
return NextActivity;
|
||||
|
||||
var plane = self.Trait<Plane>();
|
||||
Fly.FlyToward(self, plane, plane.Facing, plane.Info.CruiseAltitude);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class FlyOffMap : Activity
|
||||
{
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !self.World.Map.Contains(self.Location))
|
||||
return NextActivity;
|
||||
|
||||
var plane = self.Trait<Plane>();
|
||||
Fly.FlyToward(self, plane, plane.Facing, plane.Info.CruiseAltitude);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
base.Cancel(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
OpenRA.Mods.Common/Activities/Air/HeliAttack.cs
Normal file
52
OpenRA.Mods.Common/Activities/Air/HeliAttack.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class HeliAttack : Activity
|
||||
{
|
||||
Target target;
|
||||
public HeliAttack(Target target) { this.target = target; }
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
||||
var reloads = self.TraitOrDefault<Reloads>();
|
||||
if (limitedAmmo != null && !limitedAmmo.HasAmmo() && reloads == null)
|
||||
return Util.SequenceActivities(new HeliReturn(), NextActivity);
|
||||
|
||||
var helicopter = self.Trait<Helicopter>();
|
||||
var attack = self.Trait<AttackHeli>();
|
||||
var dist = target.CenterPosition - self.CenterPosition;
|
||||
|
||||
// Can rotate facing while ascending
|
||||
var desiredFacing = Util.GetFacing(dist, helicopter.Facing);
|
||||
helicopter.Facing = Util.TickFacing(helicopter.Facing, desiredFacing, helicopter.ROT);
|
||||
|
||||
if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
// Fly towards the target
|
||||
if (!target.IsInRange(self.CenterPosition, attack.GetMaximumRange()))
|
||||
helicopter.SetPosition(self, helicopter.CenterPosition + helicopter.FlyStep(desiredFacing));
|
||||
|
||||
attack.DoAttack(self, target);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
OpenRA.Mods.Common/Activities/Air/HeliFly.cs
Normal file
95
OpenRA.Mods.Common/Activities/Air/HeliFly.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. 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 HeliFly : Activity
|
||||
{
|
||||
readonly Helicopter helicopter;
|
||||
readonly Target target;
|
||||
readonly WRange maxRange;
|
||||
readonly WRange minRange;
|
||||
|
||||
public HeliFly(Actor self, Target t)
|
||||
{
|
||||
helicopter = self.Trait<Helicopter>();
|
||||
target = t;
|
||||
}
|
||||
|
||||
public HeliFly(Actor self, Target t, WRange minRange, WRange maxRange)
|
||||
: this(self, t)
|
||||
{
|
||||
this.maxRange = maxRange;
|
||||
this.minRange = minRange;
|
||||
}
|
||||
|
||||
public static bool AdjustAltitude(Actor self, Helicopter helicopter, WRange targetAltitude)
|
||||
{
|
||||
var altitude = helicopter.CenterPosition.Z;
|
||||
if (altitude == targetAltitude.Range)
|
||||
return false;
|
||||
|
||||
var delta = helicopter.Info.AltitudeVelocity.Range;
|
||||
var dz = (targetAltitude.Range - altitude).Clamp(-delta, delta);
|
||||
helicopter.SetPosition(self, helicopter.CenterPosition + new WVec(0, 0, dz));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
if (AdjustAltitude(self, helicopter, helicopter.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
var pos = target.CenterPosition;
|
||||
|
||||
// Rotate towards the target
|
||||
var dist = pos - self.CenterPosition;
|
||||
var desiredFacing = Util.GetFacing(dist, helicopter.Facing);
|
||||
helicopter.Facing = Util.TickFacing(helicopter.Facing, desiredFacing, helicopter.ROT);
|
||||
var move = helicopter.FlyStep(desiredFacing);
|
||||
|
||||
// Inside the minimum range, so reverse
|
||||
if (minRange.Range > 0 && target.IsInRange(helicopter.CenterPosition, minRange))
|
||||
{
|
||||
helicopter.SetPosition(self, helicopter.CenterPosition - move);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Inside the maximum range, so we're done
|
||||
if (maxRange.Range > 0 && target.IsInRange(helicopter.CenterPosition, maxRange))
|
||||
return NextActivity;
|
||||
|
||||
// The next move would overshoot, so just set the final position
|
||||
if (dist.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
{
|
||||
helicopter.SetPosition(self, pos + new WVec(0, 0, helicopter.Info.CruiseAltitude.Range - pos.Z));
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
helicopter.SetPosition(self, helicopter.CenterPosition + move);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
yield return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
OpenRA.Mods.Common/Activities/Air/HeliFlyCircle.cs
Normal file
45
OpenRA.Mods.Common/Activities/Air/HeliFlyCircle.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class HeliFlyCircle : Activity
|
||||
{
|
||||
readonly Helicopter helicopter;
|
||||
|
||||
public HeliFlyCircle(Actor self)
|
||||
{
|
||||
helicopter = self.Trait<Helicopter>();
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.CruiseAltitude))
|
||||
return this;
|
||||
|
||||
var move = helicopter.FlyStep(helicopter.Facing);
|
||||
helicopter.SetPosition(self, helicopter.CenterPosition + move);
|
||||
|
||||
var desiredFacing = helicopter.Facing + 64;
|
||||
helicopter.Facing = Util.TickFacing(helicopter.Facing, desiredFacing, helicopter.ROT / 3);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
OpenRA.Mods.Common/Activities/Air/HeliLand.cs
Normal file
41
OpenRA.Mods.Common/Activities/Air/HeliLand.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class HeliLand : Activity
|
||||
{
|
||||
bool requireSpace;
|
||||
|
||||
public HeliLand(bool requireSpace)
|
||||
{
|
||||
this.requireSpace = requireSpace;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var helicopter = self.Trait<Helicopter>();
|
||||
|
||||
if (requireSpace && !helicopter.CanLand(self.Location))
|
||||
return this;
|
||||
|
||||
if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.LandAltitude))
|
||||
return this;
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
OpenRA.Mods.Common/Activities/Air/HeliReturn.cs
Normal file
68
OpenRA.Mods.Common/Activities/Air/HeliReturn.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class HeliReturn : Activity
|
||||
{
|
||||
static Actor ChooseHelipad(Actor self)
|
||||
{
|
||||
var rearmBuildings = self.Info.Traits.Get<HelicopterInfo>().RearmBuildings;
|
||||
return self.World.Actors.Where(a => a.Owner == self.Owner).FirstOrDefault(
|
||||
a => rearmBuildings.Contains(a.Info.Name) && !Reservable.IsReserved(a));
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var dest = ChooseHelipad(self);
|
||||
var initialFacing = self.Info.Traits.Get<AircraftInfo>().InitialFacing;
|
||||
|
||||
if (dest == null)
|
||||
{
|
||||
var rearmBuildings = self.Info.Traits.Get<HelicopterInfo>().RearmBuildings;
|
||||
var nearestHpad = self.World.ActorsWithTrait<Reservable>()
|
||||
.Where(a => a.Actor.Owner == self.Owner && rearmBuildings.Contains(a.Actor.Info.Name))
|
||||
.Select(a => a.Actor)
|
||||
.ClosestTo(self);
|
||||
|
||||
if (nearestHpad == null)
|
||||
return Util.SequenceActivities(new Turn(self, initialFacing), new HeliLand(true), NextActivity);
|
||||
else
|
||||
return Util.SequenceActivities(new HeliFly(self, Target.FromActor(nearestHpad)));
|
||||
}
|
||||
|
||||
var res = dest.TraitOrDefault<Reservable>();
|
||||
var heli = self.Trait<Helicopter>();
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
heli.UnReserve();
|
||||
heli.Reservation = res.Reserve(dest, self, heli);
|
||||
}
|
||||
|
||||
var exit = dest.Info.Traits.WithInterface<ExitInfo>().FirstOrDefault();
|
||||
var offset = (exit != null) ? exit.SpawnOffset : WVec.Zero;
|
||||
|
||||
return Util.SequenceActivities(
|
||||
new HeliFly(self, Target.FromPos(dest.CenterPosition + offset)),
|
||||
new Turn(self, initialFacing),
|
||||
new HeliLand(false),
|
||||
new ResupplyAircraft());
|
||||
}
|
||||
}
|
||||
}
|
||||
48
OpenRA.Mods.Common/Activities/Air/Land.cs
Normal file
48
OpenRA.Mods.Common/Activities/Air/Land.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Land : Activity
|
||||
{
|
||||
Target target;
|
||||
|
||||
public Land(Target t) { target = t; }
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (!target.IsValidFor(self))
|
||||
Cancel(self);
|
||||
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var plane = self.Trait<Plane>();
|
||||
var d = target.CenterPosition - self.CenterPosition;
|
||||
|
||||
// The next move would overshoot, so just set the final position
|
||||
var move = plane.FlyStep(plane.Facing);
|
||||
if (d.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||
{
|
||||
plane.SetPosition(self, target.CenterPosition);
|
||||
return NextActivity;
|
||||
}
|
||||
|
||||
var desiredFacing = Util.GetFacing(d, plane.Facing);
|
||||
Fly.FlyToward(self, plane, desiredFacing, WRange.Zero);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
OpenRA.Mods.Common/Activities/Air/ResupplyAircraft.cs
Normal file
35
OpenRA.Mods.Common/Activities/Air/ResupplyAircraft.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class ResupplyAircraft : Activity
|
||||
{
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var aircraft = self.Trait<Aircraft>();
|
||||
var host = aircraft.GetActorBelow();
|
||||
|
||||
if (host == null)
|
||||
return NextActivity;
|
||||
|
||||
return Util.SequenceActivities(
|
||||
aircraft.GetResupplyActivities(host).Append(NextActivity).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
127
OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs
Normal file
127
OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class ReturnToBase : Activity
|
||||
{
|
||||
bool isCalculated;
|
||||
Actor dest;
|
||||
WPos w1, w2, w3;
|
||||
|
||||
public static Actor ChooseAirfield(Actor self, bool unreservedOnly)
|
||||
{
|
||||
var rearmBuildings = self.Info.Traits.Get<PlaneInfo>().RearmBuildings;
|
||||
return self.World.ActorsWithTrait<Reservable>()
|
||||
.Where(a => a.Actor.Owner == self.Owner)
|
||||
.Where(a => rearmBuildings.Contains(a.Actor.Info.Name)
|
||||
&& (!unreservedOnly || !Reservable.IsReserved(a.Actor)))
|
||||
.Select(a => a.Actor)
|
||||
.ClosestTo(self);
|
||||
}
|
||||
|
||||
void Calculate(Actor self)
|
||||
{
|
||||
if (dest == null || Reservable.IsReserved(dest))
|
||||
dest = ChooseAirfield(self, true);
|
||||
|
||||
if (dest == null)
|
||||
return;
|
||||
|
||||
var plane = self.Trait<Plane>();
|
||||
var planeInfo = self.Info.Traits.Get<PlaneInfo>();
|
||||
var res = dest.TraitOrDefault<Reservable>();
|
||||
if (res != null)
|
||||
{
|
||||
plane.UnReserve();
|
||||
plane.Reservation = res.Reserve(dest, self, plane);
|
||||
}
|
||||
|
||||
var landPos = dest.CenterPosition;
|
||||
var altitude = planeInfo.CruiseAltitude.Range;
|
||||
|
||||
// Distance required for descent.
|
||||
var landDistance = altitude * 1024 / plane.Info.MaximumPitch.Tan();
|
||||
|
||||
// Land towards the east
|
||||
var approachStart = landPos + new WVec(-landDistance, 0, altitude);
|
||||
|
||||
// Add 10% to the turning radius to ensure we have enough room
|
||||
var speed = plane.MovementSpeed * 32 / 35;
|
||||
var turnRadius = (int)(141 * speed / planeInfo.ROT / (float)Math.PI);
|
||||
|
||||
// Find the center of the turning circles for clockwise and counterclockwise turns
|
||||
var angle = WAngle.FromFacing(plane.Facing);
|
||||
var fwd = -new WVec(angle.Sin(), angle.Cos(), 0);
|
||||
|
||||
// Work out whether we should turn clockwise or counter-clockwise for approach
|
||||
var side = new WVec(-fwd.Y, fwd.X, fwd.Z);
|
||||
var approachDelta = self.CenterPosition - approachStart;
|
||||
var sideTowardBase = new[] { side, -side }
|
||||
.MinBy(a => WVec.Dot(a, approachDelta));
|
||||
|
||||
// Calculate the tangent line that joins the turning circles at the current and approach positions
|
||||
var cp = self.CenterPosition + turnRadius * sideTowardBase / 1024;
|
||||
var posCenter = new WPos(cp.X, cp.Y, altitude);
|
||||
var approachCenter = approachStart + new WVec(0, turnRadius * Math.Sign(self.CenterPosition.Y - approachStart.Y), 0);
|
||||
var tangentDirection = approachCenter - posCenter;
|
||||
var tangentOffset = new WVec(-tangentDirection.Y, tangentDirection.X, 0) * turnRadius / tangentDirection.Length;
|
||||
|
||||
// TODO: correctly handle CCW <-> CW turns
|
||||
if (tangentOffset.X > 0)
|
||||
tangentOffset = -tangentOffset;
|
||||
|
||||
w1 = posCenter + tangentOffset;
|
||||
w2 = approachCenter + tangentOffset;
|
||||
w3 = approachStart;
|
||||
plane.RTBPathHash = w1 + (WVec)w2 + (WVec)w3;
|
||||
|
||||
isCalculated = true;
|
||||
}
|
||||
|
||||
public ReturnToBase(Actor self, Actor dest)
|
||||
{
|
||||
this.dest = dest;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || self.IsDead)
|
||||
return NextActivity;
|
||||
|
||||
if (!isCalculated)
|
||||
Calculate(self);
|
||||
|
||||
if (dest == null)
|
||||
{
|
||||
var nearestAfld = ChooseAirfield(self, false);
|
||||
|
||||
self.CancelActivity();
|
||||
if (nearestAfld != null)
|
||||
return Util.SequenceActivities(new Fly(self, Target.FromActor(nearestAfld)), new FlyCircle());
|
||||
else
|
||||
return new FlyCircle();
|
||||
}
|
||||
|
||||
return Util.SequenceActivities(
|
||||
new Fly(self, Target.FromPos(w1)),
|
||||
new Fly(self, Target.FromPos(w2)),
|
||||
new Fly(self, Target.FromPos(w3)),
|
||||
new Land(Target.FromActor(dest)),
|
||||
NextActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
OpenRA.Mods.Common/Activities/Air/TakeOff.cs
Normal file
42
OpenRA.Mods.Common/Activities/Air/TakeOff.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class TakeOff : Activity
|
||||
{
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var aircraft = self.Trait<Aircraft>();
|
||||
|
||||
self.CancelActivity();
|
||||
|
||||
var reservation = aircraft.Reservation;
|
||||
if (reservation != null)
|
||||
{
|
||||
reservation.Dispose();
|
||||
reservation = null;
|
||||
}
|
||||
|
||||
var host = aircraft.GetActorBelow();
|
||||
var hasHost = host != null;
|
||||
var rp = hasHost ? host.TraitOrDefault<RallyPoint>() : null;
|
||||
|
||||
var destination = rp != null ? rp.Location :
|
||||
(hasHost ? self.World.Map.CellContaining(host.CenterPosition) : self.Location);
|
||||
|
||||
return new AttackMoveActivity(self, self.Trait<IMove>().MoveTo(destination, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
80
OpenRA.Mods.Common/Activities/Attack.cs
Normal file
80
OpenRA.Mods.Common/Activities/Attack.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
/* non-turreted attack */
|
||||
public class Attack : Activity
|
||||
{
|
||||
protected readonly Target Target;
|
||||
readonly AttackBase attack;
|
||||
readonly IMove move;
|
||||
readonly IFacing facing;
|
||||
readonly WRange minRange;
|
||||
readonly WRange maxRange;
|
||||
readonly IPositionable positionable;
|
||||
|
||||
public Attack(Actor self, Target target, WRange minRange, WRange maxRange, bool allowMovement)
|
||||
{
|
||||
Target = target;
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
|
||||
attack = self.Trait<AttackBase>();
|
||||
facing = self.Trait<IFacing>();
|
||||
positionable = self.Trait<IPositionable>();
|
||||
|
||||
move = allowMovement ? self.TraitOrDefault<IMove>() : null;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var ret = InnerTick(self, attack);
|
||||
attack.IsAttacking = ret == this;
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected virtual Activity InnerTick(Actor self, AttackBase attack)
|
||||
{
|
||||
if (IsCanceled)
|
||||
return NextActivity;
|
||||
|
||||
var type = Target.Type;
|
||||
if (!Target.IsValidFor(self) || type == TargetType.FrozenActor)
|
||||
return NextActivity;
|
||||
|
||||
if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(Target.Actor.Location, null, false))
|
||||
return NextActivity;
|
||||
|
||||
// Drop the target if it moves under the shroud / fog.
|
||||
// HACK: This would otherwise break targeting frozen actors
|
||||
// The problem is that Shroud.IsTargetable returns false (as it should) for
|
||||
// frozen actors, but we do want to explicitly target the underlying actor here.
|
||||
if (type == TargetType.Actor && !Target.Actor.HasTrait<FrozenUnderFog>() && !self.Owner.Shroud.IsTargetable(Target.Actor))
|
||||
return NextActivity;
|
||||
|
||||
// Try to move within range
|
||||
if (move != null && (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange)))
|
||||
return Util.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this);
|
||||
|
||||
var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0);
|
||||
if (facing.Facing != desiredFacing)
|
||||
return Util.SequenceActivities(new Turn(self, desiredFacing), this);
|
||||
|
||||
attack.DoAttack(self, Target);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
OpenRA.Mods.Common/Activities/EnterTransport.cs
Normal file
71
OpenRA.Mods.Common/Activities/EnterTransport.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
class EnterTransport : Enter
|
||||
{
|
||||
readonly Actor transport;
|
||||
readonly Passenger passenger;
|
||||
readonly int maxTries;
|
||||
Cargo cargo;
|
||||
|
||||
public EnterTransport(Actor self, Actor transport, int maxTries = 0, bool targetCenter = false)
|
||||
: base(self, transport, maxTries, targetCenter)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var status = base.Reserve(self);
|
||||
if (status != ReserveStatus.Ready)
|
||||
return status;
|
||||
if (passenger.Reserve(self, cargo))
|
||||
return ReserveStatus.Ready;
|
||||
return ReserveStatus.Pending;
|
||||
}
|
||||
|
||||
protected override void OnInside(Actor self)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (self.IsDead || transport.IsDead || !cargo.CanLoad(transport, self))
|
||||
return;
|
||||
|
||||
cargo.Load(transport, self);
|
||||
w.Remove(self);
|
||||
});
|
||||
|
||||
Done(self);
|
||||
}
|
||||
|
||||
protected override bool TryGetAlternateTarget(Actor self, int tries, ref Target target)
|
||||
{
|
||||
if (tries > maxTries)
|
||||
return false;
|
||||
var type = target.Actor.Info.Name;
|
||||
return TryGetAlternateTargetInCircle(
|
||||
self, passenger.Info.AlternateTransportScanRange,
|
||||
t => cargo = t.Actor.Trait<Cargo>(), // update cargo
|
||||
a => { var c = a.TraitOrDefault<Cargo>(); return c != null && (c.Unloading || c.CanLoad(a, self)); },
|
||||
new Func<Actor, bool>[] { a => a.Info.Name == type }); // Prefer transports of the same type
|
||||
}
|
||||
}
|
||||
}
|
||||
33
OpenRA.Mods.Common/Activities/Heal.cs
Normal file
33
OpenRA.Mods.Common/Activities/Heal.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Heal : Attack
|
||||
{
|
||||
public Heal(Actor self, Target target, WRange minRange, WRange maxRange, bool allowMovement)
|
||||
: base(self, target, minRange, maxRange, allowMovement) { }
|
||||
|
||||
protected override Activity InnerTick(Actor self, AttackBase attack)
|
||||
{
|
||||
if (!Target.IsValidFor(self) || !self.Owner.IsAlliedWith(Target.Actor.Owner))
|
||||
return NextActivity;
|
||||
|
||||
if (Target.Type == TargetType.Actor && Target.Actor.GetDamageState() == DamageState.Undamaged)
|
||||
return NextActivity;
|
||||
|
||||
return base.InnerTick(self, attack);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs
Normal file
64
OpenRA.Mods.Common/Activities/Move/AttackMoveActivity.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. 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 AttackMoveActivity : Activity
|
||||
{
|
||||
const int ScanInterval = 7;
|
||||
|
||||
Activity inner;
|
||||
int scanTicks;
|
||||
AutoTarget autoTarget;
|
||||
|
||||
public AttackMoveActivity(Actor self, Activity inner)
|
||||
{
|
||||
this.inner = inner;
|
||||
autoTarget = self.TraitOrDefault<AutoTarget>();
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (autoTarget != null && --scanTicks <= 0)
|
||||
{
|
||||
autoTarget.ScanAndAttack(self);
|
||||
scanTicks = ScanInterval;
|
||||
}
|
||||
|
||||
if (inner == null)
|
||||
return NextActivity;
|
||||
|
||||
inner = Util.RunActivity(self, inner);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
if (inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
base.Cancel(self);
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
if (inner != null)
|
||||
return inner.GetTargets(self);
|
||||
|
||||
return Target.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
OpenRA.Mods.Common/Activities/Move/Follow.cs
Normal file
50
OpenRA.Mods.Common/Activities/Move/Follow.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Follow : Activity
|
||||
{
|
||||
readonly Target target;
|
||||
readonly WRange minRange;
|
||||
readonly WRange maxRange;
|
||||
readonly IMove move;
|
||||
|
||||
public Follow(Actor self, Target target, WRange minRange, WRange maxRange)
|
||||
{
|
||||
this.target = target;
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
|
||||
move = self.Trait<IMove>();
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
var cachedPosition = target.CenterPosition;
|
||||
var path = move.MoveWithinRange(target, minRange, maxRange);
|
||||
|
||||
// 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 Util.SequenceActivities(wait, path, this);
|
||||
}
|
||||
|
||||
return Util.SequenceActivities(path, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
451
OpenRA.Mods.Common/Activities/Move/Move.cs
Normal file
451
OpenRA.Mods.Common/Activities/Move/Move.cs
Normal file
@@ -0,0 +1,451 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Move : Activity
|
||||
{
|
||||
static readonly List<CPos> NoPath = new List<CPos>();
|
||||
|
||||
readonly Mobile mobile;
|
||||
readonly IEnumerable<IDisableMove> moveDisablers;
|
||||
readonly WRange nearEnough;
|
||||
readonly Func<List<CPos>> getPath;
|
||||
readonly Actor ignoredActor;
|
||||
|
||||
List<CPos> path;
|
||||
CPos? destination;
|
||||
|
||||
// For dealing with blockers
|
||||
bool hasWaited;
|
||||
bool hasNotifiedBlocker;
|
||||
int waitTicksRemaining;
|
||||
|
||||
// Scriptable move order
|
||||
// Ignores lane bias and nearby units
|
||||
public Move(Actor self, CPos destination)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
getPath = () =>
|
||||
self.World.WorldActor.Trait<PathFinder>().FindPath(
|
||||
PathSearch.FromPoint(self.World, mobile.Info, self, mobile.ToCell, destination, false)
|
||||
.WithoutLaneBias());
|
||||
this.destination = destination;
|
||||
this.nearEnough = WRange.Zero;
|
||||
}
|
||||
|
||||
// HACK: for legacy code
|
||||
public Move(Actor self, CPos destination, int nearEnough)
|
||||
: this(self, destination, WRange.FromCells(nearEnough)) { }
|
||||
|
||||
public Move(Actor self, CPos destination, WRange nearEnough)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
getPath = () => self.World.WorldActor.Trait<PathFinder>()
|
||||
.FindUnitPath(mobile.ToCell, destination, self);
|
||||
this.destination = destination;
|
||||
this.nearEnough = nearEnough;
|
||||
}
|
||||
|
||||
public Move(Actor self, CPos destination, SubCell subCell, WRange nearEnough)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
getPath = () => self.World.WorldActor.Trait<PathFinder>()
|
||||
.FindUnitPathToRange(mobile.FromCell, subCell, self.World.Map.CenterOfSubCell(destination, subCell), nearEnough, self);
|
||||
this.destination = destination;
|
||||
this.nearEnough = nearEnough;
|
||||
}
|
||||
|
||||
public Move(Actor self, CPos destination, Actor ignoredActor)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
getPath = () =>
|
||||
self.World.WorldActor.Trait<PathFinder>().FindPath(
|
||||
PathSearch.FromPoint(self.World, mobile.Info, self, mobile.ToCell, destination, false)
|
||||
.WithIgnoredActor(ignoredActor));
|
||||
|
||||
this.destination = destination;
|
||||
this.nearEnough = WRange.Zero;
|
||||
this.ignoredActor = ignoredActor;
|
||||
}
|
||||
|
||||
public Move(Actor self, Target target, WRange range)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
getPath = () =>
|
||||
{
|
||||
if (!target.IsValidFor(self))
|
||||
return NoPath;
|
||||
|
||||
return self.World.WorldActor.Trait<PathFinder>().FindUnitPathToRange(
|
||||
mobile.ToCell, mobile.ToSubCell, target.CenterPosition, range, self);
|
||||
};
|
||||
|
||||
destination = null;
|
||||
nearEnough = range;
|
||||
}
|
||||
|
||||
public Move(Actor self, Func<List<CPos>> getPath)
|
||||
{
|
||||
mobile = self.Trait<Mobile>();
|
||||
moveDisablers = self.TraitsImplementing<IDisableMove>();
|
||||
|
||||
this.getPath = getPath;
|
||||
|
||||
destination = null;
|
||||
nearEnough = WRange.Zero;
|
||||
}
|
||||
|
||||
static int HashList<T>(List<T> xs)
|
||||
{
|
||||
var hash = 0;
|
||||
var n = 0;
|
||||
foreach (var x in xs)
|
||||
hash += n++ * x.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
List<CPos> EvalPath(Actor self, Mobile mobile)
|
||||
{
|
||||
var path = getPath().TakeWhile(a => a != mobile.ToCell).ToList();
|
||||
mobile.PathHash = HashList(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (moveDisablers.Any(d => d.MoveDisabled(self)))
|
||||
return this;
|
||||
|
||||
if (destination == mobile.ToCell)
|
||||
return NextActivity;
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
if (mobile.TicksBeforePathing > 0)
|
||||
{
|
||||
--mobile.TicksBeforePathing;
|
||||
return this;
|
||||
}
|
||||
|
||||
path = EvalPath(self, mobile);
|
||||
SanityCheckPath(mobile);
|
||||
}
|
||||
|
||||
if (path.Count == 0)
|
||||
{
|
||||
destination = mobile.ToCell;
|
||||
return this;
|
||||
}
|
||||
|
||||
destination = path[0];
|
||||
|
||||
var nextCell = PopPath(self, mobile);
|
||||
if (nextCell == null)
|
||||
return this;
|
||||
|
||||
var firstFacing = self.World.Map.FacingBetween(mobile.FromCell, nextCell.Value.First, mobile.Facing);
|
||||
if (firstFacing != mobile.Facing)
|
||||
{
|
||||
path.Add(nextCell.Value.First);
|
||||
return Util.SequenceActivities(new Turn(self, firstFacing), this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, nextCell.Value.First, nextCell.Value.Second);
|
||||
var move = new MoveFirstHalf(
|
||||
this,
|
||||
self.World.Map.CenterOfSubCell(mobile.FromCell, mobile.FromSubCell),
|
||||
Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (self.World.Map.OffsetOfSubCell(mobile.FromSubCell) + self.World.Map.OffsetOfSubCell(mobile.ToSubCell)) / 2,
|
||||
mobile.Facing,
|
||||
mobile.Facing,
|
||||
0);
|
||||
|
||||
return move;
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("SANITY_CHECKS")]
|
||||
void SanityCheckPath(Mobile mobile)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
return;
|
||||
var d = path[path.Count - 1] - mobile.ToCell;
|
||||
if (d.LengthSquared > 2)
|
||||
throw new InvalidOperationException("(Move) Sanity check failed");
|
||||
}
|
||||
|
||||
static void NotifyBlocker(Actor self, CPos nextCell)
|
||||
{
|
||||
foreach (var blocker in self.World.ActorMap.GetUnitsAt(nextCell))
|
||||
{
|
||||
// Notify the blocker that he's blocking our move:
|
||||
foreach (var moveBlocked in blocker.TraitsImplementing<INotifyBlockingMove>())
|
||||
moveBlocked.OnNotifyBlockingMove(blocker, self);
|
||||
}
|
||||
}
|
||||
|
||||
Pair<CPos, SubCell>? PopPath(Actor self, Mobile mobile)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
return null;
|
||||
|
||||
var nextCell = path[path.Count - 1];
|
||||
|
||||
// Next cell in the move is blocked by another actor
|
||||
if (!mobile.CanEnterCell(nextCell, ignoredActor, true))
|
||||
{
|
||||
// Are we close enough?
|
||||
var cellRange = nearEnough.Range / 1024;
|
||||
if ((mobile.ToCell - destination.Value).LengthSquared <= cellRange * cellRange)
|
||||
{
|
||||
path.Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
// See if they will move
|
||||
if (!hasNotifiedBlocker)
|
||||
{
|
||||
NotifyBlocker(self, nextCell);
|
||||
hasNotifiedBlocker = true;
|
||||
}
|
||||
|
||||
// Wait a bit to see if they leave
|
||||
if (!hasWaited)
|
||||
{
|
||||
var info = self.Info.Traits.Get<MobileInfo>();
|
||||
waitTicksRemaining = info.WaitAverage + self.World.SharedRandom.Next(-info.WaitSpread, info.WaitSpread);
|
||||
hasWaited = true;
|
||||
}
|
||||
|
||||
if (--waitTicksRemaining >= 0)
|
||||
return null;
|
||||
|
||||
if (mobile.TicksBeforePathing > 0)
|
||||
{
|
||||
--mobile.TicksBeforePathing;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate a new path
|
||||
mobile.RemoveInfluence();
|
||||
var newPath = EvalPath(self, mobile);
|
||||
mobile.AddInfluence();
|
||||
|
||||
if (newPath.Count != 0)
|
||||
path = newPath;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
hasNotifiedBlocker = false;
|
||||
hasWaited = false;
|
||||
path.RemoveAt(path.Count - 1);
|
||||
|
||||
var subCell = mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoredActor);
|
||||
return Pair.New(nextCell, subCell);
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
path = NoPath;
|
||||
base.Cancel(self);
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
if (path != null)
|
||||
return Enumerable.Reverse(path).Select(c => Target.FromCell(self.World, c));
|
||||
if (destination != null)
|
||||
return new Target[] { Target.FromCell(self.World, destination.Value) };
|
||||
return Target.None;
|
||||
}
|
||||
|
||||
abstract class MovePart : Activity
|
||||
{
|
||||
protected readonly Move Move;
|
||||
protected readonly WPos From, To;
|
||||
protected readonly int FromFacing, ToFacing;
|
||||
protected readonly int MoveFractionTotal;
|
||||
protected int moveFraction;
|
||||
|
||||
public MovePart(Move move, WPos from, WPos to, int fromFacing, int toFacing, int startingFraction)
|
||||
{
|
||||
Move = move;
|
||||
From = from;
|
||||
To = to;
|
||||
FromFacing = fromFacing;
|
||||
ToFacing = toFacing;
|
||||
moveFraction = startingFraction;
|
||||
MoveFractionTotal = (to - from).Length;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
Move.Cancel(self);
|
||||
base.Cancel(self);
|
||||
}
|
||||
|
||||
public override void Queue(Activity activity)
|
||||
{
|
||||
Move.Queue(activity);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var mobile = self.Trait<Mobile>();
|
||||
var ret = InnerTick(self, Move.mobile);
|
||||
mobile.IsMoving = ret is MovePart;
|
||||
|
||||
if (moveFraction > MoveFractionTotal)
|
||||
moveFraction = MoveFractionTotal;
|
||||
UpdateCenterLocation(self, mobile);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Activity InnerTick(Actor self, Mobile mobile)
|
||||
{
|
||||
moveFraction += mobile.MovementSpeedForCell(self, mobile.ToCell);
|
||||
if (moveFraction <= MoveFractionTotal)
|
||||
return this;
|
||||
|
||||
var next = OnComplete(self, mobile, Move);
|
||||
if (next != null)
|
||||
return next;
|
||||
|
||||
return Move;
|
||||
}
|
||||
|
||||
void UpdateCenterLocation(Actor self, Mobile mobile)
|
||||
{
|
||||
// avoid division through zero
|
||||
if (MoveFractionTotal != 0)
|
||||
mobile.SetVisualPosition(self, WPos.Lerp(From, To, moveFraction, MoveFractionTotal));
|
||||
else
|
||||
mobile.SetVisualPosition(self, To);
|
||||
|
||||
if (moveFraction >= MoveFractionTotal)
|
||||
mobile.Facing = ToFacing & 0xFF;
|
||||
else
|
||||
mobile.Facing = int2.Lerp(FromFacing, ToFacing, moveFraction, MoveFractionTotal) & 0xFF;
|
||||
}
|
||||
|
||||
protected abstract MovePart OnComplete(Actor self, Mobile mobile, Move parent);
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
return Move.GetTargets(self);
|
||||
}
|
||||
}
|
||||
|
||||
class MoveFirstHalf : MovePart
|
||||
{
|
||||
public MoveFirstHalf(Move move, WPos from, WPos to, int fromFacing, int toFacing, int startingFraction)
|
||||
: base(move, from, to, fromFacing, toFacing, startingFraction) { }
|
||||
|
||||
static bool IsTurn(Mobile mobile, CPos nextCell)
|
||||
{
|
||||
return nextCell - mobile.ToCell !=
|
||||
mobile.ToCell - mobile.FromCell;
|
||||
}
|
||||
|
||||
protected override MovePart OnComplete(Actor self, Mobile mobile, Move parent)
|
||||
{
|
||||
var fromSubcellOffset = self.World.Map.OffsetOfSubCell(mobile.FromSubCell);
|
||||
var toSubcellOffset = self.World.Map.OffsetOfSubCell(mobile.ToSubCell);
|
||||
|
||||
var nextCell = parent.PopPath(self, mobile);
|
||||
if (nextCell != null)
|
||||
{
|
||||
if (IsTurn(mobile, nextCell.Value.First))
|
||||
{
|
||||
var nextSubcellOffset = self.World.Map.OffsetOfSubCell(nextCell.Value.Second);
|
||||
var ret = new MoveFirstHalf(
|
||||
Move,
|
||||
Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (fromSubcellOffset + toSubcellOffset) / 2,
|
||||
Util.BetweenCells(self.World, mobile.ToCell, nextCell.Value.First) + (toSubcellOffset + nextSubcellOffset) / 2,
|
||||
mobile.Facing,
|
||||
Util.GetNearestFacing(mobile.Facing, self.World.Map.FacingBetween(mobile.ToCell, nextCell.Value.First, mobile.Facing)),
|
||||
moveFraction - MoveFractionTotal);
|
||||
|
||||
mobile.FinishedMoving(self);
|
||||
mobile.SetLocation(mobile.ToCell, mobile.ToSubCell, nextCell.Value.First, nextCell.Value.Second);
|
||||
return ret;
|
||||
}
|
||||
|
||||
parent.path.Add(nextCell.Value.First);
|
||||
}
|
||||
|
||||
var ret2 = new MoveSecondHalf(
|
||||
Move,
|
||||
Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (fromSubcellOffset + toSubcellOffset) / 2,
|
||||
self.World.Map.CenterOfCell(mobile.ToCell) + toSubcellOffset,
|
||||
mobile.Facing,
|
||||
mobile.Facing,
|
||||
moveFraction - MoveFractionTotal);
|
||||
|
||||
mobile.EnteringCell(self);
|
||||
mobile.SetLocation(mobile.ToCell, mobile.ToSubCell, mobile.ToCell, mobile.ToSubCell);
|
||||
return ret2;
|
||||
}
|
||||
}
|
||||
|
||||
class MoveSecondHalf : MovePart
|
||||
{
|
||||
public MoveSecondHalf(Move move, WPos from, WPos to, int fromFacing, int toFacing, int startingFraction)
|
||||
: base(move, from, to, fromFacing, toFacing, startingFraction) { }
|
||||
|
||||
protected override MovePart OnComplete(Actor self, Mobile mobile, Move parent)
|
||||
{
|
||||
mobile.SetPosition(self, mobile.ToCell);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActorExtensionsForMove
|
||||
{
|
||||
public static bool IsMoving(this Actor self)
|
||||
{
|
||||
var a = self.GetCurrentActivity();
|
||||
if (a == null)
|
||||
return false;
|
||||
|
||||
// HACK: Dirty, but it suffices until we do something better:
|
||||
if (a.GetType() == typeof(Move)) return true;
|
||||
if (a.GetType() == typeof(MoveAdjacentTo)) return true;
|
||||
if (a.GetType() == typeof(AttackMoveActivity)) return true;
|
||||
|
||||
// Not a move:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
144
OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs
Normal file
144
OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class MoveAdjacentTo : Activity
|
||||
{
|
||||
static readonly List<CPos> NoPath = new List<CPos>();
|
||||
|
||||
readonly Mobile mobile;
|
||||
readonly PathFinder pathFinder;
|
||||
readonly DomainIndex domainIndex;
|
||||
readonly uint movementClass;
|
||||
|
||||
protected Target Target { get; private set; }
|
||||
protected CPos targetPosition;
|
||||
Activity inner;
|
||||
bool repath;
|
||||
|
||||
public MoveAdjacentTo(Actor self, Target target)
|
||||
{
|
||||
Target = target;
|
||||
|
||||
mobile = self.Trait<Mobile>();
|
||||
pathFinder = self.World.WorldActor.Trait<PathFinder>();
|
||||
domainIndex = self.World.WorldActor.Trait<DomainIndex>();
|
||||
movementClass = (uint)mobile.Info.GetMovementClass(self.World.TileSet);
|
||||
|
||||
if (target.IsValidFor(self))
|
||||
targetPosition = self.World.Map.CellContaining(target.CenterPosition);
|
||||
|
||||
repath = true;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldStop(Actor self, CPos oldTargetPosition)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldRepath(Actor self, CPos oldTargetPosition)
|
||||
{
|
||||
return targetPosition != oldTargetPosition;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<CPos> CandidateMovementCells(Actor self)
|
||||
{
|
||||
return Util.AdjacentCells(self.World, Target);
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var targetIsValid = Target.IsValidFor(self);
|
||||
|
||||
// Inner move order has completed.
|
||||
if (inner == null)
|
||||
{
|
||||
// We are done here if the order was cancelled for any
|
||||
// reason except the target moving.
|
||||
if (IsCanceled || !repath || !targetIsValid)
|
||||
return NextActivity;
|
||||
|
||||
// Target has moved, and MoveAdjacentTo is still valid.
|
||||
inner = mobile.MoveTo(() => CalculatePathToTarget(self));
|
||||
repath = false;
|
||||
}
|
||||
|
||||
if (targetIsValid)
|
||||
{
|
||||
// Check if the target has moved
|
||||
var oldTargetPosition = targetPosition;
|
||||
targetPosition = self.World.Map.CellContaining(Target.CenterPosition);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Ticks the inner move activity to actually move the actor.
|
||||
inner = Util.RunActivity(self, inner);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
List<CPos> CalculatePathToTarget(Actor self)
|
||||
{
|
||||
var targetCells = CandidateMovementCells(self);
|
||||
var searchCells = new List<CPos>();
|
||||
var loc = self.Location;
|
||||
|
||||
foreach (var cell in targetCells)
|
||||
if (domainIndex.IsPassable(loc, cell, movementClass) && mobile.CanEnterCell(cell))
|
||||
searchCells.Add(cell);
|
||||
|
||||
if (!searchCells.Any())
|
||||
return NoPath;
|
||||
|
||||
var fromSrc = PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true);
|
||||
var fromDest = PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).Reverse();
|
||||
|
||||
return pathFinder.FindBidiPath(fromSrc, fromDest);
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
{
|
||||
if (inner != null)
|
||||
return inner.GetTargets(self);
|
||||
|
||||
return Target.None;
|
||||
}
|
||||
|
||||
public override void Cancel(Actor self)
|
||||
{
|
||||
if (inner != null)
|
||||
inner.Cancel(self);
|
||||
|
||||
base.Cancel(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs
Normal file
62
OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class MoveWithinRange : MoveAdjacentTo
|
||||
{
|
||||
readonly WRange maxRange;
|
||||
readonly WRange minRange;
|
||||
|
||||
public MoveWithinRange(Actor self, Target target, WRange minRange, WRange maxRange)
|
||||
: base(self, target)
|
||||
{
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
}
|
||||
|
||||
protected override bool ShouldStop(Actor self, CPos oldTargetPosition)
|
||||
{
|
||||
// We are now in range. Don't move any further!
|
||||
// HACK: This works around the pathfinder not returning the shortest path
|
||||
var cp = self.CenterPosition;
|
||||
return Target.IsInRange(cp, maxRange) && !Target.IsInRange(cp, minRange);
|
||||
}
|
||||
|
||||
protected override bool ShouldRepath(Actor self, CPos oldTargetPosition)
|
||||
{
|
||||
var cp = self.CenterPosition;
|
||||
return targetPosition != oldTargetPosition && (!Target.IsInRange(cp, maxRange) || Target.IsInRange(cp, minRange));
|
||||
}
|
||||
|
||||
protected override IEnumerable<CPos> CandidateMovementCells(Actor self)
|
||||
{
|
||||
var map = self.World.Map;
|
||||
var maxCells = (maxRange.Range + 1023) / 1024;
|
||||
var minCells = minRange.Range / 1024;
|
||||
|
||||
var outerSq = maxRange.Range * maxRange.Range;
|
||||
var innerSq = minRange.Range * minRange.Range;
|
||||
var center = Target.CenterPosition;
|
||||
|
||||
return map.FindTilesInAnnulus(targetPosition, minCells + 1, maxCells).Where(c =>
|
||||
{
|
||||
var dxSq = (map.CenterOfCell(c) - center).HorizontalLengthSquared;
|
||||
return dxSq >= innerSq && dxSq <= outerSq;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
60
OpenRA.Mods.Common/Activities/Rearm.cs
Normal file
60
OpenRA.Mods.Common/Activities/Rearm.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Rearm : Activity
|
||||
{
|
||||
readonly LimitedAmmo limitedAmmo;
|
||||
int ticksPerPip = 25 * 2;
|
||||
int remainingTicks = 25 * 2;
|
||||
string sound = null;
|
||||
|
||||
public Rearm(Actor self, string sound = null)
|
||||
{
|
||||
limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
|
||||
if (limitedAmmo != null)
|
||||
ticksPerPip = limitedAmmo.ReloadTimePerAmmo();
|
||||
remainingTicks = ticksPerPip;
|
||||
this.sound = sound;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || limitedAmmo == null)
|
||||
return NextActivity;
|
||||
|
||||
if (--remainingTicks == 0)
|
||||
{
|
||||
var hostBuilding = self.World.ActorMap.GetUnitsAt(self.Location)
|
||||
.FirstOrDefault(a => a.HasTrait<RenderBuilding>());
|
||||
|
||||
if (hostBuilding == null || !hostBuilding.IsInWorld)
|
||||
return NextActivity;
|
||||
|
||||
if (!limitedAmmo.GiveAmmo())
|
||||
return NextActivity;
|
||||
|
||||
hostBuilding.Trait<RenderBuilding>().PlayCustomAnim(hostBuilding, "active");
|
||||
if (sound != null)
|
||||
Sound.Play(sound, self.CenterPosition);
|
||||
|
||||
remainingTicks = limitedAmmo.ReloadTimePerAmmo();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
OpenRA.Mods.Common/Activities/Repair.cs
Normal file
63
OpenRA.Mods.Common/Activities/Repair.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class Repair : Activity
|
||||
{
|
||||
int remainingTicks;
|
||||
Actor host;
|
||||
Health health;
|
||||
|
||||
public Repair(Actor host) { this.host = host; }
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled) return NextActivity;
|
||||
if (host == null || !host.IsInWorld) return NextActivity;
|
||||
|
||||
health = self.TraitOrDefault<Health>();
|
||||
if (health == null) return NextActivity;
|
||||
|
||||
if (health.DamageState == DamageState.Undamaged)
|
||||
return NextActivity;
|
||||
|
||||
if (remainingTicks == 0)
|
||||
{
|
||||
var repairsUnits = host.Info.Traits.Get<RepairsUnitsInfo>();
|
||||
var unitCost = self.Info.Traits.Get<ValuedInfo>().Cost;
|
||||
var hpToRepair = repairsUnits.HpPerStep;
|
||||
var cost = Math.Max(1, (hpToRepair * unitCost * repairsUnits.ValuePercentage) / (health.MaxHP * 100));
|
||||
|
||||
if (!self.Owner.PlayerActor.Trait<PlayerResources>().TakeCash(cost))
|
||||
{
|
||||
remainingTicks = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
self.InflictDamage(self, -hpToRepair, null);
|
||||
|
||||
foreach (var depot in host.TraitsImplementing<INotifyRepair>())
|
||||
depot.Repairing(self, host);
|
||||
|
||||
remainingTicks = repairsUnits.Interval;
|
||||
}
|
||||
else
|
||||
--remainingTicks;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
OpenRA.Mods.Common/Activities/UnloadCargo.cs
Normal file
103
OpenRA.Mods.Common/Activities/UnloadCargo.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Activities
|
||||
{
|
||||
public class UnloadCargo : Activity
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly Cargo cargo;
|
||||
readonly Cloak cloak;
|
||||
readonly bool unloadAll;
|
||||
|
||||
public UnloadCargo(Actor self, bool unloadAll)
|
||||
{
|
||||
this.self = self;
|
||||
cargo = self.Trait<Cargo>();
|
||||
cloak = self.TraitOrDefault<Cloak>();
|
||||
this.unloadAll = unloadAll;
|
||||
}
|
||||
|
||||
public Pair<CPos, SubCell>? ChooseExitSubCell(Actor passenger)
|
||||
{
|
||||
var pos = passenger.Trait<IPositionable>();
|
||||
|
||||
return cargo.CurrentAdjacentCells
|
||||
.Shuffle(self.World.SharedRandom)
|
||||
.Select(c => Pair.New(c, pos.GetAvailableSubCell(c)))
|
||||
.Cast<Pair<CPos, SubCell>?>()
|
||||
.FirstOrDefault(s => s.Value.Second != SubCell.Invalid);
|
||||
}
|
||||
|
||||
IEnumerable<CPos> BlockedExitCells(Actor passenger)
|
||||
{
|
||||
var pos = passenger.Trait<IPositionable>();
|
||||
|
||||
// Find the cells that are blocked by transient actors
|
||||
return cargo.CurrentAdjacentCells
|
||||
.Where(c => pos.CanEnterCell(c, null, true) != pos.CanEnterCell(c, null, false));
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
cargo.Unloading = false;
|
||||
if (IsCanceled || cargo.IsEmpty(self))
|
||||
return NextActivity;
|
||||
|
||||
if (cloak != null && cloak.Info.UncloakOnUnload)
|
||||
cloak.Uncloak();
|
||||
|
||||
var actor = cargo.Peek(self);
|
||||
var spawn = self.CenterPosition;
|
||||
|
||||
var exitSubCell = ChooseExitSubCell(actor);
|
||||
if (exitSubCell == null)
|
||||
{
|
||||
foreach (var blocker in BlockedExitCells(actor).SelectMany(p => self.World.ActorMap.GetUnitsAt(p)))
|
||||
{
|
||||
foreach (var nbm in blocker.TraitsImplementing<INotifyBlockingMove>())
|
||||
nbm.OnNotifyBlockingMove(blocker, self);
|
||||
}
|
||||
|
||||
return Util.SequenceActivities(new Wait(10), this);
|
||||
}
|
||||
|
||||
cargo.Unload(self);
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
if (actor.Destroyed)
|
||||
return;
|
||||
|
||||
var move = actor.Trait<IMove>();
|
||||
var pos = actor.Trait<IPositionable>();
|
||||
|
||||
actor.CancelActivity();
|
||||
pos.SetVisualPosition(actor, spawn);
|
||||
actor.QueueActivity(move.MoveIntoWorld(actor, exitSubCell.Value.First, exitSubCell.Value.Second));
|
||||
actor.SetTargetLine(Target.FromCell(w, exitSubCell.Value.First, exitSubCell.Value.Second), Color.Green, false);
|
||||
w.Add(actor);
|
||||
});
|
||||
|
||||
if (!unloadAll || cargo.IsEmpty(self))
|
||||
return NextActivity;
|
||||
|
||||
cargo.Unloading = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,38 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Activities\Air\FallToEarth.cs" />
|
||||
<Compile Include="Activities\Air\Fly.cs" />
|
||||
<Compile Include="Activities\Air\FlyAttack.cs" />
|
||||
<Compile Include="Activities\Air\FlyCircle.cs" />
|
||||
<Compile Include="Activities\Air\FlyFollow.cs" />
|
||||
<Compile Include="Activities\Air\FlyTimed.cs" />
|
||||
<Compile Include="Activities\Air\HeliAttack.cs" />
|
||||
<Compile Include="Activities\Air\HeliFly.cs" />
|
||||
<Compile Include="Activities\Air\HeliFlyCircle.cs" />
|
||||
<Compile Include="Activities\Air\HeliLand.cs" />
|
||||
<Compile Include="Activities\Air\HeliReturn.cs" />
|
||||
<Compile Include="Activities\Air\Land.cs" />
|
||||
<Compile Include="Activities\Air\ResupplyAircraft.cs" />
|
||||
<Compile Include="Activities\Air\ReturnToBase.cs" />
|
||||
<Compile Include="Activities\Air\TakeOff.cs" />
|
||||
<Compile Include="Activities\Attack.cs" />
|
||||
<Compile Include="Activities\Enter.cs" />
|
||||
<Compile Include="Activities\EnterTransport.cs" />
|
||||
<Compile Include="Activities\Heal.cs" />
|
||||
<Compile Include="Activities\Move\AttackMoveActivity.cs" />
|
||||
<Compile Include="Activities\Move\Drag.cs" />
|
||||
<Compile Include="Activities\Move\Follow.cs" />
|
||||
<Compile Include="Activities\Move\Move.cs" />
|
||||
<Compile Include="Activities\Move\MoveAdjacentTo.cs" />
|
||||
<Compile Include="Activities\Move\MoveWithinRange.cs" />
|
||||
<Compile Include="Activities\Rearm.cs" />
|
||||
<Compile Include="Activities\RemoveSelf.cs" />
|
||||
<Compile Include="Activities\Repair.cs" />
|
||||
<Compile Include="Activities\RepairBuilding.cs" />
|
||||
<Compile Include="Activities\SimpleTeleport.cs" />
|
||||
<Compile Include="Activities\Turn.cs" />
|
||||
<Compile Include="Activities\UnloadCargo.cs" />
|
||||
<Compile Include="Activities\Wait.cs" />
|
||||
<Compile Include="ActorExts.cs" />
|
||||
<Compile Include="Effects\Beacon.cs" />
|
||||
@@ -121,7 +147,28 @@
|
||||
<Compile Include="Scripting\Properties\ResourceProperties.cs" />
|
||||
<Compile Include="Scripting\Properties\UpgradeProperties.cs" />
|
||||
<Compile Include="TraitsInterfaces.cs" />
|
||||
<Compile Include="Traits\Air\Aircraft.cs" />
|
||||
<Compile Include="Traits\Air\AttackBomber.cs" />
|
||||
<Compile Include="Traits\Air\AttackHeli.cs" />
|
||||
<Compile Include="Traits\Air\AttackPlane.cs" />
|
||||
<Compile Include="Traits\Air\FlyAwayOnIdle.cs" />
|
||||
<Compile Include="Traits\Air\FallsToEarth.cs" />
|
||||
<Compile Include="Traits\Air\Helicopter.cs" />
|
||||
<Compile Include="Traits\Air\Plane.cs" />
|
||||
<Compile Include="Traits\Air\ReturnOnIdle.cs" />
|
||||
<Compile Include="Traits\AppearsOnRadar.cs" />
|
||||
<Compile Include="Traits\Armament.cs" />
|
||||
<Compile Include="Traits\AttackMove.cs" />
|
||||
<Compile Include="Traits\Attack\AttackBase.cs" />
|
||||
<Compile Include="Traits\Attack\AttackCharge.cs" />
|
||||
<Compile Include="Traits\Attack\AttackFollow.cs" />
|
||||
<Compile Include="Traits\Attack\AttackFrontal.cs" />
|
||||
<Compile Include="Traits\Attack\AttackMedic.cs" />
|
||||
<Compile Include="Traits\Attack\AttackOmni.cs" />
|
||||
<Compile Include="Traits\Attack\AttackTurreted.cs" />
|
||||
<Compile Include="Traits\Attack\AttackWander.cs" />
|
||||
<Compile Include="Traits\AutoHeal.cs" />
|
||||
<Compile Include="Traits\AutoTarget.cs" />
|
||||
<Compile Include="Traits\BlocksBullets.cs" />
|
||||
<Compile Include="Traits\Buildable.cs" />
|
||||
<Compile Include="Traits\Buildings\BaseBuilding.cs" />
|
||||
@@ -131,13 +178,17 @@
|
||||
<Compile Include="Traits\Buildings\BuildingInfluence.cs" />
|
||||
<Compile Include="Traits\Buildings\BuildingUtils.cs" />
|
||||
<Compile Include="Traits\Buildings\DeadBuildingState.cs" />
|
||||
<Compile Include="Traits\Buildings\Exit.cs" />
|
||||
<Compile Include="Traits\Buildings\FreeActor.cs" />
|
||||
<Compile Include="Traits\Buildings\LineBuild.cs" />
|
||||
<Compile Include="Traits\Buildings\LineBuildNode.cs" />
|
||||
<Compile Include="Traits\Buildings\RallyPoint.cs" />
|
||||
<Compile Include="Traits\Buildings\RepairsUnits.cs" />
|
||||
<Compile Include="Traits\Buildings\Reservable.cs" />
|
||||
<Compile Include="Traits\Buildings\FootprintUtils.cs" />
|
||||
<Compile Include="Traits\Burns.cs" />
|
||||
<Compile Include="Traits\Cargo.cs" />
|
||||
<Compile Include="Traits\Cloak.cs" />
|
||||
<Compile Include="Traits\IgnoresDisguise.cs" />
|
||||
<Compile Include="Traits\DetectCloaked.cs" />
|
||||
<Compile Include="Traits\EngineerRepair.cs" />
|
||||
@@ -153,11 +204,14 @@
|
||||
<Compile Include="Traits\Immobile.cs" />
|
||||
<Compile Include="Traits\JamsMissiles.cs" />
|
||||
<Compile Include="Traits\KillsSelf.cs" />
|
||||
<Compile Include="Traits\LimitedAmmo.cs" />
|
||||
<Compile Include="Traits\Mobile.cs" />
|
||||
<Compile Include="Traits\Modifiers\DisabledOverlay.cs" />
|
||||
<Compile Include="Traits\Modifiers\FrozenUnderFog.cs" />
|
||||
<Compile Include="Traits\Modifiers\HiddenUnderFog.cs" />
|
||||
<Compile Include="Traits\Modifiers\UpgradeOverlay.cs" />
|
||||
<Compile Include="Traits\MustBeDestroyed.cs" />
|
||||
<Compile Include="Traits\Passenger.cs" />
|
||||
<Compile Include="Traits\PaletteEffects\CloakPaletteEffect.cs" />
|
||||
<Compile Include="Traits\PaletteEffects\LightPaletteRotator.cs" />
|
||||
<Compile Include="Traits\PaletteEffects\MenuPaletteEffect.cs" />
|
||||
@@ -181,7 +235,11 @@
|
||||
<Compile Include="Traits\Power\ScalePowerWithHealth.cs" />
|
||||
<Compile Include="Traits\ProvidesRadar.cs" />
|
||||
<Compile Include="Traits\RadarColorFromTerrain.cs" />
|
||||
<Compile Include="Traits\Reloads.cs" />
|
||||
<Compile Include="Traits\Render\Hovers.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuilding.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuildingCharge.cs" />
|
||||
<Compile Include="Traits\Render\RenderBuildingTurreted.cs" />
|
||||
<Compile Include="Traits\Render\RenderEditorOnly.cs" />
|
||||
<Compile Include="Traits\Render\RenderFlare.cs" />
|
||||
<Compile Include="Traits\Render\RenderNameTag.cs" />
|
||||
@@ -189,15 +247,18 @@
|
||||
<Compile Include="Traits\Render\RenderSprites.cs" />
|
||||
<Compile Include="Traits\Render\RenderUnit.cs" />
|
||||
<Compile Include="Traits\Render\TimedUpgradeBar.cs" />
|
||||
<Compile Include="Traits\Render\WithBarrel.cs" />
|
||||
<Compile Include="Traits\Render\WithBuildingPlacedAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithMakeAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithCrateBody.cs" />
|
||||
<Compile Include="Traits\Render\WithDeathAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithHarvestAnimation.cs" />
|
||||
<Compile Include="Traits\Render\WithMuzzleFlash.cs" />
|
||||
<Compile Include="Traits\Render\WithResources.cs" />
|
||||
<Compile Include="Traits\Render\WithRotor.cs" />
|
||||
<Compile Include="Traits\Render\WithShadow.cs" />
|
||||
<Compile Include="Traits\Render\WithSmoke.cs" />
|
||||
<Compile Include="Traits\Render\WithTurret.cs" />
|
||||
<Compile Include="Traits\ShakeOnDeath.cs" />
|
||||
<Compile Include="Traits\SmokeTrailWhenDamaged.cs" />
|
||||
<Compile Include="Traits\Sound\ActorLostNotification.cs" />
|
||||
@@ -206,16 +267,22 @@
|
||||
<Compile Include="Traits\Sound\DeathSounds.cs" />
|
||||
<Compile Include="Traits\Sound\SoundOnDamageTransition.cs" />
|
||||
<Compile Include="Traits\Tooltip.cs" />
|
||||
<Compile Include="Traits\Turreted.cs" />
|
||||
<Compile Include="Traits\Upgrades\UpgradableTrait.cs" />
|
||||
<Compile Include="Traits\Upgrades\UpgradeManager.cs" />
|
||||
<Compile Include="Traits\Valued.cs" />
|
||||
<Compile Include="Traits\Wanders.cs" />
|
||||
<Compile Include="Traits\World\CreateMPPlayers.cs" />
|
||||
<Compile Include="Traits\World\DomainIndex.cs" />
|
||||
<Compile Include="Traits\World\LoadWidgetAtGameStart.cs" />
|
||||
<Compile Include="Traits\World\MPStartLocations.cs" />
|
||||
<Compile Include="Traits\World\MPStartUnits.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromCurrentTileset.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromFile.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromRGBA.cs" />
|
||||
<Compile Include="Traits\World\PathFinder.cs" />
|
||||
<Compile Include="Traits\World\PathfinderDebugOverlay.cs" />
|
||||
<Compile Include="Traits\World\PathSearch.cs" />
|
||||
<Compile Include="Traits\World\PlayerPaletteFromCurrentTileset.cs" />
|
||||
<Compile Include="Traits\World\PlayMusicOnMapLoad.cs" />
|
||||
<Compile Include="Traits\World\RadarPings.cs" />
|
||||
|
||||
331
OpenRA.Mods.Common/Traits/Air/Aircraft.cs
Normal file
331
OpenRA.Mods.Common/Traits/Air/Aircraft.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class AircraftInfo : ITraitInfo, IFacingInfo, IOccupySpaceInfo, ICruiseAltitudeInfo, UsesInit<LocationInit>, UsesInit<FacingInit>
|
||||
{
|
||||
public readonly WRange CruiseAltitude = new WRange(1280);
|
||||
public readonly WRange IdealSeparation = new WRange(1706);
|
||||
[Desc("Whether the aircraft can be repulsed.")]
|
||||
public readonly bool Repulsable = true;
|
||||
[Desc("The speed at which the aircraft is repulsed from other aircraft. Specify -1 for normal movement speed.")]
|
||||
public readonly int RepulsionSpeed = -1;
|
||||
|
||||
[ActorReference]
|
||||
public readonly string[] RepairBuildings = { "fix" };
|
||||
[ActorReference]
|
||||
public readonly string[] RearmBuildings = { "hpad", "afld" };
|
||||
public readonly int InitialFacing = 128;
|
||||
public readonly int ROT = 255;
|
||||
public readonly int Speed = 1;
|
||||
public readonly string[] LandableTerrainTypes = { };
|
||||
|
||||
[Desc("Can the actor be ordered to move in to shroud?")]
|
||||
public readonly bool MoveIntoShroud = true;
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Aircraft(init, this); }
|
||||
public int GetInitialFacing() { return InitialFacing; }
|
||||
public WRange GetCruiseAltitude() { return CruiseAltitude; }
|
||||
}
|
||||
|
||||
public class Aircraft : IFacing, IPositionable, ISync, INotifyKilled, IIssueOrder, IOrderVoice, INotifyAddedToWorld, INotifyRemovedFromWorld
|
||||
{
|
||||
static readonly Pair<CPos, SubCell>[] NoCells = { };
|
||||
|
||||
readonly AircraftInfo info;
|
||||
readonly Actor self;
|
||||
|
||||
[Sync] public int Facing { get; set; }
|
||||
[Sync] public WPos CenterPosition { get; private set; }
|
||||
public CPos TopLeft { get { return self.World.Map.CellContaining(CenterPosition); } }
|
||||
public IDisposable Reservation;
|
||||
public int ROT { get { return info.ROT; } }
|
||||
|
||||
public Aircraft(ActorInitializer init, AircraftInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
this.self = init.self;
|
||||
|
||||
if (init.Contains<LocationInit>())
|
||||
SetPosition(self, init.Get<LocationInit, CPos>());
|
||||
|
||||
if (init.Contains<CenterPositionInit>())
|
||||
SetPosition(self, init.Get<CenterPositionInit, WPos>());
|
||||
|
||||
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
|
||||
}
|
||||
|
||||
public void Repulse()
|
||||
{
|
||||
var repulsionForce = GetRepulsionForce();
|
||||
|
||||
var repulsionFacing = Util.GetFacing(repulsionForce, -1);
|
||||
if (repulsionFacing == -1)
|
||||
return;
|
||||
|
||||
var speed = info.RepulsionSpeed != -1 ? info.RepulsionSpeed : MovementSpeed;
|
||||
SetPosition(self, CenterPosition + FlyStep(speed, repulsionFacing));
|
||||
}
|
||||
|
||||
public virtual WVec GetRepulsionForce()
|
||||
{
|
||||
if (!info.Repulsable)
|
||||
return WVec.Zero;
|
||||
|
||||
// Repulsion only applies when we're flying!
|
||||
var altitude = CenterPosition.Z;
|
||||
if (altitude != info.CruiseAltitude.Range)
|
||||
return WVec.Zero;
|
||||
|
||||
return self.World.FindActorsInCircle(self.CenterPosition, info.IdealSeparation)
|
||||
.Where(a => !a.IsDead && a.HasTrait<Aircraft>())
|
||||
.Select(GetRepulsionForce)
|
||||
.Aggregate(WVec.Zero, (a, b) => a + b);
|
||||
}
|
||||
|
||||
public WVec GetRepulsionForce(Actor other)
|
||||
{
|
||||
if (self == other || other.CenterPosition.Z < self.CenterPosition.Z)
|
||||
return WVec.Zero;
|
||||
|
||||
var d = self.CenterPosition - other.CenterPosition;
|
||||
var distSq = d.HorizontalLengthSquared;
|
||||
if (distSq > info.IdealSeparation.Range * info.IdealSeparation.Range)
|
||||
return WVec.Zero;
|
||||
|
||||
if (distSq < 1)
|
||||
{
|
||||
var yaw = self.World.SharedRandom.Next(0, 1023);
|
||||
var rot = new WRot(WAngle.Zero, WAngle.Zero, new WAngle(yaw));
|
||||
return new WVec(1024, 0, 0).Rotate(rot);
|
||||
}
|
||||
|
||||
return (d * 1024 * 8) / (int)distSq;
|
||||
}
|
||||
|
||||
public Actor GetActorBelow()
|
||||
{
|
||||
if (self.CenterPosition.Z != 0)
|
||||
return null; // not on the ground.
|
||||
|
||||
return self.World.ActorMap.GetUnitsAt(self.Location)
|
||||
.FirstOrDefault(a => a.HasTrait<Reservable>());
|
||||
}
|
||||
|
||||
protected void ReserveSpawnBuilding()
|
||||
{
|
||||
/* HACK: not spawning in the air, so try to assoc. with our afld. */
|
||||
var afld = GetActorBelow();
|
||||
if (afld == null)
|
||||
return;
|
||||
|
||||
var res = afld.TraitOrDefault<Reservable>();
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
UnReserve();
|
||||
Reservation = res.Reserve(afld, self, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnReserve()
|
||||
{
|
||||
if (Reservation != null)
|
||||
{
|
||||
Reservation.Dispose();
|
||||
Reservation = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Killed(Actor self, AttackInfo e)
|
||||
{
|
||||
UnReserve();
|
||||
}
|
||||
|
||||
public void SetPosition(Actor self, WPos pos)
|
||||
{
|
||||
CenterPosition = pos;
|
||||
|
||||
if (self.IsInWorld)
|
||||
{
|
||||
self.World.ScreenMap.Update(self);
|
||||
self.World.ActorMap.UpdatePosition(self, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Changes position, but not altitude
|
||||
public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
SetPosition(self, self.World.Map.CenterOfCell(cell) + new WVec(0, 0, CenterPosition.Z));
|
||||
}
|
||||
|
||||
public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, pos); }
|
||||
|
||||
public void AddedToWorld(Actor self)
|
||||
{
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
self.World.ActorMap.AddPosition(self, this);
|
||||
self.World.ScreenMap.Add(self);
|
||||
}
|
||||
|
||||
public void RemovedFromWorld(Actor self)
|
||||
{
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
self.World.ActorMap.RemovePosition(self, this);
|
||||
self.World.ScreenMap.Remove(self);
|
||||
}
|
||||
|
||||
public bool AircraftCanEnter(Actor a)
|
||||
{
|
||||
if (self.AppearsHostileTo(a))
|
||||
return false;
|
||||
|
||||
return info.RearmBuildings.Contains(a.Info.Name)
|
||||
|| info.RepairBuildings.Contains(a.Info.Name);
|
||||
}
|
||||
|
||||
public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } // TODO: Handle landing
|
||||
public SubCell GetValidSubCell(SubCell preferred) { return SubCell.Invalid; }
|
||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) { return SubCell.Invalid; } // Does not use any subcell
|
||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; }
|
||||
|
||||
public bool CanEnterTargetNow(Actor self, Target target)
|
||||
{
|
||||
if (target.Positions.Any(p => self.World.ActorMap.GetUnitsAt(self.World.Map.CellContaining(p)).Any(a => a != self && a != target.Actor)))
|
||||
return false;
|
||||
var res = target.Actor.TraitOrDefault<Reservable>();
|
||||
if (res == null)
|
||||
return true;
|
||||
UnReserve();
|
||||
Reservation = res.Reserve(target.Actor, self, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int MovementSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
var modifiers = self.TraitsImplementing<ISpeedModifier>()
|
||||
.Select(m => m.GetSpeedModifier());
|
||||
return Util.ApplyPercentageModifiers(info.Speed, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Pair<CPos, SubCell>> OccupiedCells() { return NoCells; }
|
||||
|
||||
public WVec FlyStep(int facing)
|
||||
{
|
||||
return FlyStep(MovementSpeed, facing);
|
||||
}
|
||||
|
||||
public WVec FlyStep(int speed, int facing)
|
||||
{
|
||||
var dir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing));
|
||||
return speed * dir / 1024;
|
||||
}
|
||||
|
||||
public bool CanLand(CPos cell)
|
||||
{
|
||||
if (!self.World.Map.Contains(cell))
|
||||
return false;
|
||||
|
||||
if (self.World.ActorMap.AnyUnitsAt(cell))
|
||||
return false;
|
||||
|
||||
var type = self.World.Map.GetTerrainInfo(cell).Type;
|
||||
return info.LandableTerrainTypes.Contains(type);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Activity> GetResupplyActivities(Actor a)
|
||||
{
|
||||
var name = a.Info.Name;
|
||||
if (info.RearmBuildings.Contains(name))
|
||||
yield return new Rearm(self);
|
||||
if (info.RepairBuildings.Contains(name))
|
||||
yield return new Repair(a);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new EnterAlliedActorTargeter<Building>("Enter", 5,
|
||||
target => AircraftCanEnter(target), target => !Reservable.IsReserved(target));
|
||||
|
||||
yield return new AircraftMoveOrderTargeter(info);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "Enter")
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
|
||||
if (order.OrderID == "Move")
|
||||
return new Order(order.OrderID, self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "Move":
|
||||
case "Enter":
|
||||
case "ReturnToBase":
|
||||
case "Stop":
|
||||
return "Move";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AircraftMoveOrderTargeter : IOrderTargeter
|
||||
{
|
||||
public string OrderID { get { return "Move"; } }
|
||||
public int OrderPriority { get { return 4; } }
|
||||
|
||||
readonly AircraftInfo info;
|
||||
|
||||
public AircraftMoveOrderTargeter(AircraftInfo info) { this.info = info; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
if (target.Type != TargetType.Terrain)
|
||||
return false;
|
||||
|
||||
var location = self.World.Map.CellContaining(target.CenterPosition);
|
||||
var explored = self.Owner.Shroud.IsExplored(location);
|
||||
cursor = self.World.Map.Contains(location) ?
|
||||
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? "move") :
|
||||
"move-blocked";
|
||||
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
if (!explored && !info.MoveIntoShroud)
|
||||
cursor = "move-blocked";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsQueued { get; protected set; }
|
||||
}
|
||||
}
|
||||
107
OpenRA.Mods.Common/Traits/Air/AttackBomber.cs
Normal file
107
OpenRA.Mods.Common/Traits/Air/AttackBomber.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class AttackBomberInfo : AttackBaseInfo
|
||||
{
|
||||
[Desc("Armament name")]
|
||||
public readonly string Bombs = "primary";
|
||||
|
||||
[Desc("Armament name")]
|
||||
public readonly string Guns = "secondary";
|
||||
public readonly int FacingTolerance = 2;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackBomber(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackBomber : AttackBase, ITick, ISync, INotifyRemovedFromWorld
|
||||
{
|
||||
AttackBomberInfo info;
|
||||
[Sync] Target target;
|
||||
[Sync] bool inAttackRange;
|
||||
[Sync] bool facingTarget = true;
|
||||
|
||||
public event Action<Actor> OnRemovedFromWorld = self => { };
|
||||
public event Action<Actor> OnEnteredAttackRange = self => { };
|
||||
public event Action<Actor> OnExitedAttackRange = self => { };
|
||||
|
||||
public AttackBomber(Actor self, AttackBomberInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
var cp = self.CenterPosition;
|
||||
var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z));
|
||||
var wasInAttackRange = inAttackRange;
|
||||
var wasFacingTarget = facingTarget;
|
||||
|
||||
inAttackRange = false;
|
||||
|
||||
var f = facing.Value.Facing;
|
||||
var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, f);
|
||||
facingTarget = Math.Abs(facingToTarget - f) % 256 <= info.FacingTolerance;
|
||||
|
||||
// Bombs drop anywhere in range
|
||||
foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs))
|
||||
{
|
||||
if (!target.IsInRange(self.CenterPosition, a.Weapon.Range))
|
||||
continue;
|
||||
|
||||
inAttackRange = true;
|
||||
a.CheckFire(self, facing.Value, bombTarget);
|
||||
}
|
||||
|
||||
// Guns only fire when approaching the target
|
||||
if (facingTarget)
|
||||
{
|
||||
foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns))
|
||||
{
|
||||
if (!target.IsInRange(self.CenterPosition, a.Weapon.Range))
|
||||
continue;
|
||||
|
||||
var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Range / 2, cp.Z).Rotate(WRot.FromFacing(f)));
|
||||
inAttackRange = true;
|
||||
a.CheckFire(self, facing.Value, t);
|
||||
}
|
||||
}
|
||||
|
||||
// Actors without armaments may want to trigger an action when it passes the target
|
||||
if (!Armaments.Any())
|
||||
inAttackRange = !wasInAttackRange && !facingTarget && wasFacingTarget;
|
||||
|
||||
if (inAttackRange && !wasInAttackRange)
|
||||
OnEnteredAttackRange(self);
|
||||
|
||||
if (!inAttackRange && wasInAttackRange)
|
||||
OnExitedAttackRange(self);
|
||||
}
|
||||
|
||||
public void SetTarget(World w, WPos pos) { target = Target.FromPos(pos); }
|
||||
|
||||
public void RemovedFromWorld(Actor self)
|
||||
{
|
||||
OnRemovedFromWorld(self);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
throw new NotImplementedException("AttackBomber requires a scripted target");
|
||||
}
|
||||
}
|
||||
}
|
||||
32
OpenRA.Mods.Common/Traits/Air/AttackHeli.cs
Normal file
32
OpenRA.Mods.Common/Traits/Air/AttackHeli.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class AttackHeliInfo : AttackFrontalInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackHeli(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackHeli : AttackFrontal
|
||||
{
|
||||
public AttackHeli(Actor self, AttackHeliInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
return new HeliAttack(newTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
OpenRA.Mods.Common/Traits/Air/AttackPlane.cs
Normal file
38
OpenRA.Mods.Common/Traits/Air/AttackPlane.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class AttackPlaneInfo : AttackFrontalInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackPlane(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackPlane : AttackFrontal
|
||||
{
|
||||
public AttackPlane(Actor self, AttackPlaneInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
return new FlyAttack(newTarget);
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
// dont fire while landed or when outside the map
|
||||
return base.CanAttack(self, target) && self.CenterPosition.Z > 0 && self.World.Map.Contains(self.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
OpenRA.Mods.Common/Traits/Air/FallsToEarth.cs
Normal file
38
OpenRA.Mods.Common/Traits/Air/FallsToEarth.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Causes aircraft husks that are spawned in the air to crash to the ground.")]
|
||||
public class FallsToEarthInfo : ITraitInfo
|
||||
{
|
||||
[WeaponReference]
|
||||
public readonly string Explosion = "UnitExplode";
|
||||
|
||||
public readonly bool Spins = true;
|
||||
public readonly bool Moves = false;
|
||||
public readonly WRange Velocity = new WRange(43);
|
||||
|
||||
public object Create(ActorInitializer init) { return new FallsToEarth(init.self, this); }
|
||||
}
|
||||
|
||||
public class FallsToEarth
|
||||
{
|
||||
public FallsToEarth(Actor self, FallsToEarthInfo info)
|
||||
{
|
||||
self.QueueActivity(false, new FallToEarth(self, info));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
OpenRA.Mods.Common/Traits/Air/FlyAwayOnIdle.cs
Normal file
27
OpenRA.Mods.Common/Traits/Air/FlyAwayOnIdle.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Leave the map when idle.")]
|
||||
class FlyAwayOnIdleInfo : TraitInfo<FlyAwayOnIdle> { }
|
||||
|
||||
class FlyAwayOnIdle : INotifyIdle
|
||||
{
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
self.QueueActivity(new FlyOffMap());
|
||||
self.QueueActivity(new RemoveSelf());
|
||||
}
|
||||
}
|
||||
}
|
||||
176
OpenRA.Mods.Common/Traits/Air/Helicopter.cs
Normal file
176
OpenRA.Mods.Common/Traits/Air/Helicopter.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class HelicopterInfo : AircraftInfo, IMoveInfo
|
||||
{
|
||||
[Desc("Allow the helicopter land after it has no more commands.")]
|
||||
public readonly bool LandWhenIdle = true;
|
||||
|
||||
[Desc("Allow the helicopter turn before landing.")]
|
||||
public readonly bool TurnToLand = false;
|
||||
public readonly WRange LandAltitude = WRange.Zero;
|
||||
|
||||
[Desc("How fast the helicopter ascends or descends.")]
|
||||
public readonly WRange AltitudeVelocity = new WRange(43);
|
||||
|
||||
public override object Create(ActorInitializer init) { return new Helicopter(init, this); }
|
||||
}
|
||||
|
||||
public class Helicopter : Aircraft, ITick, IResolveOrder, IMove
|
||||
{
|
||||
public HelicopterInfo Info;
|
||||
Actor self;
|
||||
bool firstTick = true;
|
||||
public bool IsMoving { get { return self.CenterPosition.Z > 0; } set { } }
|
||||
|
||||
public Helicopter(ActorInitializer init, HelicopterInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
self = init.self;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (Reservation != null)
|
||||
{
|
||||
Reservation.Dispose();
|
||||
Reservation = null;
|
||||
}
|
||||
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
var cell = self.World.Map.Clamp(order.TargetLocation);
|
||||
var explored = self.Owner.Shroud.IsExplored(cell);
|
||||
|
||||
if (!explored && !Info.MoveIntoShroud)
|
||||
return;
|
||||
|
||||
var target = Target.FromCell(self.World, cell);
|
||||
|
||||
self.SetTargetLine(target, Color.Green);
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new HeliFly(self, target));
|
||||
|
||||
if (Info.LandWhenIdle)
|
||||
{
|
||||
if (Info.TurnToLand)
|
||||
self.QueueActivity(new Turn(self, Info.InitialFacing));
|
||||
|
||||
self.QueueActivity(new HeliLand(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (order.OrderString == "Enter")
|
||||
{
|
||||
if (Reservable.IsReserved(order.TargetActor))
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new HeliReturn());
|
||||
}
|
||||
else
|
||||
{
|
||||
var res = order.TargetActor.TraitOrDefault<Reservable>();
|
||||
if (res != null)
|
||||
Reservation = res.Reserve(order.TargetActor, self, this);
|
||||
|
||||
var exit = order.TargetActor.Info.Traits.WithInterface<ExitInfo>().FirstOrDefault();
|
||||
var offset = (exit != null) ? exit.SpawnOffset : WVec.Zero;
|
||||
|
||||
self.SetTargetLine(Target.FromActor(order.TargetActor), Color.Green);
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new HeliFly(self, Target.FromPos(order.TargetActor.CenterPosition + offset)));
|
||||
self.QueueActivity(new Turn(self, Info.InitialFacing));
|
||||
self.QueueActivity(new HeliLand(false));
|
||||
self.QueueActivity(new ResupplyAircraft());
|
||||
self.QueueActivity(new TakeOff());
|
||||
}
|
||||
}
|
||||
|
||||
if (order.OrderString == "ReturnToBase")
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new HeliReturn());
|
||||
}
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
{
|
||||
self.CancelActivity();
|
||||
|
||||
if (Info.LandWhenIdle)
|
||||
{
|
||||
if (Info.TurnToLand)
|
||||
self.QueueActivity(new Turn(self, Info.InitialFacing));
|
||||
|
||||
self.QueueActivity(new HeliLand(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (firstTick)
|
||||
{
|
||||
firstTick = false;
|
||||
if (!self.HasTrait<FallsToEarth>()) // TODO: Aircraft husks don't properly unreserve.
|
||||
ReserveSpawnBuilding();
|
||||
|
||||
var host = GetActorBelow();
|
||||
if (host == null)
|
||||
return;
|
||||
|
||||
self.QueueActivity(new TakeOff());
|
||||
}
|
||||
|
||||
Repulse();
|
||||
}
|
||||
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return new HeliFly(self, Target.FromCell(self.World, cell)); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoredActor) { return new HeliFly(self, Target.FromCell(self.World, cell)); }
|
||||
public Activity MoveWithinRange(Target target, WRange range) { return new HeliFly(self, target, WRange.Zero, range); }
|
||||
public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return new HeliFly(self, target, minRange, maxRange); }
|
||||
public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new Follow(self, target, minRange, maxRange); }
|
||||
public CPos NearestMoveableCell(CPos cell) { return cell; }
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
return new HeliFly(self, Target.FromCell(self.World, cell, subCell));
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target) { return new HeliLand(false); }
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
{
|
||||
return Util.SequenceActivities(new HeliFly(self, target), new Turn(self, Info.InitialFacing));
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos)
|
||||
{
|
||||
// TODO: Ignore repulsion when moving
|
||||
return Util.SequenceActivities(new CallFunc(() => SetVisualPosition(self, fromPos)), new HeliFly(self, Target.FromPos(toPos)));
|
||||
}
|
||||
|
||||
public override IEnumerable<Activity> GetResupplyActivities(Actor a)
|
||||
{
|
||||
foreach (var b in base.GetResupplyActivities(a))
|
||||
yield return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
OpenRA.Mods.Common/Traits/Air/Plane.cs
Normal file
141
OpenRA.Mods.Common/Traits/Air/Plane.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class PlaneInfo : AircraftInfo, IMoveInfo
|
||||
{
|
||||
public readonly WAngle MaximumPitch = WAngle.FromDegrees(10);
|
||||
|
||||
public override object Create(ActorInitializer init) { return new Plane(init, this); }
|
||||
}
|
||||
|
||||
public class Plane : Aircraft, IResolveOrder, IMove, ITick, ISync
|
||||
{
|
||||
public readonly PlaneInfo Info;
|
||||
[Sync] public WPos RTBPathHash;
|
||||
Actor self;
|
||||
public bool IsMoving { get { return self.CenterPosition.Z > 0; } set { } }
|
||||
|
||||
public Plane(ActorInitializer init, PlaneInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
self = init.self;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
bool firstTick = true;
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (firstTick)
|
||||
{
|
||||
firstTick = false;
|
||||
if (!self.HasTrait<FallsToEarth>()) // TODO: Aircraft husks don't properly unreserve.
|
||||
ReserveSpawnBuilding();
|
||||
|
||||
var host = GetActorBelow();
|
||||
if (host == null)
|
||||
return;
|
||||
|
||||
self.QueueActivity(new TakeOff());
|
||||
}
|
||||
|
||||
Repulse();
|
||||
}
|
||||
|
||||
public override WVec GetRepulsionForce()
|
||||
{
|
||||
var repulsionForce = base.GetRepulsionForce();
|
||||
if (repulsionForce == WVec.Zero)
|
||||
return WVec.Zero;
|
||||
|
||||
var currentDir = FlyStep(Facing);
|
||||
var length = currentDir.HorizontalLength * repulsionForce.HorizontalLength;
|
||||
if (length == 0)
|
||||
return WVec.Zero;
|
||||
|
||||
var dot = WVec.Dot(currentDir, repulsionForce) / length;
|
||||
|
||||
// avoid stalling the plane
|
||||
return dot >= 0 ? repulsionForce : WVec.Zero;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
var cell = self.World.Map.Clamp(order.TargetLocation);
|
||||
var explored = self.Owner.Shroud.IsExplored(cell);
|
||||
|
||||
if (!explored && !Info.MoveIntoShroud)
|
||||
return;
|
||||
|
||||
UnReserve();
|
||||
|
||||
var target = Target.FromCell(self.World, cell);
|
||||
self.SetTargetLine(target, Color.Green);
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new Fly(self, target));
|
||||
self.QueueActivity(new FlyCircle());
|
||||
}
|
||||
else if (order.OrderString == "Enter")
|
||||
{
|
||||
if (Reservable.IsReserved(order.TargetActor)) return;
|
||||
|
||||
UnReserve();
|
||||
|
||||
self.SetTargetLine(Target.FromOrder(self.World, order), Color.Green);
|
||||
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new ReturnToBase(self, order.TargetActor));
|
||||
self.QueueActivity(new ResupplyAircraft());
|
||||
}
|
||||
else if (order.OrderString == "Stop")
|
||||
{
|
||||
UnReserve();
|
||||
self.CancelActivity();
|
||||
}
|
||||
else if (order.OrderString == "ReturnToBase")
|
||||
{
|
||||
var airfield = ReturnToBase.ChooseAirfield(self, true);
|
||||
if (airfield == null) return;
|
||||
|
||||
UnReserve();
|
||||
self.CancelActivity();
|
||||
self.SetTargetLine(Target.FromActor(airfield), Color.Green);
|
||||
self.QueueActivity(new ReturnToBase(self, airfield));
|
||||
self.QueueActivity(new ResupplyAircraft());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Game.Debug("Unreserve due to unhandled order: {0}".F(order.OrderString));
|
||||
UnReserve();
|
||||
}
|
||||
}
|
||||
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return Util.SequenceActivities(new Fly(self, Target.FromCell(self.World, cell)), new FlyCircle()); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoredActor) { return Util.SequenceActivities(new Fly(self, Target.FromCell(self.World, cell)), new FlyCircle()); }
|
||||
public Activity MoveWithinRange(Target target, WRange range) { return Util.SequenceActivities(new Fly(self, target, WRange.Zero, range), new FlyCircle()); }
|
||||
public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return Util.SequenceActivities(new Fly(self, target, minRange, maxRange), new FlyCircle()); }
|
||||
public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new FlyFollow(self, target, minRange, maxRange); }
|
||||
public CPos NearestMoveableCell(CPos cell) { return cell; }
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any) { return new Fly(self, Target.FromCell(self.World, cell)); }
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { return Util.SequenceActivities(new CallFunc(() => SetVisualPosition(self, fromPos)), new Fly(self, Target.FromPos(toPos))); }
|
||||
public Activity MoveToTarget(Actor self, Target target) { return new Fly(self, target, WRange.FromCells(3), WRange.FromCells(5)); }
|
||||
public Activity MoveIntoTarget(Actor self, Target target) { return new Land(target); }
|
||||
}
|
||||
}
|
||||
62
OpenRA.Mods.Common/Traits/Air/ReturnOnIdle.cs
Normal file
62
OpenRA.Mods.Common/Traits/Air/ReturnOnIdle.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Return to a player owned RearmBuildings. If none available, head back to base and circle over it.")]
|
||||
class ReturnOnIdleInfo : TraitInfo<ReturnOnIdle> { }
|
||||
|
||||
class ReturnOnIdle : INotifyIdle
|
||||
{
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
// We're on the ground, let's stay there.
|
||||
if (self.CenterPosition.Z == 0)
|
||||
return;
|
||||
|
||||
var airfield = ReturnToBase.ChooseAirfield(self, true);
|
||||
if (airfield != null)
|
||||
{
|
||||
self.QueueActivity(new ReturnToBase(self, airfield));
|
||||
self.QueueActivity(new ResupplyAircraft());
|
||||
}
|
||||
else
|
||||
{
|
||||
// nowhere to land, pick something friendly and circle over it.
|
||||
|
||||
// i'd prefer something we own
|
||||
var someBuilding = self.World.ActorsWithTrait<Building>()
|
||||
.Select(a => a.Actor)
|
||||
.FirstOrDefault(a => a.Owner == self.Owner);
|
||||
|
||||
// failing that, something unlikely to shoot at us
|
||||
if (someBuilding == null)
|
||||
someBuilding = self.World.ActorsWithTrait<Building>()
|
||||
.Select(a => a.Actor)
|
||||
.FirstOrDefault(a => self.Owner.Stances[a.Owner] == Stance.Ally);
|
||||
|
||||
if (someBuilding == null)
|
||||
{
|
||||
// ... going down the garden to eat worms ...
|
||||
self.QueueActivity(new FlyOffMap());
|
||||
self.QueueActivity(new RemoveSelf());
|
||||
return;
|
||||
}
|
||||
|
||||
self.QueueActivity(new Fly(self, Target.FromActor(someBuilding)));
|
||||
self.QueueActivity(new FlyCircle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
230
OpenRA.Mods.Common/Traits/Armament.cs
Normal file
230
OpenRA.Mods.Common/Traits/Armament.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class Barrel
|
||||
{
|
||||
public WVec Offset;
|
||||
public WAngle Yaw;
|
||||
}
|
||||
|
||||
[Desc("Allows you to attach weapons to the unit (use @IdentifierSuffix for > 1)")]
|
||||
public class ArmamentInfo : UpgradableTraitInfo, ITraitInfo, Requires<AttackBaseInfo>
|
||||
{
|
||||
public readonly string Name = "primary";
|
||||
|
||||
[WeaponReference]
|
||||
[Desc("Has to be defined here and in weapons.yaml.")]
|
||||
public readonly string Weapon = null;
|
||||
public readonly string Turret = "primary";
|
||||
[Desc("Time (in frames) until the weapon can fire again.")]
|
||||
public readonly int FireDelay = 0;
|
||||
|
||||
[Desc("Muzzle position relative to turret or body. (forward, right, up) triples")]
|
||||
public readonly WVec[] LocalOffset = { };
|
||||
[Desc("Muzzle yaw relative to turret or body.")]
|
||||
public readonly WAngle[] LocalYaw = { };
|
||||
[Desc("Move the turret backwards when firing.")]
|
||||
public readonly WRange Recoil = WRange.Zero;
|
||||
[Desc("Recoil recovery per-frame")]
|
||||
public readonly WRange RecoilRecovery = new WRange(9);
|
||||
|
||||
[Desc("Muzzle flash sequence to render")]
|
||||
public readonly string MuzzleSequence = null;
|
||||
|
||||
[Desc("Palette to render Muzzle flash sequence in")]
|
||||
public readonly string MuzzlePalette = "effect";
|
||||
|
||||
[Desc("Use multiple muzzle images if non-zero")]
|
||||
public readonly int MuzzleSplitFacings = 0;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Armament(init.self, this); }
|
||||
}
|
||||
|
||||
public class Armament : UpgradableTrait<ArmamentInfo>, ITick, IExplodeModifier
|
||||
{
|
||||
public readonly WeaponInfo Weapon;
|
||||
public readonly Barrel[] Barrels;
|
||||
|
||||
readonly Actor self;
|
||||
Lazy<Turreted> turret;
|
||||
Lazy<IBodyOrientation> coords;
|
||||
Lazy<LimitedAmmo> limitedAmmo;
|
||||
List<Pair<int, Action>> delayedActions = new List<Pair<int, Action>>();
|
||||
|
||||
public WRange Recoil;
|
||||
public int FireDelay { get; private set; }
|
||||
public int Burst { get; private set; }
|
||||
|
||||
public Armament(Actor self, ArmamentInfo info)
|
||||
: base(info)
|
||||
{
|
||||
this.self = self;
|
||||
|
||||
// We can't resolve these until runtime
|
||||
turret = Exts.Lazy(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret));
|
||||
coords = Exts.Lazy(() => self.Trait<IBodyOrientation>());
|
||||
limitedAmmo = Exts.Lazy(() => self.TraitOrDefault<LimitedAmmo>());
|
||||
|
||||
Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()];
|
||||
Burst = Weapon.Burst;
|
||||
|
||||
var barrels = new List<Barrel>();
|
||||
for (var i = 0; i < info.LocalOffset.Length; i++)
|
||||
{
|
||||
barrels.Add(new Barrel
|
||||
{
|
||||
Offset = info.LocalOffset[i],
|
||||
Yaw = info.LocalYaw.Length > i ? info.LocalYaw[i] : WAngle.Zero
|
||||
});
|
||||
}
|
||||
|
||||
if (barrels.Count == 0)
|
||||
barrels.Add(new Barrel { Offset = WVec.Zero, Yaw = WAngle.Zero });
|
||||
|
||||
Barrels = barrels.ToArray();
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (IsTraitDisabled)
|
||||
return;
|
||||
|
||||
if (FireDelay > 0)
|
||||
--FireDelay;
|
||||
|
||||
Recoil = new WRange(Math.Max(0, Recoil.Range - Info.RecoilRecovery.Range));
|
||||
|
||||
for (var i = 0; i < delayedActions.Count; i++)
|
||||
{
|
||||
var x = delayedActions[i];
|
||||
if (--x.First <= 0)
|
||||
x.Second();
|
||||
delayedActions[i] = x;
|
||||
}
|
||||
|
||||
delayedActions.RemoveAll(a => a.First <= 0);
|
||||
}
|
||||
|
||||
void ScheduleDelayedAction(int t, Action a)
|
||||
{
|
||||
if (t > 0)
|
||||
delayedActions.Add(Pair.New(t, a));
|
||||
else
|
||||
a();
|
||||
}
|
||||
|
||||
// Note: facing is only used by the legacy positioning code
|
||||
// The world coordinate model uses Actor.Orientation
|
||||
public Barrel CheckFire(Actor self, IFacing facing, Target target)
|
||||
{
|
||||
if (IsReloading)
|
||||
return null;
|
||||
|
||||
if (limitedAmmo.Value != null && !limitedAmmo.Value.HasAmmo())
|
||||
return null;
|
||||
|
||||
if (!target.IsInRange(self.CenterPosition, Weapon.Range))
|
||||
return null;
|
||||
|
||||
if (Weapon.MinRange != WRange.Zero && target.IsInRange(self.CenterPosition, Weapon.MinRange))
|
||||
return null;
|
||||
|
||||
if (!Weapon.IsValidAgainst(target, self.World, self))
|
||||
return null;
|
||||
|
||||
var barrel = Barrels[Burst % Barrels.Length];
|
||||
var muzzlePosition = self.CenterPosition + MuzzleOffset(self, barrel);
|
||||
var legacyFacing = MuzzleOrientation(self, barrel).Yaw.Angle / 4;
|
||||
|
||||
var args = new ProjectileArgs
|
||||
{
|
||||
Weapon = Weapon,
|
||||
Facing = legacyFacing,
|
||||
|
||||
DamageModifiers = self.TraitsImplementing<IFirepowerModifier>()
|
||||
.Select(a => a.GetFirepowerModifier()),
|
||||
|
||||
InaccuracyModifiers = self.TraitsImplementing<IInaccuracyModifier>()
|
||||
.Select(a => a.GetInaccuracyModifier()),
|
||||
|
||||
Source = muzzlePosition,
|
||||
SourceActor = self,
|
||||
PassiveTarget = target.CenterPosition,
|
||||
GuidedTarget = target
|
||||
};
|
||||
|
||||
ScheduleDelayedAction(Info.FireDelay, () =>
|
||||
{
|
||||
if (args.Weapon.Projectile != null)
|
||||
{
|
||||
var projectile = args.Weapon.Projectile.Create(args);
|
||||
if (projectile != null)
|
||||
self.World.Add(projectile);
|
||||
|
||||
if (args.Weapon.Report != null && args.Weapon.Report.Any())
|
||||
Sound.Play(args.Weapon.Report.Random(self.World.SharedRandom), self.CenterPosition);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var na in self.TraitsImplementing<INotifyAttack>())
|
||||
na.Attacking(self, target, this, barrel);
|
||||
|
||||
Recoil = Info.Recoil;
|
||||
|
||||
if (--Burst > 0)
|
||||
FireDelay = Weapon.BurstDelay;
|
||||
else
|
||||
{
|
||||
var modifiers = self.TraitsImplementing<IReloadModifier>()
|
||||
.Select(m => m.GetReloadModifier());
|
||||
FireDelay = Util.ApplyPercentageModifiers(Weapon.ReloadDelay, modifiers);
|
||||
Burst = Weapon.Burst;
|
||||
}
|
||||
|
||||
return barrel;
|
||||
}
|
||||
|
||||
public bool IsReloading { get { return FireDelay > 0 || IsTraitDisabled; } }
|
||||
public bool ShouldExplode(Actor self) { return !IsReloading; }
|
||||
|
||||
public WVec MuzzleOffset(Actor self, Barrel b)
|
||||
{
|
||||
var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation);
|
||||
var localOffset = b.Offset + new WVec(-Recoil, WRange.Zero, WRange.Zero);
|
||||
if (turret.Value != null)
|
||||
{
|
||||
var turretOrientation = coords.Value.QuantizeOrientation(self, turret.Value.LocalOrientation(self));
|
||||
localOffset = localOffset.Rotate(turretOrientation);
|
||||
localOffset += turret.Value.Offset;
|
||||
}
|
||||
|
||||
return coords.Value.LocalToWorld(localOffset.Rotate(bodyOrientation));
|
||||
}
|
||||
|
||||
public WRot MuzzleOrientation(Actor self, Barrel b)
|
||||
{
|
||||
var orientation = self.Orientation + WRot.FromYaw(b.Yaw);
|
||||
if (turret.Value != null)
|
||||
orientation += turret.Value.LocalOrientation(self);
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public Actor Actor { get { return self; } }
|
||||
}
|
||||
}
|
||||
271
OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
Normal file
271
OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public abstract class AttackBaseInfo : ITraitInfo
|
||||
{
|
||||
[Desc("Armament names")]
|
||||
public readonly string[] Armaments = { "primary", "secondary" };
|
||||
|
||||
public readonly string Cursor = "attack";
|
||||
public readonly string OutsideRangeCursor = "attackoutsiderange";
|
||||
|
||||
[Desc("Does the attack type require the attacker to enter the target's cell?")]
|
||||
public readonly bool AttackRequiresEnteringCell = false;
|
||||
|
||||
public abstract object Create(ActorInitializer init);
|
||||
}
|
||||
|
||||
public abstract class AttackBase : IIssueOrder, IResolveOrder, IOrderVoice, ISync
|
||||
{
|
||||
[Sync] public bool IsAttacking { get; internal set; }
|
||||
public IEnumerable<Armament> Armaments { get { return getArmaments(); } }
|
||||
public readonly AttackBaseInfo Info;
|
||||
|
||||
protected Lazy<IFacing> facing;
|
||||
protected Lazy<Building> building;
|
||||
protected Lazy<IPositionable> positionable;
|
||||
protected Func<IEnumerable<Armament>> getArmaments;
|
||||
|
||||
readonly Actor self;
|
||||
|
||||
public AttackBase(Actor self, AttackBaseInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
Info = info;
|
||||
|
||||
var armaments = Exts.Lazy(() => self.TraitsImplementing<Armament>()
|
||||
.Where(a => info.Armaments.Contains(a.Info.Name)));
|
||||
|
||||
getArmaments = () => armaments.Value;
|
||||
|
||||
facing = Exts.Lazy(() => self.TraitOrDefault<IFacing>());
|
||||
building = Exts.Lazy(() => self.TraitOrDefault<Building>());
|
||||
positionable = Exts.Lazy(() => self.Trait<IPositionable>());
|
||||
}
|
||||
|
||||
protected virtual bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (!self.IsInWorld)
|
||||
return false;
|
||||
|
||||
if (!HasAnyValidWeapons(target))
|
||||
return false;
|
||||
|
||||
// Building is under construction or is being sold
|
||||
if (building.Value != null && !building.Value.BuildComplete)
|
||||
return false;
|
||||
|
||||
if (!target.IsValidFor(self))
|
||||
return false;
|
||||
|
||||
if (Armaments.All(a => a.IsReloading))
|
||||
return false;
|
||||
|
||||
if (self.IsDisabled())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void DoAttack(Actor self, Target target)
|
||||
{
|
||||
if (!CanAttack(self, target))
|
||||
return;
|
||||
|
||||
foreach (var a in Armaments)
|
||||
a.CheckFire(self, facing.Value, target);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
var armament = Armaments.FirstOrDefault(a => a.Weapon.Warheads.Any(w => (w is DamageWarhead)));
|
||||
if (armament == null)
|
||||
yield break;
|
||||
|
||||
var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0;
|
||||
yield return new AttackOrderTargeter(this, "Attack", 6, negativeDamage);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order is AttackOrderTargeter)
|
||||
{
|
||||
switch (target.Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
return new Order("Attack", self, queued) { TargetActor = target.Actor };
|
||||
case TargetType.FrozenActor:
|
||||
return new Order("Attack", self, queued) { ExtraData = target.FrozenActor.ID };
|
||||
case TargetType.Terrain:
|
||||
return new Order("Attack", self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Attack")
|
||||
{
|
||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||
if (!target.IsValidFor(self))
|
||||
return;
|
||||
|
||||
self.SetTargetLine(target, Color.Red);
|
||||
AttackTarget(target, order.Queued, true);
|
||||
}
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
return order.OrderString == "Attack" ? "Attack" : null;
|
||||
}
|
||||
|
||||
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove);
|
||||
|
||||
public bool HasAnyValidWeapons(Target t)
|
||||
{
|
||||
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
|
||||
return false;
|
||||
|
||||
return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World, self));
|
||||
}
|
||||
|
||||
public WRange GetMaximumRange()
|
||||
{
|
||||
return Armaments.Select(a => a.Weapon.Range).Append(WRange.Zero).Max();
|
||||
}
|
||||
|
||||
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World, self)); }
|
||||
|
||||
public void AttackTarget(Target target, bool queued, bool allowMove)
|
||||
{
|
||||
if (!target.IsValidFor(self))
|
||||
return;
|
||||
|
||||
if (!queued)
|
||||
self.CancelActivity();
|
||||
|
||||
self.QueueActivity(GetAttackActivity(self, target, allowMove));
|
||||
}
|
||||
|
||||
public bool IsReachableTarget(Target target, bool allowMove)
|
||||
{
|
||||
return HasAnyValidWeapons(target)
|
||||
&& (target.IsInRange(self.CenterPosition, GetMaximumRange()) || (allowMove && self.HasTrait<IMove>()));
|
||||
}
|
||||
|
||||
class AttackOrderTargeter : IOrderTargeter
|
||||
{
|
||||
readonly bool negativeDamage;
|
||||
readonly AttackBase ab;
|
||||
|
||||
public AttackOrderTargeter(AttackBase ab, string order, int priority, bool negativeDamage)
|
||||
{
|
||||
this.ab = ab;
|
||||
this.OrderID = order;
|
||||
this.OrderPriority = priority;
|
||||
this.negativeDamage = negativeDamage;
|
||||
}
|
||||
|
||||
public string OrderID { get; private set; }
|
||||
public int OrderPriority { get; private set; }
|
||||
|
||||
bool CanTargetActor(Actor self, Target target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
var a = ab.ChooseArmamentForTarget(target);
|
||||
cursor = a != null && !target.IsInRange(self.CenterPosition, a.Weapon.Range)
|
||||
? ab.Info.OutsideRangeCursor
|
||||
: ab.Info.Cursor;
|
||||
|
||||
if (target.Type == TargetType.Actor && target.Actor == self)
|
||||
return false;
|
||||
|
||||
if (!ab.HasAnyValidWeapons(target))
|
||||
return false;
|
||||
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
return true;
|
||||
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
|
||||
if (target.RequiresForceFire)
|
||||
return false;
|
||||
|
||||
var targetableRelationship = negativeDamage ? Stance.Ally : Stance.Enemy;
|
||||
|
||||
var owner = target.Type == TargetType.FrozenActor ? target.FrozenActor.Owner : target.Actor.Owner;
|
||||
return self.Owner.Stances[owner] == targetableRelationship;
|
||||
}
|
||||
|
||||
bool CanTargetLocation(Actor self, CPos location, List<Actor> actorsAtLocation, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
if (!self.World.Map.Contains(location))
|
||||
return false;
|
||||
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
cursor = ab.Info.Cursor;
|
||||
|
||||
if (negativeDamage)
|
||||
return false;
|
||||
|
||||
if (!ab.HasAnyValidWeapons(Target.FromCell(self.World, location)))
|
||||
return false;
|
||||
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceAttack))
|
||||
{
|
||||
var maxRange = ab.GetMaximumRange().Range;
|
||||
var targetRange = (self.World.Map.CenterOfCell(location) - self.CenterPosition).HorizontalLengthSquared;
|
||||
if (targetRange > maxRange * maxRange)
|
||||
cursor = ab.Info.OutsideRangeCursor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
switch (target.Type)
|
||||
{
|
||||
case TargetType.Actor:
|
||||
case TargetType.FrozenActor:
|
||||
return CanTargetActor(self, target, modifiers, ref cursor);
|
||||
case TargetType.Terrain:
|
||||
return CanTargetLocation(self, self.World.Map.CellContaining(target.CenterPosition), othersAtTarget, modifiers, ref cursor);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsQueued { get; protected set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
132
OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs
Normal file
132
OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Charges up before being able to attack.")]
|
||||
class AttackChargeInfo : AttackOmniInfo
|
||||
{
|
||||
[Desc("How many charges this actor has to attack with, once charged.")]
|
||||
public readonly int MaxCharges = 1;
|
||||
|
||||
[Desc("Reload time for all charges (in ticks).")]
|
||||
public readonly int ReloadTime = 120;
|
||||
|
||||
[Desc("Delay for initial charge attack (in ticks).")]
|
||||
public readonly int InitialChargeDelay = 22;
|
||||
|
||||
[Desc("Delay between charge attacks (in ticks).")]
|
||||
public readonly int ChargeDelay = 3;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackCharge(init.self, this); }
|
||||
}
|
||||
|
||||
class AttackCharge : AttackOmni, ITick, INotifyAttack, ISync
|
||||
{
|
||||
readonly AttackChargeInfo info;
|
||||
|
||||
[Sync] int charges;
|
||||
[Sync] int timeToRecharge;
|
||||
|
||||
public AttackCharge(Actor self, AttackChargeInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.info = info;
|
||||
charges = info.MaxCharges;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (--timeToRecharge <= 0)
|
||||
charges = info.MaxCharges;
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (!IsReachableTarget(target, true))
|
||||
return false;
|
||||
|
||||
return base.CanAttack(self, target);
|
||||
}
|
||||
|
||||
public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
|
||||
{
|
||||
--charges;
|
||||
timeToRecharge = info.ReloadTime;
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
return new ChargeAttack(this, newTarget);
|
||||
}
|
||||
|
||||
public override void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
base.ResolveOrder(self, order);
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
self.CancelActivity();
|
||||
}
|
||||
|
||||
class ChargeAttack : Activity
|
||||
{
|
||||
readonly AttackCharge attack;
|
||||
readonly Target target;
|
||||
|
||||
public ChargeAttack(AttackCharge attack, Target target)
|
||||
{
|
||||
this.attack = attack;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !attack.CanAttack(self, target))
|
||||
return NextActivity;
|
||||
|
||||
if (attack.charges == 0)
|
||||
return this;
|
||||
|
||||
self.Trait<RenderBuildingCharge>().PlayCharge(self);
|
||||
|
||||
return Util.SequenceActivities(new Wait(attack.info.InitialChargeDelay), new ChargeFire(attack, target), this);
|
||||
}
|
||||
}
|
||||
|
||||
class ChargeFire : Activity
|
||||
{
|
||||
readonly AttackCharge attack;
|
||||
readonly Target target;
|
||||
|
||||
public ChargeFire(AttackCharge attack, Target target)
|
||||
{
|
||||
this.attack = attack;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !attack.CanAttack(self, target))
|
||||
return NextActivity;
|
||||
|
||||
if (attack.charges == 0)
|
||||
return NextActivity;
|
||||
|
||||
attack.DoAttack(self, target);
|
||||
|
||||
return Util.SequenceActivities(new Wait(attack.info.ChargeDelay), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs
Normal file
104
OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Actor will follow units until in range to attack them.")]
|
||||
public class AttackFollowInfo : AttackBaseInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackFollow(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackFollow : AttackBase, ITick, ISync
|
||||
{
|
||||
public Target Target { get; protected set; }
|
||||
|
||||
public AttackFollow(Actor self, AttackFollowInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (!target.IsValidFor(self))
|
||||
return false;
|
||||
|
||||
return base.CanAttack(self, target);
|
||||
}
|
||||
|
||||
public virtual void Tick(Actor self)
|
||||
{
|
||||
DoAttack(self, Target);
|
||||
IsAttacking = Target.IsValidFor(self);
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
return new AttackActivity(self, newTarget, allowMove);
|
||||
}
|
||||
|
||||
public override void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
base.ResolveOrder(self, order);
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
Target = Target.Invalid;
|
||||
}
|
||||
|
||||
class AttackActivity : Activity
|
||||
{
|
||||
readonly AttackFollow attack;
|
||||
readonly IMove move;
|
||||
readonly Target target;
|
||||
|
||||
public AttackActivity(Actor self, Target target, bool allowMove)
|
||||
{
|
||||
attack = self.Trait<AttackFollow>();
|
||||
move = allowMove ? self.TraitOrDefault<IMove>() : null;
|
||||
|
||||
// HACK: Mobile.OnRails is horrible
|
||||
var mobile = move as Mobile;
|
||||
if (mobile != null && mobile.Info.OnRails)
|
||||
move = null;
|
||||
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
if (self.IsDisabled())
|
||||
return this;
|
||||
|
||||
var weapon = attack.ChooseArmamentForTarget(target);
|
||||
if (weapon != null)
|
||||
{
|
||||
var targetIsMobile = (target.Type == TargetType.Actor && target.Actor.HasTrait<IMove>())
|
||||
|| (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.Traits.Contains<IMove>());
|
||||
|
||||
// Try and sit at least one cell closer than the max range to give some leeway if the target starts moving.
|
||||
var maxRange = targetIsMobile ? new WRange(Math.Max(weapon.Weapon.MinRange.Range, weapon.Weapon.Range.Range - 1024))
|
||||
: weapon.Weapon.Range;
|
||||
|
||||
attack.Target = target;
|
||||
|
||||
if (move != null)
|
||||
return Util.SequenceActivities(move.MoveFollow(self, target, weapon.Weapon.MinRange, maxRange), this);
|
||||
}
|
||||
|
||||
return NextActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs
Normal file
58
OpenRA.Mods.Common/Traits/Attack/AttackFrontal.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Unit got to face the target")]
|
||||
public class AttackFrontalInfo : AttackBaseInfo, Requires<IFacingInfo>
|
||||
{
|
||||
public readonly int FacingTolerance = 1;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new AttackFrontal(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackFrontal : AttackBase
|
||||
{
|
||||
readonly AttackFrontalInfo info;
|
||||
|
||||
public AttackFrontal(Actor self, AttackFrontalInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (!base.CanAttack(self, target))
|
||||
return false;
|
||||
|
||||
var f = facing.Value.Facing;
|
||||
var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, f);
|
||||
|
||||
if (Math.Abs(facingToTarget - f) % 256 > info.FacingTolerance)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
var a = ChooseArmamentForTarget(newTarget);
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return new Activities.Attack(self, newTarget, a.Weapon.MinRange, a.Weapon.Range, allowMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
OpenRA.Mods.Common/Traits/Attack/AttackMedic.cs
Normal file
38
OpenRA.Mods.Common/Traits/Attack/AttackMedic.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Give the unit a \"heal-weapon\" that attacks friendly targets if they are damaged.",
|
||||
"It conflicts with any other weapon or Attack*: trait because it will hurt friendlies during the",
|
||||
"heal process then. It also won't work with buildings (use RepairsUnits: for them)")]
|
||||
public class AttackMedicInfo : AttackFrontalInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackMedic(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackMedic : AttackFrontal
|
||||
{
|
||||
public AttackMedic(Actor self, AttackMedicInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
var a = ChooseArmamentForTarget(newTarget);
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return new Activities.Heal(self, newTarget, a.Weapon.MinRange, a.Weapon.Range, allowMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs
Normal file
52
OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
class AttackOmniInfo : AttackBaseInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackOmni(init.self, this); }
|
||||
}
|
||||
|
||||
class AttackOmni : AttackBase, ISync
|
||||
{
|
||||
public AttackOmni(Actor self, AttackOmniInfo info)
|
||||
: base(self, info) { }
|
||||
|
||||
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
|
||||
{
|
||||
return new SetTarget(this, newTarget);
|
||||
}
|
||||
|
||||
class SetTarget : Activity
|
||||
{
|
||||
readonly Target target;
|
||||
readonly AttackOmni attack;
|
||||
|
||||
public SetTarget(AttackOmni attack, Target target)
|
||||
{
|
||||
this.target = target;
|
||||
this.attack = attack;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
if (IsCanceled || !target.IsValidFor(self))
|
||||
return NextActivity;
|
||||
|
||||
attack.DoAttack(self, target);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
OpenRA.Mods.Common/Traits/Attack/AttackTurreted.cs
Normal file
46
OpenRA.Mods.Common/Traits/Attack/AttackTurreted.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Actor has a visual turret used to attack.")]
|
||||
public class AttackTurretedInfo : AttackFollowInfo, Requires<TurretedInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackTurreted(init.self, this); }
|
||||
}
|
||||
|
||||
public class AttackTurreted : AttackFollow, ITick, ISync
|
||||
{
|
||||
protected IEnumerable<Turreted> turrets;
|
||||
|
||||
public AttackTurreted(Actor self, AttackTurretedInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
turrets = self.TraitsImplementing<Turreted>();
|
||||
}
|
||||
|
||||
protected override bool CanAttack(Actor self, Target target)
|
||||
{
|
||||
if (!base.CanAttack(self, target))
|
||||
return false;
|
||||
|
||||
var canAttack = false;
|
||||
foreach (var t in turrets)
|
||||
if (t.FaceTarget(self, target))
|
||||
canAttack = true;
|
||||
|
||||
return canAttack;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
OpenRA.Mods.Common/Traits/Attack/AttackWander.cs
Normal file
37
OpenRA.Mods.Common/Traits/Attack/AttackWander.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Will AttackMove to a random location within MoveRadius when idle.",
|
||||
"This conflicts with player orders and should only be added to animal creeps.")]
|
||||
class AttackWanderInfo : WandersInfo, Requires<AttackMoveInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new AttackWander(init.self, this); }
|
||||
}
|
||||
|
||||
class AttackWander : Wanders
|
||||
{
|
||||
readonly AttackMove attackMove;
|
||||
|
||||
public AttackWander(Actor self, AttackWanderInfo info)
|
||||
: base(self, info)
|
||||
{
|
||||
attackMove = self.TraitOrDefault<AttackMove>();
|
||||
}
|
||||
|
||||
public override void DoAction(Actor self, CPos targetPos)
|
||||
{
|
||||
attackMove.ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = targetPos });
|
||||
}
|
||||
}
|
||||
}
|
||||
69
OpenRA.Mods.Common/Traits/AttackMove.cs
Normal file
69
OpenRA.Mods.Common/Traits/AttackMove.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Provides access to the attack-move command, which will make the actor automatically engage viable targets while moving to the destination.")]
|
||||
class AttackMoveInfo : ITraitInfo
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new AttackMove(init.self, this); }
|
||||
}
|
||||
|
||||
class AttackMove : IResolveOrder, IOrderVoice, INotifyIdle, ISync
|
||||
{
|
||||
[Sync] public CPos _targetLocation { get { return TargetLocation.HasValue ? TargetLocation.Value : CPos.Zero; } }
|
||||
public CPos? TargetLocation = null;
|
||||
|
||||
readonly IMove move;
|
||||
|
||||
public AttackMove(Actor self, AttackMoveInfo info)
|
||||
{
|
||||
move = self.Trait<IMove>();
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "AttackMove")
|
||||
return "AttackMove";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void Activate(Actor self)
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.QueueActivity(new AttackMoveActivity(self, move.MoveTo(TargetLocation.Value, 1)));
|
||||
}
|
||||
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
// This might cause the actor to be stuck if the target location is unreachable
|
||||
if (TargetLocation.HasValue && self.Location != TargetLocation.Value)
|
||||
Activate(self);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
TargetLocation = null;
|
||||
|
||||
if (order.OrderString == "AttackMove")
|
||||
{
|
||||
TargetLocation = move.NearestMoveableCell(order.TargetLocation);
|
||||
self.SetTargetLine(Target.FromCell(self.World, TargetLocation.Value), Color.Red);
|
||||
Activate(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
OpenRA.Mods.Common/Traits/AutoHeal.cs
Normal file
37
OpenRA.Mods.Common/Traits/AutoHeal.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Used together with AttackMedic: to make the healer do it's job automatically to nearby units.")]
|
||||
class AutoHealInfo : TraitInfo<AutoHeal>, Requires<AttackBaseInfo> { }
|
||||
|
||||
class AutoHeal : INotifyIdle
|
||||
{
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
var attack = self.Trait<AttackBase>();
|
||||
var inRange = self.World.FindActorsInCircle(self.CenterPosition, attack.GetMaximumRange());
|
||||
|
||||
var target = inRange
|
||||
.Where(a => a != self && a.AppearsFriendlyTo(self))
|
||||
.Where(a => a.IsInWorld && !a.IsDead)
|
||||
.Where(a => a.GetDamageState() > DamageState.Undamaged)
|
||||
.Where(a => attack.HasAnyValidWeapons(Target.FromActor(a)))
|
||||
.ClosestTo(self);
|
||||
|
||||
if (target != null)
|
||||
self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor(target), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
167
OpenRA.Mods.Common/Traits/AutoTarget.cs
Normal file
167
OpenRA.Mods.Common/Traits/AutoTarget.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("The actor will automatically engage the enemy when it is in range.")]
|
||||
public class AutoTargetInfo : ITraitInfo, Requires<AttackBaseInfo>
|
||||
{
|
||||
[Desc("It will try to hunt down the enemy if it is not set to defend.")]
|
||||
public readonly bool AllowMovement = true;
|
||||
[Desc("Set to a value >1 to override weapons maximum range for this.")]
|
||||
public readonly int ScanRadius = -1;
|
||||
|
||||
[Desc("Possible values are HoldFire, ReturnFire, Defend and AttackAnything.")]
|
||||
public readonly UnitStance InitialStance = UnitStance.AttackAnything;
|
||||
[Desc("Allow the player to change the unit stance.")]
|
||||
public readonly bool EnableStances = true;
|
||||
|
||||
[Desc("Ticks to wait until next AutoTarget: attempt.")]
|
||||
public readonly int MinimumScanTimeInterval = 3;
|
||||
[Desc("Ticks to wait until next AutoTarget: attempt.")]
|
||||
public readonly int MaximumScanTimeInterval = 8;
|
||||
|
||||
public readonly bool TargetWhenIdle = true;
|
||||
public readonly bool TargetWhenDamaged = true;
|
||||
|
||||
public object Create(ActorInitializer init) { return new AutoTarget(init.self, this); }
|
||||
}
|
||||
|
||||
public enum UnitStance { HoldFire, ReturnFire, Defend, AttackAnything }
|
||||
|
||||
public class AutoTarget : INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync
|
||||
{
|
||||
readonly AutoTargetInfo info;
|
||||
readonly AttackBase attack;
|
||||
readonly AttackFollow at;
|
||||
[Sync] int nextScanTime = 0;
|
||||
|
||||
public UnitStance Stance;
|
||||
[Sync] public Actor Aggressor;
|
||||
[Sync] public Actor TargetedActor;
|
||||
|
||||
// NOT SYNCED: do not refer to this anywhere other than UI code
|
||||
public UnitStance PredictedStance;
|
||||
|
||||
public AutoTarget(Actor self, AutoTargetInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
attack = self.Trait<AttackBase>();
|
||||
Stance = info.InitialStance;
|
||||
PredictedStance = Stance;
|
||||
at = self.TraitOrDefault<AttackFollow>();
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "SetUnitStance" && info.EnableStances)
|
||||
Stance = (UnitStance)order.ExtraData;
|
||||
}
|
||||
|
||||
public void Damaged(Actor self, AttackInfo e)
|
||||
{
|
||||
if (!self.IsIdle || !info.TargetWhenDamaged)
|
||||
return;
|
||||
|
||||
var attacker = e.Attacker;
|
||||
if (attacker.Destroyed || Stance < UnitStance.ReturnFire)
|
||||
return;
|
||||
|
||||
if (!attacker.IsInWorld && !attacker.Destroyed)
|
||||
{
|
||||
// If the aggressor is in a transport, then attack the transport instead
|
||||
var passenger = attacker.TraitOrDefault<Passenger>();
|
||||
if (passenger != null && passenger.Transport != null)
|
||||
attacker = passenger.Transport;
|
||||
}
|
||||
|
||||
// not a lot we can do about things we can't hurt... although maybe we should automatically run away?
|
||||
if (!attack.HasAnyValidWeapons(Target.FromActor(attacker)))
|
||||
return;
|
||||
|
||||
// don't retaliate against own units force-firing on us. It's usually not what the player wanted.
|
||||
if (attacker.AppearsFriendlyTo(self))
|
||||
return;
|
||||
|
||||
// don't retaliate against healers
|
||||
if (e.Damage < 0)
|
||||
return;
|
||||
|
||||
Aggressor = attacker;
|
||||
if (at == null || !at.IsReachableTarget(at.Target, info.AllowMovement && Stance != UnitStance.Defend))
|
||||
Attack(self, Aggressor);
|
||||
}
|
||||
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
if (Stance < UnitStance.Defend || !info.TargetWhenIdle)
|
||||
return;
|
||||
|
||||
var allowMovement = info.AllowMovement && Stance != UnitStance.Defend;
|
||||
if (at == null || !at.IsReachableTarget(at.Target, allowMovement))
|
||||
ScanAndAttack(self);
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (nextScanTime > 0)
|
||||
--nextScanTime;
|
||||
}
|
||||
|
||||
public Actor ScanForTarget(Actor self, Actor currentTarget)
|
||||
{
|
||||
if (nextScanTime <= 0)
|
||||
{
|
||||
var range = info.ScanRadius > 0 ? WRange.FromCells(info.ScanRadius) : attack.GetMaximumRange();
|
||||
if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range))
|
||||
return ChooseTarget(self, range);
|
||||
}
|
||||
|
||||
return currentTarget;
|
||||
}
|
||||
|
||||
public void ScanAndAttack(Actor self)
|
||||
{
|
||||
var targetActor = ScanForTarget(self, null);
|
||||
if (targetActor != null)
|
||||
Attack(self, targetActor);
|
||||
}
|
||||
|
||||
void Attack(Actor self, Actor targetActor)
|
||||
{
|
||||
TargetedActor = targetActor;
|
||||
var target = Target.FromActor(targetActor);
|
||||
self.SetTargetLine(target, Color.Red, false);
|
||||
attack.AttackTarget(target, false, info.AllowMovement && Stance != UnitStance.Defend);
|
||||
}
|
||||
|
||||
Actor ChooseTarget(Actor self, WRange range)
|
||||
{
|
||||
nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval);
|
||||
var inRange = self.World.FindActorsInCircle(self.CenterPosition, range);
|
||||
|
||||
return inRange
|
||||
.Where(a =>
|
||||
a.AppearsHostileTo(self) &&
|
||||
!a.HasTrait<AutoTargetIgnore>() &&
|
||||
attack.HasAnyValidWeapons(Target.FromActor(a)) &&
|
||||
self.Owner.Shroud.IsTargetable(a))
|
||||
.ClosestTo(self);
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("Will not get automatically targeted by enemy (like walls)")]
|
||||
class AutoTargetIgnoreInfo : TraitInfo<AutoTargetIgnore> { }
|
||||
class AutoTargetIgnore { }
|
||||
}
|
||||
32
OpenRA.Mods.Common/Traits/Buildings/Exit.cs
Normal file
32
OpenRA.Mods.Common/Traits/Buildings/Exit.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Where the unit should leave the building. Multiples are allowed if IDs are added: Exit@2, ...")]
|
||||
public class ExitInfo : TraitInfo<Exit>
|
||||
{
|
||||
[Desc("Offset at which that the exiting actor is spawned")]
|
||||
public readonly WVec SpawnOffset = WVec.Zero;
|
||||
|
||||
[Desc("Cell offset where the exiting actor enters the ActorMap")]
|
||||
public readonly CVec ExitCell = CVec.Zero;
|
||||
public readonly int Facing = -1;
|
||||
|
||||
[Desc("AttackMove to a RallyPoint or stay where you are spawned.")]
|
||||
public readonly bool MoveIntoWorld = true;
|
||||
}
|
||||
|
||||
public class Exit { }
|
||||
}
|
||||
75
OpenRA.Mods.Common/Traits/Buildings/Reservable.cs
Normal file
75
OpenRA.Mods.Common/Traits/Buildings/Reservable.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Reserve landing places for aircraft.")]
|
||||
class ReservableInfo : TraitInfo<Reservable> { }
|
||||
|
||||
public class Reservable : ITick, INotifyKilled, INotifyOwnerChanged, INotifySold
|
||||
{
|
||||
Actor reservedFor;
|
||||
Aircraft reservedForAircraft;
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (reservedFor == null)
|
||||
return; /* nothing to do */
|
||||
|
||||
if (!Target.FromActor(reservedFor).IsValidFor(self))
|
||||
reservedFor = null; /* not likely to arrive now. */
|
||||
}
|
||||
|
||||
public IDisposable Reserve(Actor self, Actor forActor, Aircraft forAircraft)
|
||||
{
|
||||
reservedFor = forActor;
|
||||
reservedForAircraft = forAircraft;
|
||||
|
||||
// NOTE: we really dont care about the GC eating DisposableActions that apply to a world *other* than
|
||||
// the one we're playing in.
|
||||
return new DisposableAction(
|
||||
() => { reservedFor = null; reservedForAircraft = null; },
|
||||
() => Game.RunAfterTick(
|
||||
() => { if (Game.IsCurrentWorld(self.World)) throw new InvalidOperationException(
|
||||
"Attempted to finalize an undisposed DisposableAction. {0} ({1}) reserved {2} ({3})"
|
||||
.F(forActor.Info.Name, forActor.ActorID, self.Info.Name, self.ActorID)); }));
|
||||
}
|
||||
|
||||
public static bool IsReserved(Actor a)
|
||||
{
|
||||
var res = a.TraitOrDefault<Reservable>();
|
||||
return res != null && res.reservedFor != null;
|
||||
}
|
||||
|
||||
public void Killed(Actor self, AttackInfo e)
|
||||
{
|
||||
if (reservedForAircraft != null)
|
||||
reservedForAircraft.UnReserve();
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (reservedForAircraft != null)
|
||||
reservedForAircraft.UnReserve();
|
||||
}
|
||||
|
||||
public void Selling(Actor self) { Sold(self); }
|
||||
|
||||
public void Sold(Actor self)
|
||||
{
|
||||
if (reservedForAircraft != null)
|
||||
reservedForAircraft.UnReserve();
|
||||
}
|
||||
}
|
||||
}
|
||||
363
OpenRA.Mods.Common/Traits/Cargo.cs
Normal file
363
OpenRA.Mods.Common/Traits/Cargo.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("This actor can transport Passenger actors.")]
|
||||
public class CargoInfo : ITraitInfo, Requires<IOccupySpaceInfo>
|
||||
{
|
||||
public readonly int MaxWeight = 0;
|
||||
public readonly int PipCount = 0;
|
||||
public readonly string[] Types = { };
|
||||
public readonly string[] InitialUnits = { };
|
||||
public readonly bool EjectOnSell = true;
|
||||
|
||||
[Desc("Which direction the passenger will face (relative to the transport) when unloading.")]
|
||||
public readonly int PassengerFacing = 128;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Cargo(init, this); }
|
||||
}
|
||||
|
||||
public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyCreated, INotifyKilled, INotifyOwnerChanged, INotifyAddedToWorld, ITick, INotifySold, IDisableMove
|
||||
{
|
||||
public readonly CargoInfo Info;
|
||||
readonly Actor self;
|
||||
readonly List<Actor> cargo = new List<Actor>();
|
||||
readonly HashSet<Actor> reserves = new HashSet<Actor>();
|
||||
readonly Lazy<IFacing> facing;
|
||||
|
||||
int totalWeight = 0;
|
||||
int reservedWeight = 0;
|
||||
Helicopter helicopter;
|
||||
|
||||
CPos currentCell;
|
||||
public IEnumerable<CPos> CurrentAdjacentCells { get; private set; }
|
||||
public bool Unloading { get; internal set; }
|
||||
public IEnumerable<Actor> Passengers { get { return cargo; } }
|
||||
public int PassengerCount { get { return cargo.Count; } }
|
||||
|
||||
public Cargo(ActorInitializer init, CargoInfo info)
|
||||
{
|
||||
self = init.self;
|
||||
Info = info;
|
||||
Unloading = false;
|
||||
|
||||
if (init.Contains<RuntimeCargoInit>())
|
||||
{
|
||||
cargo = init.Get<RuntimeCargoInit, Actor[]>().ToList();
|
||||
totalWeight = cargo.Sum(c => GetWeight(c));
|
||||
}
|
||||
else if (init.Contains<CargoInit>())
|
||||
{
|
||||
foreach (var u in init.Get<CargoInit, string[]>())
|
||||
{
|
||||
var unit = self.World.CreateActor(false, u.ToLowerInvariant(),
|
||||
new TypeDictionary { new OwnerInit(self.Owner) });
|
||||
|
||||
cargo.Add(unit);
|
||||
}
|
||||
|
||||
totalWeight = cargo.Sum(c => GetWeight(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var u in info.InitialUnits)
|
||||
{
|
||||
var unit = self.World.CreateActor(false, u.ToLowerInvariant(),
|
||||
new TypeDictionary { new OwnerInit(self.Owner) });
|
||||
|
||||
cargo.Add(unit);
|
||||
}
|
||||
|
||||
totalWeight = cargo.Sum(c => GetWeight(c));
|
||||
}
|
||||
|
||||
facing = Exts.Lazy(self.TraitOrDefault<IFacing>);
|
||||
}
|
||||
|
||||
public void Created(Actor self)
|
||||
{
|
||||
helicopter = self.TraitOrDefault<Helicopter>();
|
||||
}
|
||||
|
||||
static int GetWeight(Actor a) { return a.Info.Traits.Get<PassengerInfo>().Weight; }
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get { yield return new DeployOrderTargeter("Unload", 10, CanUnload); }
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "Unload")
|
||||
return new Order(order.OrderID, self, queued);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Unload")
|
||||
{
|
||||
if (!CanUnload())
|
||||
return;
|
||||
|
||||
Unloading = true;
|
||||
self.CancelActivity();
|
||||
if (helicopter != null)
|
||||
self.QueueActivity(new HeliLand(true));
|
||||
self.QueueActivity(new UnloadCargo(self, true));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<CPos> GetAdjacentCells()
|
||||
{
|
||||
return Util.AdjacentCells(self.World, Target.FromActor(self)).Where(c => self.Location != c);
|
||||
}
|
||||
|
||||
bool CanUnload()
|
||||
{
|
||||
return !IsEmpty(self) && (helicopter == null || helicopter.CanLand(self.Location))
|
||||
&& CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait<IPositionable>().CanEnterCell(c)));
|
||||
}
|
||||
|
||||
public bool CanLoad(Actor self, Actor a)
|
||||
{
|
||||
return (reserves.Contains(a) || HasSpace(GetWeight(a))) && self.CenterPosition.Z == 0;
|
||||
}
|
||||
|
||||
internal bool ReserveSpace(Actor a)
|
||||
{
|
||||
if (reserves.Contains(a))
|
||||
return true;
|
||||
|
||||
var w = GetWeight(a);
|
||||
if (!HasSpace(w))
|
||||
return false;
|
||||
|
||||
reserves.Add(a);
|
||||
reservedWeight += w;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void UnreserveSpace(Actor a)
|
||||
{
|
||||
if (!reserves.Contains(a))
|
||||
return;
|
||||
|
||||
reservedWeight -= GetWeight(a);
|
||||
reserves.Remove(a);
|
||||
}
|
||||
|
||||
public string CursorForOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "Unload")
|
||||
return null;
|
||||
|
||||
return CanUnload() ? "deploy" : "deploy-blocked";
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString != "Unload" || IsEmpty(self))
|
||||
return null;
|
||||
|
||||
return self.HasVoice("Unload") ? "Unload" : "Move";
|
||||
}
|
||||
|
||||
public bool MoveDisabled(Actor self) { return reserves.Any(); }
|
||||
public bool HasSpace(int weight) { return totalWeight + reservedWeight + weight <= Info.MaxWeight; }
|
||||
public bool IsEmpty(Actor self) { return cargo.Count == 0; }
|
||||
|
||||
public Actor Peek(Actor self) { return cargo[0]; }
|
||||
|
||||
public Actor Unload(Actor self)
|
||||
{
|
||||
var a = cargo[0];
|
||||
|
||||
cargo.RemoveAt(0);
|
||||
totalWeight -= GetWeight(a);
|
||||
|
||||
SetPassengerFacing(a);
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerExited>())
|
||||
npe.PassengerExited(self, a);
|
||||
|
||||
var p = a.Trait<Passenger>();
|
||||
p.Transport = null;
|
||||
|
||||
foreach (var u in p.Info.GrantUpgrades)
|
||||
self.Trait<UpgradeManager>().RevokeUpgrade(self, u, p);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void SetPassengerFacing(Actor passenger)
|
||||
{
|
||||
if (facing.Value == null)
|
||||
return;
|
||||
|
||||
var passengerFacing = passenger.TraitOrDefault<IFacing>();
|
||||
if (passengerFacing != null)
|
||||
passengerFacing.Facing = facing.Value.Facing + Info.PassengerFacing;
|
||||
|
||||
var passengerTurreted = passenger.TraitOrDefault<Turreted>();
|
||||
if (passengerTurreted != null)
|
||||
passengerTurreted.TurretFacing = facing.Value.Facing + Info.PassengerFacing;
|
||||
}
|
||||
|
||||
public IEnumerable<PipType> GetPips(Actor self)
|
||||
{
|
||||
var numPips = Info.PipCount;
|
||||
|
||||
for (var i = 0; i < numPips; i++)
|
||||
yield return GetPipAt(i);
|
||||
}
|
||||
|
||||
PipType GetPipAt(int i)
|
||||
{
|
||||
var n = i * Info.MaxWeight / Info.PipCount;
|
||||
|
||||
foreach (var c in cargo)
|
||||
{
|
||||
var pi = c.Info.Traits.Get<PassengerInfo>();
|
||||
if (n < pi.Weight)
|
||||
return pi.PipType;
|
||||
else
|
||||
n -= pi.Weight;
|
||||
}
|
||||
|
||||
return PipType.Transparent;
|
||||
}
|
||||
|
||||
public void Load(Actor self, Actor a)
|
||||
{
|
||||
cargo.Add(a);
|
||||
var w = GetWeight(a);
|
||||
totalWeight += w;
|
||||
if (reserves.Contains(a))
|
||||
{
|
||||
reservedWeight -= w;
|
||||
reserves.Remove(a);
|
||||
}
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.PassengerEntered(self, a);
|
||||
|
||||
var p = a.Trait<Passenger>();
|
||||
p.Transport = self;
|
||||
foreach (var u in p.Info.GrantUpgrades)
|
||||
self.Trait<UpgradeManager>().GrantUpgrade(self, u, p);
|
||||
}
|
||||
|
||||
public void Killed(Actor self, AttackInfo e)
|
||||
{
|
||||
foreach (var c in cargo)
|
||||
c.Kill(e.Attacker);
|
||||
|
||||
cargo.Clear();
|
||||
}
|
||||
|
||||
public void Selling(Actor self) { }
|
||||
public void Sold(Actor self)
|
||||
{
|
||||
if (!Info.EjectOnSell || cargo == null)
|
||||
return;
|
||||
|
||||
while (!IsEmpty(self))
|
||||
SpawnPassenger(Unload(self));
|
||||
}
|
||||
|
||||
void SpawnPassenger(Actor passenger)
|
||||
{
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
w.Add(passenger);
|
||||
passenger.Trait<IPositionable>().SetPosition(passenger, self.Location);
|
||||
|
||||
// TODO: this won't work well for >1 actor as they should move towards the next enterable (sub) cell instead
|
||||
});
|
||||
}
|
||||
|
||||
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (cargo == null)
|
||||
return;
|
||||
|
||||
self.World.AddFrameEndTask(w =>
|
||||
{
|
||||
foreach (var p in Passengers)
|
||||
p.Owner = newOwner;
|
||||
});
|
||||
}
|
||||
|
||||
public void AddedToWorld(Actor self)
|
||||
{
|
||||
// Force location update to avoid issues when initial spawn is outside map
|
||||
currentCell = self.Location;
|
||||
CurrentAdjacentCells = GetAdjacentCells();
|
||||
}
|
||||
|
||||
bool initialized;
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
// Notify initial cargo load
|
||||
if (!initialized)
|
||||
{
|
||||
foreach (var c in cargo)
|
||||
{
|
||||
c.Trait<Passenger>().Transport = self;
|
||||
|
||||
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
|
||||
npe.PassengerEntered(self, c);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
var cell = self.World.Map.CellContaining(self.CenterPosition);
|
||||
if (currentCell != cell)
|
||||
{
|
||||
currentCell = cell;
|
||||
CurrentAdjacentCells = GetAdjacentCells();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface INotifyPassengerEntered { void PassengerEntered(Actor self, Actor passenger); }
|
||||
public interface INotifyPassengerExited { void PassengerExited(Actor self, Actor passenger); }
|
||||
|
||||
public class RuntimeCargoInit : IActorInit<Actor[]>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
readonly Actor[] value = { };
|
||||
public RuntimeCargoInit() { }
|
||||
public RuntimeCargoInit(Actor[] init) { value = init; }
|
||||
public Actor[] Value(World world) { return value; }
|
||||
}
|
||||
|
||||
public class CargoInit : IActorInit<string[]>
|
||||
{
|
||||
[FieldFromYamlKey]
|
||||
readonly string[] value = { };
|
||||
public CargoInit() { }
|
||||
public CargoInit(string[] init) { value = init; }
|
||||
public string[] Value(World world) { return value; }
|
||||
}
|
||||
}
|
||||
144
OpenRA.Mods.Common/Traits/Cloak.cs
Normal file
144
OpenRA.Mods.Common/Traits/Cloak.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("This unit can cloak and uncloak in specific situations.")]
|
||||
public class CloakInfo : UpgradableTraitInfo, ITraitInfo
|
||||
{
|
||||
[Desc("Measured in game ticks.")]
|
||||
public readonly int InitialDelay = 10;
|
||||
|
||||
[Desc("Measured in game ticks.")]
|
||||
public readonly int CloakDelay = 30;
|
||||
|
||||
public readonly bool UncloakOnAttack = true;
|
||||
public readonly bool UncloakOnMove = false;
|
||||
public readonly bool UncloakOnUnload = true;
|
||||
public readonly bool UncloakOnInfiltrate = true;
|
||||
public readonly bool UncloakOnDemolish = true;
|
||||
|
||||
public readonly string CloakSound = null;
|
||||
public readonly string UncloakSound = null;
|
||||
public readonly string Palette = "cloak";
|
||||
|
||||
public readonly string[] CloakTypes = { "Cloak" };
|
||||
|
||||
public object Create(ActorInitializer init) { return new Cloak(init.self, this); }
|
||||
}
|
||||
|
||||
public class Cloak : UpgradableTrait<CloakInfo>, IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier
|
||||
{
|
||||
[Sync] int remainingTime;
|
||||
[Sync] bool damageDisabled;
|
||||
|
||||
Actor self;
|
||||
CPos? lastPos;
|
||||
|
||||
public Cloak(Actor self, CloakInfo info)
|
||||
: base(info)
|
||||
{
|
||||
this.self = self;
|
||||
|
||||
remainingTime = info.InitialDelay;
|
||||
}
|
||||
|
||||
protected override void UpgradeDisabled(Actor self)
|
||||
{
|
||||
Uncloak();
|
||||
remainingTime = Info.InitialDelay;
|
||||
}
|
||||
|
||||
public void Uncloak() { Uncloak(Info.CloakDelay); }
|
||||
|
||||
public void Uncloak(int time)
|
||||
{
|
||||
if (Cloaked)
|
||||
Sound.Play(Info.UncloakSound, self.CenterPosition);
|
||||
|
||||
remainingTime = Math.Max(remainingTime, time);
|
||||
}
|
||||
|
||||
public void Attacking(Actor self, Target target, Armament a, Barrel barrel) { if (Info.UncloakOnAttack) Uncloak(); }
|
||||
|
||||
public bool Cloaked { get { return !IsTraitDisabled && remainingTime <= 0; } }
|
||||
|
||||
public void DamageStateChanged(Actor self, AttackInfo e)
|
||||
{
|
||||
damageDisabled = e.DamageState >= DamageState.Critical;
|
||||
if (damageDisabled)
|
||||
Uncloak();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
|
||||
{
|
||||
if (remainingTime > 0 || IsTraitDisabled)
|
||||
return r;
|
||||
|
||||
if (Cloaked && IsVisible(self, self.World.RenderPlayer))
|
||||
{
|
||||
if (string.IsNullOrEmpty(Info.Palette))
|
||||
return r;
|
||||
else
|
||||
return r.Select(a => a.WithPalette(wr.Palette(Info.Palette)));
|
||||
}
|
||||
else
|
||||
return SpriteRenderable.None;
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (IsTraitDisabled)
|
||||
return;
|
||||
|
||||
if (remainingTime > 0 && !IsTraitDisabled && !damageDisabled && --remainingTime <= 0)
|
||||
Sound.Play(Info.CloakSound, self.CenterPosition);
|
||||
|
||||
if (self.IsDisabled())
|
||||
Uncloak();
|
||||
|
||||
if (Info.UncloakOnMove && (lastPos == null || lastPos.Value != self.Location))
|
||||
{
|
||||
Uncloak();
|
||||
lastPos = self.Location;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsVisible(Actor self, Player viewer)
|
||||
{
|
||||
if (!Cloaked || self.Owner.IsAlliedWith(viewer))
|
||||
return true;
|
||||
|
||||
return self.World.ActorsWithTrait<DetectCloaked>().Any(a =>
|
||||
{
|
||||
var dc = a.Actor.Info.Traits.Get<DetectCloakedInfo>();
|
||||
|
||||
return a.Actor.Owner.IsAlliedWith(viewer)
|
||||
&& Info.CloakTypes.Intersect(dc.CloakTypes).Any()
|
||||
&& (self.CenterPosition - a.Actor.CenterPosition).Length <= WRange.FromCells(dc.Range).Range;
|
||||
});
|
||||
}
|
||||
|
||||
public Color RadarColorOverride(Actor self)
|
||||
{
|
||||
var c = self.Owner.Color.RGB;
|
||||
if (self.Owner == self.World.LocalPlayer && Cloaked)
|
||||
c = Color.FromArgb(128, c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
OpenRA.Mods.Common/Traits/LimitedAmmo.cs
Normal file
75
OpenRA.Mods.Common/Traits/LimitedAmmo.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Actor has a limited amount of ammo, after using it all the actor must reload in some way.")]
|
||||
public class LimitedAmmoInfo : ITraitInfo
|
||||
{
|
||||
public readonly int Ammo = 0;
|
||||
|
||||
[Desc("Defaults to value in Ammo.")]
|
||||
public readonly int PipCount = 0;
|
||||
|
||||
public readonly PipType PipType = PipType.Green;
|
||||
public readonly PipType PipTypeEmpty = PipType.Transparent;
|
||||
|
||||
[Desc("Time to reload measured in ticks.")]
|
||||
public readonly int ReloadTicks = 25 * 2;
|
||||
|
||||
public object Create(ActorInitializer init) { return new LimitedAmmo(this); }
|
||||
}
|
||||
|
||||
public class LimitedAmmo : INotifyAttack, IPips, ISync
|
||||
{
|
||||
[Sync] int ammo;
|
||||
LimitedAmmoInfo info;
|
||||
|
||||
public LimitedAmmo(LimitedAmmoInfo info)
|
||||
{
|
||||
ammo = info.Ammo;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public bool FullAmmo() { return ammo == info.Ammo; }
|
||||
public bool HasAmmo() { return ammo > 0; }
|
||||
public bool GiveAmmo()
|
||||
{
|
||||
if (ammo >= info.Ammo) return false;
|
||||
++ammo;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TakeAmmo()
|
||||
{
|
||||
if (ammo <= 0) return false;
|
||||
--ammo;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int ReloadTimePerAmmo() { return info.ReloadTicks; }
|
||||
|
||||
public void Attacking(Actor self, Target target, Armament a, Barrel barrel) { TakeAmmo(); }
|
||||
|
||||
public int GetAmmoCount() { return ammo; }
|
||||
|
||||
public IEnumerable<PipType> GetPips(Actor self)
|
||||
{
|
||||
var pips = info.PipCount != 0 ? info.PipCount : info.Ammo;
|
||||
return Enumerable.Range(0, pips).Select(i =>
|
||||
(ammo * pips) / info.Ammo > i ?
|
||||
info.PipType : info.PipTypeEmpty);
|
||||
}
|
||||
}
|
||||
}
|
||||
700
OpenRA.Mods.Common/Traits/Mobile.cs
Normal file
700
OpenRA.Mods.Common/Traits/Mobile.cs
Normal file
@@ -0,0 +1,700 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Activities;
|
||||
using OpenRA.Mods.Common;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Flags]
|
||||
public enum CellConditions
|
||||
{
|
||||
None = 0,
|
||||
TransientActors,
|
||||
BlockedByMovers,
|
||||
All = TransientActors | BlockedByMovers
|
||||
}
|
||||
|
||||
[Desc("Unit is able to move.")]
|
||||
public class MobileInfo : ITraitInfo, IOccupySpaceInfo, IFacingInfo, IMoveInfo, UsesInit<FacingInit>, UsesInit<LocationInit>, UsesInit<SubCellInit>
|
||||
{
|
||||
[FieldLoader.LoadUsing("LoadSpeeds")]
|
||||
[Desc("Set Water: 0 for ground units and lower the value on rough terrain.")]
|
||||
public readonly Dictionary<string, TerrainInfo> TerrainSpeeds;
|
||||
|
||||
[Desc("e.g. crate, wall, infantry")]
|
||||
public readonly string[] Crushes = { };
|
||||
|
||||
public readonly int WaitAverage = 5;
|
||||
|
||||
public readonly int WaitSpread = 2;
|
||||
|
||||
public readonly int InitialFacing = 128;
|
||||
|
||||
[Desc("Rate of Turning")]
|
||||
public readonly int ROT = 255;
|
||||
|
||||
public readonly int Speed = 1;
|
||||
|
||||
public readonly bool OnRails = false;
|
||||
|
||||
[Desc("Allow multiple (infantry) units in one cell.")]
|
||||
public readonly bool SharesCell = false;
|
||||
|
||||
[Desc("Can the actor be ordered to move in to shroud?")]
|
||||
public readonly bool MoveIntoShroud = true;
|
||||
|
||||
public readonly string Cursor = "move";
|
||||
public readonly string BlockedCursor = "move-blocked";
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Mobile(init, this); }
|
||||
|
||||
static object LoadSpeeds(MiniYaml y)
|
||||
{
|
||||
var ret = new Dictionary<string, TerrainInfo>();
|
||||
foreach (var t in y.ToDictionary()["TerrainSpeeds"].Nodes)
|
||||
{
|
||||
var speed = FieldLoader.GetValue<decimal>("speed", t.Value.Value);
|
||||
var nodesDict = t.Value.ToDictionary();
|
||||
var cost = nodesDict.ContainsKey("PathingCost")
|
||||
? FieldLoader.GetValue<int>("cost", nodesDict["PathingCost"].Value)
|
||||
: (int)(10000 / speed);
|
||||
ret.Add(t.Key, new TerrainInfo(speed, cost));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TerrainInfo[] LoadTilesetSpeeds(TileSet tileSet)
|
||||
{
|
||||
var info = new TerrainInfo[tileSet.TerrainInfo.Length];
|
||||
for (var i = 0; i < info.Length; i++)
|
||||
info[i] = TerrainInfo.Impassable;
|
||||
|
||||
foreach (var kvp in TerrainSpeeds)
|
||||
{
|
||||
byte index;
|
||||
if (tileSet.TryGetTerrainIndex(kvp.Key, out index))
|
||||
info[index] = kvp.Value;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public class TerrainInfo
|
||||
{
|
||||
public static readonly TerrainInfo Impassable = new TerrainInfo();
|
||||
|
||||
public readonly int Cost;
|
||||
public readonly decimal Speed;
|
||||
|
||||
public TerrainInfo()
|
||||
{
|
||||
Cost = int.MaxValue;
|
||||
Speed = 0;
|
||||
}
|
||||
|
||||
public TerrainInfo(decimal speed, int cost)
|
||||
{
|
||||
Speed = speed;
|
||||
Cost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Cache<TileSet, TerrainInfo[]> TilesetTerrainInfo;
|
||||
public readonly Cache<TileSet, int> TilesetMovementClass;
|
||||
|
||||
public MobileInfo()
|
||||
{
|
||||
TilesetTerrainInfo = new Cache<TileSet, TerrainInfo[]>(LoadTilesetSpeeds);
|
||||
TilesetMovementClass = new Cache<TileSet, int>(CalculateTilesetMovementClass);
|
||||
}
|
||||
|
||||
public int MovementCostForCell(World world, CPos cell)
|
||||
{
|
||||
if (!world.Map.Contains(cell))
|
||||
return int.MaxValue;
|
||||
|
||||
var index = world.Map.GetTerrainIndex(cell);
|
||||
if (index == byte.MaxValue)
|
||||
return int.MaxValue;
|
||||
|
||||
return TilesetTerrainInfo[world.TileSet][index].Cost;
|
||||
}
|
||||
|
||||
public int CalculateTilesetMovementClass(TileSet tileset)
|
||||
{
|
||||
/* collect our ability to cross *all* terraintypes, in a bitvector */
|
||||
return TilesetTerrainInfo[tileset].Select(ti => ti.Cost < int.MaxValue).ToBits();
|
||||
}
|
||||
|
||||
public int GetMovementClass(TileSet tileset)
|
||||
{
|
||||
return TilesetMovementClass[tileset];
|
||||
}
|
||||
|
||||
static bool IsMovingInMyDirection(Actor self, Actor other)
|
||||
{
|
||||
if (!other.IsMoving()) return false;
|
||||
if (self == null) return true;
|
||||
|
||||
var selfMobile = self.TraitOrDefault<Mobile>();
|
||||
if (selfMobile == null) return false;
|
||||
|
||||
var otherMobile = other.TraitOrDefault<Mobile>();
|
||||
if (otherMobile == null) return false;
|
||||
|
||||
// Sign of dot-product indicates (roughly) if vectors are facing in same or opposite directions:
|
||||
var dp = CVec.Dot(selfMobile.ToCell - self.Location, otherMobile.ToCell - other.Location);
|
||||
if (dp <= 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
||||
{
|
||||
if (MovementCostForCell(world, cell) == int.MaxValue)
|
||||
return false;
|
||||
|
||||
if (SharesCell && world.ActorMap.HasFreeSubCell(cell))
|
||||
return true;
|
||||
|
||||
if (check.HasFlag(CellConditions.TransientActors))
|
||||
{
|
||||
var canIgnoreMovingAllies = self != null && !check.HasFlag(CellConditions.BlockedByMovers);
|
||||
var needsCellExclusively = self == null || Crushes == null || !Crushes.Any();
|
||||
foreach (var a in world.ActorMap.GetUnitsAt(cell))
|
||||
{
|
||||
if (a == ignoreActor)
|
||||
continue;
|
||||
|
||||
// Neutral/enemy units are blockers. Allied units that are moving are not blockers.
|
||||
if (canIgnoreMovingAllies && self.Owner.Stances[a.Owner] == Stance.Ally && IsMovingInMyDirection(self, a)) continue;
|
||||
|
||||
// Non-sharable unit can enter a cell with shareable units only if it can crush all of them.
|
||||
if (needsCellExclusively)
|
||||
return false;
|
||||
var crushables = a.TraitsImplementing<ICrushable>();
|
||||
if (!crushables.Any())
|
||||
return false;
|
||||
foreach (var crushable in crushables)
|
||||
if (!crushable.CrushableBy(Crushes, self.Owner))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public SubCell GetAvailableSubCell(World world, Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
||||
{
|
||||
if (MovementCostForCell(world, cell) == int.MaxValue)
|
||||
return SubCell.Invalid;
|
||||
|
||||
if (check.HasFlag(CellConditions.TransientActors))
|
||||
{
|
||||
var canIgnoreMovingAllies = self != null && !check.HasFlag(CellConditions.BlockedByMovers);
|
||||
var needsCellExclusively = self == null || Crushes == null || !Crushes.Any();
|
||||
|
||||
Func<Actor, bool> checkTransient = a =>
|
||||
{
|
||||
if (a == ignoreActor)
|
||||
return false;
|
||||
|
||||
// Neutral/enemy units are blockers. Allied units that are moving are not blockers.
|
||||
if (canIgnoreMovingAllies && self.Owner.Stances[a.Owner] == Stance.Ally && IsMovingInMyDirection(self, a))
|
||||
return false;
|
||||
|
||||
// Non-sharable unit can enter a cell with shareable units only if it can crush all of them.
|
||||
if (needsCellExclusively)
|
||||
return true;
|
||||
var crushables = a.TraitsImplementing<ICrushable>();
|
||||
if (!crushables.Any())
|
||||
return true;
|
||||
foreach (var crushable in crushables)
|
||||
if (!crushable.CrushableBy(Crushes, self.Owner))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!SharesCell)
|
||||
return world.ActorMap.AnyUnitsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
|
||||
|
||||
return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient);
|
||||
}
|
||||
|
||||
if (!SharesCell)
|
||||
return world.ActorMap.AnyUnitsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
|
||||
|
||||
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
|
||||
}
|
||||
|
||||
public int GetInitialFacing() { return InitialFacing; }
|
||||
}
|
||||
|
||||
public class Mobile : IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, IFacing, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyBlockingMove
|
||||
{
|
||||
const int AverageTicksBeforePathing = 5;
|
||||
const int SpreadTicksBeforePathing = 5;
|
||||
internal int TicksBeforePathing = 0;
|
||||
|
||||
readonly Actor self;
|
||||
public readonly MobileInfo Info;
|
||||
public bool IsMoving { get; set; }
|
||||
|
||||
int facing;
|
||||
CPos fromCell, toCell;
|
||||
public SubCell FromSubCell, ToSubCell;
|
||||
|
||||
[Sync] public int Facing
|
||||
{
|
||||
get { return facing; }
|
||||
set { facing = value; }
|
||||
}
|
||||
|
||||
public int ROT { get { return Info.ROT; } }
|
||||
|
||||
[Sync] public WPos CenterPosition { get; private set; }
|
||||
[Sync] public CPos FromCell { get { return fromCell; } }
|
||||
[Sync] public CPos ToCell { get { return toCell; } }
|
||||
|
||||
[Sync] public int PathHash; // written by Move.EvalPath, to temporarily debug this crap.
|
||||
|
||||
public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub)
|
||||
{
|
||||
if (FromCell == from && ToCell == to && FromSubCell == fromSub && ToSubCell == toSub)
|
||||
return;
|
||||
|
||||
RemoveInfluence();
|
||||
fromCell = from;
|
||||
toCell = to;
|
||||
FromSubCell = fromSub;
|
||||
ToSubCell = toSub;
|
||||
AddInfluence();
|
||||
}
|
||||
|
||||
public Mobile(ActorInitializer init, MobileInfo info)
|
||||
{
|
||||
self = init.self;
|
||||
Info = info;
|
||||
|
||||
ToSubCell = FromSubCell = info.SharesCell ? init.world.Map.DefaultSubCell : SubCell.FullCell;
|
||||
if (init.Contains<SubCellInit>())
|
||||
{
|
||||
this.FromSubCell = this.ToSubCell = init.Get<SubCellInit, SubCell>();
|
||||
}
|
||||
|
||||
if (init.Contains<LocationInit>())
|
||||
{
|
||||
this.fromCell = this.toCell = init.Get<LocationInit, CPos>();
|
||||
SetVisualPosition(self, init.world.Map.CenterOfSubCell(FromCell, FromSubCell));
|
||||
}
|
||||
|
||||
this.Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
|
||||
|
||||
// Sets the visual position to WPos accuracy
|
||||
// Use LocationInit if you want to insert the actor into the ActorMap!
|
||||
if (init.Contains<CenterPositionInit>())
|
||||
SetVisualPosition(self, init.Get<CenterPositionInit, WPos>());
|
||||
}
|
||||
|
||||
// Returns a valid sub-cell
|
||||
public SubCell GetValidSubCell(SubCell preferred = SubCell.Any)
|
||||
{
|
||||
// Try same sub-cell
|
||||
if (preferred == SubCell.Any)
|
||||
preferred = FromSubCell;
|
||||
|
||||
// Fix sub-cell assignment
|
||||
if (Info.SharesCell)
|
||||
{
|
||||
if (preferred <= SubCell.FullCell)
|
||||
return self.World.Map.DefaultSubCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (preferred != SubCell.FullCell)
|
||||
return SubCell.FullCell;
|
||||
}
|
||||
|
||||
return preferred;
|
||||
}
|
||||
|
||||
public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
subCell = GetValidSubCell(subCell);
|
||||
SetLocation(cell, subCell, cell, subCell);
|
||||
SetVisualPosition(self, self.World.Map.CenterOfSubCell(cell, subCell));
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
public void SetPosition(Actor self, WPos pos)
|
||||
{
|
||||
var cell = self.World.Map.CellContaining(pos);
|
||||
SetLocation(cell, FromSubCell, cell, FromSubCell);
|
||||
SetVisualPosition(self, pos);
|
||||
FinishedMoving(self);
|
||||
}
|
||||
|
||||
public void SetVisualPosition(Actor self, WPos pos)
|
||||
{
|
||||
CenterPosition = pos;
|
||||
if (self.IsInWorld)
|
||||
{
|
||||
self.World.ScreenMap.Update(self);
|
||||
self.World.ActorMap.UpdatePosition(self, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddedToWorld(Actor self)
|
||||
{
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
self.World.ActorMap.AddPosition(self, this);
|
||||
self.World.ScreenMap.Add(self);
|
||||
}
|
||||
|
||||
public void RemovedFromWorld(Actor self)
|
||||
{
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
self.World.ActorMap.RemovePosition(self, this);
|
||||
self.World.ScreenMap.Remove(self);
|
||||
}
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders { get { yield return new MoveOrderTargeter(self, Info); } }
|
||||
|
||||
// Note: Returns a valid order even if the unit can't move to the target
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order is MoveOrderTargeter)
|
||||
{
|
||||
if (Info.OnRails)
|
||||
return null;
|
||||
|
||||
return new Order("Move", self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target)
|
||||
{
|
||||
// Limit search to a radius of 10 tiles
|
||||
return NearestMoveableCell(target, 1, 10);
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target, int minRange, int maxRange)
|
||||
{
|
||||
if (CanEnterCell(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (CanEnterCell(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
public CPos NearestCell(CPos target, Func<CPos, bool> check, int minRange, int maxRange)
|
||||
{
|
||||
if (check(target))
|
||||
return target;
|
||||
|
||||
foreach (var tile in self.World.Map.FindTilesInAnnulus(target, minRange, maxRange))
|
||||
if (check(tile))
|
||||
return tile;
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
void PerformMoveInner(Actor self, CPos targetLocation, bool queued)
|
||||
{
|
||||
var currentLocation = NearestMoveableCell(targetLocation);
|
||||
|
||||
if (!CanEnterCell(currentLocation))
|
||||
{
|
||||
if (queued) self.CancelActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!queued) self.CancelActivity();
|
||||
|
||||
TicksBeforePathing = AverageTicksBeforePathing + self.World.SharedRandom.Next(-SpreadTicksBeforePathing, SpreadTicksBeforePathing);
|
||||
|
||||
self.QueueActivity(new Move(self, currentLocation, 8));
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, currentLocation), Color.Green);
|
||||
}
|
||||
|
||||
protected void PerformMove(Actor self, CPos targetLocation, bool queued)
|
||||
{
|
||||
if (queued)
|
||||
self.QueueActivity(new CallFunc(() => PerformMoveInner(self, targetLocation, true)));
|
||||
else
|
||||
PerformMoveInner(self, targetLocation, false);
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "Move")
|
||||
{
|
||||
if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(order.TargetLocation))
|
||||
return;
|
||||
|
||||
PerformMove(self, self.World.Map.Clamp(order.TargetLocation),
|
||||
order.Queued && !self.IsIdle);
|
||||
}
|
||||
|
||||
if (order.OrderString == "Stop")
|
||||
self.CancelActivity();
|
||||
|
||||
if (order.OrderString == "Scatter")
|
||||
Nudge(self, self, true);
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
switch (order.OrderString)
|
||||
{
|
||||
case "Move":
|
||||
case "Scatter":
|
||||
case "Stop":
|
||||
return "Move";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CPos TopLeft { get { return ToCell; } }
|
||||
|
||||
public IEnumerable<Pair<CPos, SubCell>> OccupiedCells()
|
||||
{
|
||||
if (FromCell == ToCell)
|
||||
return new[] { Pair.New(FromCell, FromSubCell) };
|
||||
if (CanEnterCell(ToCell))
|
||||
return new[] { Pair.New(ToCell, ToSubCell) };
|
||||
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
|
||||
}
|
||||
|
||||
public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
return ToCell != location && fromCell == location
|
||||
&& (subCell == SubCell.Any || FromSubCell == subCell || subCell == SubCell.FullCell || FromSubCell == SubCell.FullCell);
|
||||
}
|
||||
|
||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.None);
|
||||
}
|
||||
|
||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers);
|
||||
}
|
||||
|
||||
public void EnteringCell(Actor self)
|
||||
{
|
||||
var crushables = self.World.ActorMap.GetUnitsAt(ToCell).Where(a => a != self)
|
||||
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(b => b.CrushableBy(Info.Crushes, self.Owner)));
|
||||
foreach (var crushable in crushables)
|
||||
crushable.WarnCrush(self);
|
||||
}
|
||||
|
||||
public void FinishedMoving(Actor self)
|
||||
{
|
||||
var crushables = self.World.ActorMap.GetUnitsAt(ToCell).Where(a => a != self)
|
||||
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(c => c.CrushableBy(Info.Crushes, self.Owner)));
|
||||
foreach (var crushable in crushables)
|
||||
crushable.OnCrush(self);
|
||||
}
|
||||
|
||||
public int MovementSpeedForCell(Actor self, CPos cell)
|
||||
{
|
||||
var index = self.World.Map.GetTerrainIndex(cell);
|
||||
if (index == byte.MaxValue)
|
||||
return 0;
|
||||
|
||||
// TODO: Convert to integers
|
||||
var speed = Info.TilesetTerrainInfo[self.World.TileSet][index].Speed;
|
||||
if (speed == decimal.Zero)
|
||||
return 0;
|
||||
|
||||
speed *= Info.Speed;
|
||||
foreach (var t in self.TraitsImplementing<ISpeedModifier>())
|
||||
speed *= t.GetSpeedModifier() / 100m;
|
||||
|
||||
return (int)(speed / 100);
|
||||
}
|
||||
|
||||
public void AddInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.AddInfluence(self, this);
|
||||
}
|
||||
|
||||
public void RemoveInfluence()
|
||||
{
|
||||
if (self.IsInWorld)
|
||||
self.World.ActorMap.RemoveInfluence(self, this);
|
||||
}
|
||||
|
||||
public void Nudge(Actor self, Actor nudger, bool force)
|
||||
{
|
||||
/* initial fairly braindead implementation. */
|
||||
if (!force && self.Owner.Stances[nudger.Owner] != Stance.Ally)
|
||||
return; /* don't allow ourselves to be pushed around
|
||||
* by the enemy! */
|
||||
|
||||
if (!force && !self.IsIdle)
|
||||
return; /* don't nudge if we're busy doing something! */
|
||||
|
||||
// pick an adjacent available cell.
|
||||
var availCells = new List<CPos>();
|
||||
var notStupidCells = new List<CPos>();
|
||||
|
||||
for (var i = -1; i < 2; i++)
|
||||
for (var j = -1; j < 2; j++)
|
||||
{
|
||||
var p = ToCell + new CVec(i, j);
|
||||
if (CanEnterCell(p))
|
||||
availCells.Add(p);
|
||||
else
|
||||
if (p != nudger.Location && p != ToCell)
|
||||
notStupidCells.Add(p);
|
||||
}
|
||||
|
||||
var moveTo = availCells.Any() ? availCells.Random(self.World.SharedRandom) :
|
||||
notStupidCells.Any() ? notStupidCells.Random(self.World.SharedRandom) : (CPos?)null;
|
||||
|
||||
if (moveTo.HasValue)
|
||||
{
|
||||
self.CancelActivity();
|
||||
self.SetTargetLine(Target.FromCell(self.World, moveTo.Value), Color.Green, false);
|
||||
self.QueueActivity(new Move(self, moveTo.Value, 0));
|
||||
|
||||
Log.Write("debug", "OnNudge #{0} from {1} to {2}",
|
||||
self.ActorID, self.Location, moveTo.Value);
|
||||
}
|
||||
else
|
||||
Log.Write("debug", "OnNudge #{0} refuses at {1}",
|
||||
self.ActorID, self.Location);
|
||||
}
|
||||
|
||||
class MoveOrderTargeter : IOrderTargeter
|
||||
{
|
||||
readonly MobileInfo unitType;
|
||||
readonly bool rejectMove;
|
||||
|
||||
public MoveOrderTargeter(Actor self, MobileInfo unitType)
|
||||
{
|
||||
this.unitType = unitType;
|
||||
this.rejectMove = !self.AcceptsOrder("Move");
|
||||
}
|
||||
|
||||
public string OrderID { get { return "Move"; } }
|
||||
public int OrderPriority { get { return 4; } }
|
||||
public bool IsQueued { get; protected set; }
|
||||
|
||||
public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
if (rejectMove || !target.IsValidFor(self))
|
||||
return false;
|
||||
|
||||
var location = self.World.Map.CellContaining(target.CenterPosition);
|
||||
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
|
||||
|
||||
var explored = self.Owner.Shroud.IsExplored(location);
|
||||
cursor = self.World.Map.Contains(location) ?
|
||||
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? unitType.Cursor) : unitType.BlockedCursor;
|
||||
|
||||
if ((!explored && !unitType.MoveIntoShroud) || (explored && unitType.MovementCostForCell(self.World, location) == int.MaxValue))
|
||||
cursor = unitType.BlockedCursor;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Activity ScriptedMove(CPos cell) { return new Move(self, cell); }
|
||||
public Activity MoveTo(CPos cell, int nearEnough) { return new Move(self, cell, nearEnough); }
|
||||
public Activity MoveTo(CPos cell, Actor ignoredActor) { return new Move(self, cell, ignoredActor); }
|
||||
public Activity MoveWithinRange(Target target, WRange range) { return new MoveWithinRange(self, target, WRange.Zero, range); }
|
||||
public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return new MoveWithinRange(self, target, minRange, maxRange); }
|
||||
public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new Follow(self, target, minRange, maxRange); }
|
||||
public Activity MoveTo(Func<List<CPos>> pathFunc) { return new Move(self, pathFunc); }
|
||||
|
||||
public void OnNotifyBlockingMove(Actor self, Actor blocking)
|
||||
{
|
||||
if (self.IsIdle && self.AppearsFriendlyTo(blocking))
|
||||
Nudge(self, blocking, true);
|
||||
}
|
||||
|
||||
public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any)
|
||||
{
|
||||
var pos = self.CenterPosition;
|
||||
|
||||
if (subCell == SubCell.Any)
|
||||
subCell = self.World.ActorMap.FreeSubCell(cell, subCell);
|
||||
|
||||
// TODO: solve/reduce cell is full problem
|
||||
if (subCell == SubCell.Invalid)
|
||||
subCell = self.World.Map.DefaultSubCell;
|
||||
|
||||
// Reserve the exit cell
|
||||
SetPosition(self, cell, subCell);
|
||||
SetVisualPosition(self, pos);
|
||||
|
||||
return VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell);
|
||||
}
|
||||
|
||||
public Activity MoveToTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return new MoveAdjacentTo(self, target);
|
||||
}
|
||||
|
||||
public Activity MoveIntoTarget(Actor self, Target target)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return null;
|
||||
|
||||
return VisualMove(self, self.CenterPosition, target.CenterPosition);
|
||||
}
|
||||
|
||||
public bool CanEnterTargetNow(Actor self, Target target)
|
||||
{
|
||||
return self.Location == self.World.Map.CellContaining(target.CenterPosition) || Util.AdjacentCells(self.World, target).Any(c => c == self.Location);
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos)
|
||||
{
|
||||
return VisualMove(self, fromPos, toPos, self.Location);
|
||||
}
|
||||
|
||||
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos, CPos cell)
|
||||
{
|
||||
var speed = MovementSpeedForCell(self, cell);
|
||||
var length = speed > 0 ? (toPos - fromPos).Length / speed : 0;
|
||||
|
||||
var facing = Util.GetFacing(toPos - fromPos, Facing);
|
||||
return Util.SequenceActivities(new Turn(self, facing), new Drag(self, fromPos, toPos, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
195
OpenRA.Mods.Common/Traits/Passenger.cs
Normal file
195
OpenRA.Mods.Common/Traits/Passenger.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public enum AlternateTransportsMode { None, Force, Default, Always }
|
||||
|
||||
public class EnterTransportTargeter : EnterAlliedActorTargeter<Cargo>
|
||||
{
|
||||
readonly AlternateTransportsMode mode;
|
||||
|
||||
public EnterTransportTargeter(string order, int priority,
|
||||
Func<Actor, bool> canTarget, Func<Actor, bool> useEnterCursor,
|
||||
AlternateTransportsMode mode)
|
||||
: base(order, priority, canTarget, useEnterCursor) { this.mode = mode; }
|
||||
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AlternateTransportsMode.None:
|
||||
break;
|
||||
case AlternateTransportsMode.Force:
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
break;
|
||||
case AlternateTransportsMode.Default:
|
||||
if (!modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
break;
|
||||
case AlternateTransportsMode.Always:
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.CanTargetActor(self, target, modifiers, ref cursor);
|
||||
}
|
||||
}
|
||||
|
||||
public class EnterTransportsTargeter : EnterAlliedActorTargeter<Cargo>
|
||||
{
|
||||
readonly AlternateTransportsMode mode;
|
||||
|
||||
public EnterTransportsTargeter(string order, int priority,
|
||||
Func<Actor, bool> canTarget, Func<Actor, bool> useEnterCursor,
|
||||
AlternateTransportsMode mode)
|
||||
: base(order, priority, canTarget, useEnterCursor) { this.mode = mode; }
|
||||
|
||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AlternateTransportsMode.None:
|
||||
return false;
|
||||
case AlternateTransportsMode.Force:
|
||||
if (!modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
break;
|
||||
case AlternateTransportsMode.Default:
|
||||
if (modifiers.HasModifier(TargetModifiers.ForceMove))
|
||||
return false;
|
||||
break;
|
||||
case AlternateTransportsMode.Always:
|
||||
break;
|
||||
}
|
||||
|
||||
return base.CanTargetActor(self, target, modifiers, ref cursor);
|
||||
}
|
||||
}
|
||||
|
||||
[Desc("This actor can enter Cargo actors.")]
|
||||
public class PassengerInfo : ITraitInfo
|
||||
{
|
||||
public readonly string CargoType = null;
|
||||
public readonly PipType PipType = PipType.Green;
|
||||
public readonly int Weight = 1;
|
||||
|
||||
[Desc("Use to set when to use alternate transports (Never, Force, Default, Always).",
|
||||
"Force - use force move modifier (Alt) to enable.",
|
||||
"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 WRange AlternateTransportScanRange = WRange.FromCells(11) / 2;
|
||||
|
||||
[Desc("Upgrade types to grant to transport.")]
|
||||
public readonly string[] GrantUpgrades = { };
|
||||
|
||||
public object Create(ActorInitializer init) { return new Passenger(this); }
|
||||
}
|
||||
|
||||
public class Passenger : IIssueOrder, IResolveOrder, IOrderVoice, INotifyRemovedFromWorld
|
||||
{
|
||||
public readonly PassengerInfo Info;
|
||||
public Passenger(PassengerInfo info) { Info = info; }
|
||||
public Actor Transport;
|
||||
public Cargo ReservedCargo { get; private set; }
|
||||
|
||||
public IEnumerable<IOrderTargeter> Orders
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new EnterTransportTargeter("EnterTransport", 6,
|
||||
target => IsCorrectCargoType(target), target => CanEnter(target),
|
||||
Info.AlternateTransportsMode);
|
||||
yield return new EnterTransportsTargeter("EnterTransports", 6,
|
||||
target => IsCorrectCargoType(target), target => CanEnter(target),
|
||||
Info.AlternateTransportsMode);
|
||||
}
|
||||
}
|
||||
|
||||
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
|
||||
{
|
||||
if (order.OrderID == "EnterTransport" || order.OrderID == "EnterTransports")
|
||||
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool IsCorrectCargoType(Actor target)
|
||||
{
|
||||
var ci = target.Info.Traits.Get<CargoInfo>();
|
||||
return ci.Types.Contains(Info.CargoType);
|
||||
}
|
||||
|
||||
bool CanEnter(Cargo cargo)
|
||||
{
|
||||
return cargo != null && cargo.HasSpace(Info.Weight);
|
||||
}
|
||||
|
||||
bool CanEnter(Actor target)
|
||||
{
|
||||
return CanEnter(target.TraitOrDefault<Cargo>());
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
{
|
||||
if ((order.OrderString != "EnterTransport" && order.OrderString != "EnterTransports") ||
|
||||
!CanEnter(order.TargetActor)) return null;
|
||||
return "Move";
|
||||
}
|
||||
|
||||
public void ResolveOrder(Actor self, Order order)
|
||||
{
|
||||
if (order.OrderString == "EnterTransport" || order.OrderString == "EnterTransports")
|
||||
{
|
||||
if (order.TargetActor == null) return;
|
||||
if (!CanEnter(order.TargetActor)) return;
|
||||
if (!IsCorrectCargoType(order.TargetActor)) return;
|
||||
|
||||
var target = Target.FromOrder(self.World, order);
|
||||
self.SetTargetLine(target, Color.Green);
|
||||
|
||||
self.CancelActivity();
|
||||
var transports = order.OrderString == "EnterTransports";
|
||||
self.QueueActivity(new EnterTransport(self, order.TargetActor, transports ? Info.MaxAlternateTransportAttempts : 0, transports));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Reserve(Actor self, Cargo cargo)
|
||||
{
|
||||
Unreserve(self);
|
||||
if (!cargo.ReserveSpace(self))
|
||||
return false;
|
||||
ReservedCargo = cargo;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemovedFromWorld(Actor self) { Unreserve(self); }
|
||||
public void Unreserve(Actor self)
|
||||
{
|
||||
if (ReservedCargo == null)
|
||||
return;
|
||||
ReservedCargo.UnreserveSpace(self);
|
||||
ReservedCargo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
OpenRA.Mods.Common/Traits/Reloads.cs
Normal file
63
OpenRA.Mods.Common/Traits/Reloads.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Unit will reload its limited ammo itself.")]
|
||||
public class ReloadsInfo : ITraitInfo, Requires<LimitedAmmoInfo>
|
||||
{
|
||||
[Desc("How much ammo is reloaded after a certain period.")]
|
||||
public readonly int Count = 0;
|
||||
[Desc("How long it takes to do so.")]
|
||||
public readonly int Period = 50;
|
||||
[Desc("Whether or not reload counter should be reset when ammo has been fired.")]
|
||||
public readonly bool ResetOnFire = false;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Reloads(init.self, this); }
|
||||
}
|
||||
|
||||
public class Reloads : ITick
|
||||
{
|
||||
[Sync] int remainingTicks;
|
||||
ReloadsInfo info;
|
||||
LimitedAmmo la;
|
||||
int previousAmmo;
|
||||
|
||||
public Reloads(Actor self, ReloadsInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
remainingTicks = info.Period;
|
||||
la = self.Trait<LimitedAmmo>();
|
||||
previousAmmo = la.GetAmmoCount();
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (!la.FullAmmo() && --remainingTicks == 0)
|
||||
{
|
||||
remainingTicks = info.Period;
|
||||
|
||||
for (var i = 0; i < info.Count; i++)
|
||||
la.GiveAmmo();
|
||||
|
||||
previousAmmo = la.GetAmmoCount();
|
||||
}
|
||||
|
||||
// Resets the tick counter if ammo was fired.
|
||||
if (info.ResetOnFire && la.GetAmmoCount() < previousAmmo)
|
||||
{
|
||||
remainingTicks = info.Period;
|
||||
previousAmmo = la.GetAmmoCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
OpenRA.Mods.Common/Traits/Render/Hovers.cs
Normal file
51
OpenRA.Mods.Common/Traits/Render/Hovers.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Changes the visual Z position periodically.")]
|
||||
class HoversInfo : ITraitInfo, Requires<IMoveInfo>
|
||||
{
|
||||
[Desc("Amount of Z axis changes in world units.")]
|
||||
public readonly int OffsetModifier = -43;
|
||||
|
||||
public object Create(ActorInitializer init) { return new Hovers(this, init.self); }
|
||||
}
|
||||
|
||||
class Hovers : IRenderModifier
|
||||
{
|
||||
readonly HoversInfo info;
|
||||
readonly bool aircraft;
|
||||
|
||||
public Hovers(HoversInfo info, Actor self)
|
||||
{
|
||||
this.info = info;
|
||||
aircraft = self.HasTrait<Aircraft>();
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
|
||||
{
|
||||
if (self.World.Paused)
|
||||
return r;
|
||||
|
||||
var visualOffset = !aircraft || self.CenterPosition.Z > 0 ? (int)Math.Abs((self.ActorID + Game.LocalTick) / 5 % 4 - 1) - 1 : 0;
|
||||
var worldVisualOffset = new WVec(0, 0, info.OffsetModifier * visualOffset);
|
||||
|
||||
return r.Select(a => a.OffsetBy(worldVisualOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
39
OpenRA.Mods.Common/Traits/Render/RenderBuildingCharge.cs
Normal file
39
OpenRA.Mods.Common/Traits/Render/RenderBuildingCharge.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Used for tesla coil and obelisk.")]
|
||||
public class RenderBuildingChargeInfo : RenderBuildingInfo
|
||||
{
|
||||
[Desc("Sound to play when building charges.")]
|
||||
public readonly string ChargeAudio = null;
|
||||
[Desc("Sequence to use for building charge animation.")]
|
||||
public readonly string ChargeSequence = "active";
|
||||
public override object Create(ActorInitializer init) { return new RenderBuildingCharge(init, this); }
|
||||
}
|
||||
|
||||
public class RenderBuildingCharge : RenderBuilding
|
||||
{
|
||||
RenderBuildingChargeInfo info;
|
||||
|
||||
public RenderBuildingCharge(ActorInitializer init, RenderBuildingChargeInfo info)
|
||||
: base(init, info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void PlayCharge(Actor self)
|
||||
{
|
||||
Sound.Play(info.ChargeAudio, self.CenterPosition);
|
||||
PlayCustomAnim(self, info.ChargeSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
OpenRA.Mods.Common/Traits/Render/RenderBuildingTurreted.cs
Normal file
61
OpenRA.Mods.Common/Traits/Render/RenderBuildingTurreted.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
class RenderBuildingTurretedInfo : RenderBuildingInfo, Requires<TurretedInfo>
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new RenderBuildingTurreted(init, this); }
|
||||
|
||||
public override IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
|
||||
{
|
||||
var t = init.Actor.Traits.WithInterface<TurretedInfo>()
|
||||
.FirstOrDefault();
|
||||
|
||||
// Show the correct turret facing
|
||||
var anim = new Animation(init.World, image, () => t.InitialFacing);
|
||||
anim.PlayRepeating("idle");
|
||||
|
||||
yield return new SpriteActorPreview(anim, WVec.Zero, 0, p, rs.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderBuildingTurreted : RenderBuilding
|
||||
{
|
||||
Turreted t;
|
||||
|
||||
static Func<int> MakeTurretFacingFunc(Actor self)
|
||||
{
|
||||
// Turret artwork is baked into the sprite, so only the first turret makes sense.
|
||||
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
|
||||
return () => turreted.TurretFacing;
|
||||
}
|
||||
|
||||
public RenderBuildingTurreted(ActorInitializer init, RenderBuildingInfo info)
|
||||
: base(init, info, MakeTurretFacingFunc(init.self))
|
||||
{
|
||||
t = init.self.TraitsImplementing<Turreted>().FirstOrDefault();
|
||||
t.QuantizedFacings = DefaultAnimation.CurrentSequence.Facings;
|
||||
}
|
||||
|
||||
public override void DamageStateChanged(Actor self, AttackInfo e)
|
||||
{
|
||||
base.DamageStateChanged(self, e);
|
||||
t.QuantizedFacings = DefaultAnimation.CurrentSequence.Facings;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
OpenRA.Mods.Common/Traits/Render/WithBarrel.cs
Normal file
102
OpenRA.Mods.Common/Traits/Render/WithBarrel.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Renders barrels for units with the Turreted trait.")]
|
||||
class WithBarrelInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires<RenderSpritesInfo>, Requires<IBodyOrientationInfo>
|
||||
{
|
||||
[Desc("Sequence name to use")]
|
||||
public readonly string Sequence = "barrel";
|
||||
|
||||
[Desc("Armament to use for recoil")]
|
||||
public readonly string Armament = "primary";
|
||||
|
||||
[Desc("Turreted 'Barrel' key to display")]
|
||||
public readonly string Barrel = "first";
|
||||
|
||||
[Desc("Visual offset")]
|
||||
public readonly WVec LocalOffset = WVec.Zero;
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithBarrel(init.self, this); }
|
||||
|
||||
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
|
||||
{
|
||||
var body = init.Actor.Traits.Get<BodyOrientationInfo>();
|
||||
var armament = init.Actor.Traits.WithInterface<ArmamentInfo>()
|
||||
.First(a => a.Name == Armament);
|
||||
var t = init.Actor.Traits.WithInterface<TurretedInfo>()
|
||||
.First(tt => tt.Turret == armament.Turret);
|
||||
|
||||
var anim = new Animation(init.World, image, () => t.InitialFacing);
|
||||
anim.Play(Sequence);
|
||||
|
||||
var turretOrientation = body.QuantizeOrientation(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(t.InitialFacing)), facings);
|
||||
var turretOffset = body.LocalToWorld(t.Offset.Rotate(turretOrientation));
|
||||
|
||||
yield return new SpriteActorPreview(anim, turretOffset, turretOffset.Y + turretOffset.Z, p, rs.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
class WithBarrel
|
||||
{
|
||||
WithBarrelInfo info;
|
||||
Actor self;
|
||||
Armament armament;
|
||||
Turreted turreted;
|
||||
IBodyOrientation body;
|
||||
Animation anim;
|
||||
|
||||
public WithBarrel(Actor self, WithBarrelInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
body = self.Trait<IBodyOrientation>();
|
||||
armament = self.TraitsImplementing<Armament>()
|
||||
.First(a => a.Info.Name == info.Armament);
|
||||
turreted = self.TraitsImplementing<Turreted>()
|
||||
.First(tt => tt.Name == armament.Info.Turret);
|
||||
|
||||
var rs = self.Trait<RenderSprites>();
|
||||
anim = new Animation(self.World, rs.GetImage(self), () => turreted.TurretFacing);
|
||||
anim.Play(info.Sequence);
|
||||
rs.Add("barrel_{0}".F(info.Barrel), new AnimationWithOffset(
|
||||
anim, () => BarrelOffset(), null, () => false, p => WithTurret.ZOffsetFromCenter(self, p, 0)));
|
||||
|
||||
// Restrict turret facings to match the sprite
|
||||
turreted.QuantizedFacings = anim.CurrentSequence.Facings;
|
||||
}
|
||||
|
||||
WVec BarrelOffset()
|
||||
{
|
||||
var localOffset = info.LocalOffset + new WVec(-armament.Recoil, WRange.Zero, WRange.Zero);
|
||||
var turretOffset = turreted != null ? turreted.Position(self) : WVec.Zero;
|
||||
var turretOrientation = turreted != null ? turreted.LocalOrientation(self) : WRot.Zero;
|
||||
|
||||
var quantizedBody = body.QuantizeOrientation(self, self.Orientation);
|
||||
var quantizedTurret = body.QuantizeOrientation(self, turretOrientation);
|
||||
return turretOffset + body.LocalToWorld(localOffset.Rotate(quantizedTurret).Rotate(quantizedBody));
|
||||
}
|
||||
|
||||
IEnumerable<WRot> BarrelRotation()
|
||||
{
|
||||
var b = self.Orientation;
|
||||
var qb = body.QuantizeOrientation(self, b);
|
||||
yield return turreted.LocalOrientation(self) + WRot.FromYaw(b.Yaw - qb.Yaw);
|
||||
yield return qb;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs
Normal file
112
OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Renders the MuzzleSequence from the Armament trait.")]
|
||||
class WithMuzzleFlashInfo : ITraitInfo, Requires<RenderSpritesInfo>, Requires<AttackBaseInfo>, Requires<ArmamentInfo>
|
||||
{
|
||||
[Desc("Ignore the weapon position, and always draw relative to the center of the actor")]
|
||||
public readonly bool IgnoreOffset = false;
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithMuzzleFlash(init.self, this); }
|
||||
}
|
||||
|
||||
class WithMuzzleFlash : INotifyAttack, IRender, ITick
|
||||
{
|
||||
Dictionary<Barrel, bool> visible = new Dictionary<Barrel, bool>();
|
||||
Dictionary<Barrel, AnimationWithOffset> anims = new Dictionary<Barrel, AnimationWithOffset>();
|
||||
Func<int> getFacing;
|
||||
|
||||
public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info)
|
||||
{
|
||||
var render = self.Trait<RenderSprites>();
|
||||
var facing = self.TraitOrDefault<IFacing>();
|
||||
|
||||
foreach (var arm in self.TraitsImplementing<Armament>())
|
||||
{
|
||||
var armClosure = arm; // closure hazard in AnimationWithOffset
|
||||
|
||||
// Skip armaments that don't define muzzles
|
||||
if (arm.Info.MuzzleSequence == null)
|
||||
continue;
|
||||
|
||||
foreach (var b in arm.Barrels)
|
||||
{
|
||||
var barrel = b;
|
||||
var turreted = self.TraitsImplementing<Turreted>()
|
||||
.FirstOrDefault(t => t.Name == arm.Info.Turret);
|
||||
|
||||
// Workaround for broken ternary operators in certain versions of mono (3.10 and
|
||||
// certain versions of the 3.8 series): https://bugzilla.xamarin.com/show_bug.cgi?id=23319
|
||||
if (turreted != null)
|
||||
getFacing = () => turreted.TurretFacing;
|
||||
else if (facing != null)
|
||||
getFacing = (Func<int>)(() => facing.Facing);
|
||||
else
|
||||
getFacing = () => 0;
|
||||
|
||||
var muzzleFlash = new Animation(self.World, render.GetImage(self), getFacing);
|
||||
visible.Add(barrel, false);
|
||||
anims.Add(barrel,
|
||||
new AnimationWithOffset(muzzleFlash,
|
||||
() => info.IgnoreOffset ? WVec.Zero : armClosure.MuzzleOffset(self, barrel),
|
||||
() => !visible[barrel],
|
||||
() => false,
|
||||
p => WithTurret.ZOffsetFromCenter(self, p, 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
|
||||
{
|
||||
var sequence = a.Info.MuzzleSequence;
|
||||
if (sequence == null)
|
||||
return;
|
||||
|
||||
if (a.Info.MuzzleSplitFacings > 0)
|
||||
sequence += OpenRA.Traits.Util.QuantizeFacing(getFacing(), a.Info.MuzzleSplitFacings).ToString();
|
||||
|
||||
visible[barrel] = true;
|
||||
anims[barrel].Animation.PlayThen(sequence, () => visible[barrel] = false);
|
||||
}
|
||||
|
||||
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
|
||||
{
|
||||
foreach (var arm in self.TraitsImplementing<Armament>())
|
||||
{
|
||||
var palette = wr.Palette(arm.Info.MuzzlePalette);
|
||||
foreach (var kv in anims)
|
||||
{
|
||||
if (!visible[kv.Key])
|
||||
continue;
|
||||
|
||||
if (kv.Value.DisableFunc != null && kv.Value.DisableFunc())
|
||||
continue;
|
||||
|
||||
foreach (var r in kv.Value.Render(self, wr, palette, 1f))
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
foreach (var a in anims.Values)
|
||||
a.Animation.Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
109
OpenRA.Mods.Common/Traits/Render/WithTurret.cs
Normal file
109
OpenRA.Mods.Common/Traits/Render/WithTurret.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Renders turrets for units with the Turreted trait.")]
|
||||
public class WithTurretInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires<RenderSpritesInfo>, Requires<TurretedInfo>, Requires<IBodyOrientationInfo>
|
||||
{
|
||||
[Desc("Sequence name to use")]
|
||||
public readonly string Sequence = "turret";
|
||||
|
||||
[Desc("Sequence name to use when prepared to fire")]
|
||||
public readonly string AimSequence = null;
|
||||
|
||||
[Desc("Turreted 'Turret' key to display")]
|
||||
public readonly string Turret = "primary";
|
||||
|
||||
[Desc("Render recoil")]
|
||||
public readonly bool Recoils = true;
|
||||
|
||||
public object Create(ActorInitializer init) { return new WithTurret(init.self, this); }
|
||||
|
||||
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
|
||||
{
|
||||
var body = init.Actor.Traits.Get<BodyOrientationInfo>();
|
||||
var t = init.Actor.Traits.WithInterface<TurretedInfo>()
|
||||
.First(tt => tt.Turret == Turret);
|
||||
|
||||
var anim = new Animation(init.World, image, () => t.InitialFacing);
|
||||
anim.Play(Sequence);
|
||||
|
||||
var orientation = body.QuantizeOrientation(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(t.InitialFacing)), facings);
|
||||
var offset = body.LocalToWorld(t.Offset.Rotate(orientation));
|
||||
yield return new SpriteActorPreview(anim, offset, offset.Y + offset.Z + 1, p, rs.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
public class WithTurret : ITick
|
||||
{
|
||||
WithTurretInfo info;
|
||||
RenderSprites rs;
|
||||
IBodyOrientation body;
|
||||
AttackBase ab;
|
||||
Turreted t;
|
||||
IEnumerable<Armament> arms;
|
||||
Animation anim;
|
||||
|
||||
public WithTurret(Actor self, WithTurretInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
rs = self.Trait<RenderSprites>();
|
||||
body = self.Trait<IBodyOrientation>();
|
||||
|
||||
ab = self.TraitOrDefault<AttackBase>();
|
||||
t = self.TraitsImplementing<Turreted>()
|
||||
.First(tt => tt.Name == info.Turret);
|
||||
arms = self.TraitsImplementing<Armament>()
|
||||
.Where(w => w.Info.Turret == info.Turret);
|
||||
|
||||
anim = new Animation(self.World, rs.GetImage(self), () => t.TurretFacing);
|
||||
anim.Play(info.Sequence);
|
||||
rs.Add("turret_{0}".F(info.Turret), new AnimationWithOffset(
|
||||
anim, () => TurretOffset(self), null, () => false, p => ZOffsetFromCenter(self, p, 1)));
|
||||
|
||||
// Restrict turret facings to match the sprite
|
||||
t.QuantizedFacings = anim.CurrentSequence.Facings;
|
||||
}
|
||||
|
||||
WVec TurretOffset(Actor self)
|
||||
{
|
||||
if (!info.Recoils)
|
||||
return t.Position(self);
|
||||
|
||||
var recoil = arms.Aggregate(WRange.Zero, (a, b) => a + b.Recoil);
|
||||
var localOffset = new WVec(-recoil, WRange.Zero, WRange.Zero);
|
||||
var bodyOrientation = body.QuantizeOrientation(self, self.Orientation);
|
||||
var turretOrientation = body.QuantizeOrientation(self, t.LocalOrientation(self));
|
||||
return t.Position(self) + body.LocalToWorld(localOffset.Rotate(turretOrientation).Rotate(bodyOrientation));
|
||||
}
|
||||
|
||||
public void Tick(Actor self)
|
||||
{
|
||||
if (info.AimSequence == null)
|
||||
return;
|
||||
|
||||
var sequence = ab.IsAttacking ? info.AimSequence : info.Sequence;
|
||||
anim.ReplaceAnim(sequence);
|
||||
}
|
||||
|
||||
public static int ZOffsetFromCenter(Actor self, WPos pos, int offset)
|
||||
{
|
||||
var delta = self.CenterPosition - pos;
|
||||
return delta.Y + delta.Z + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
OpenRA.Mods.Common/Traits/Turreted.cs
Normal file
117
OpenRA.Mods.Common/Traits/Turreted.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class TurretedInfo : ITraitInfo, UsesInit<TurretFacingInit>
|
||||
{
|
||||
public readonly string Turret = "primary";
|
||||
[Desc("Rate of Turning")]
|
||||
public readonly int ROT = 255;
|
||||
public readonly int InitialFacing = 128;
|
||||
|
||||
[Desc("Number of ticks before turret is realigned. (-1 turns off realignment)")]
|
||||
public readonly int RealignDelay = 40;
|
||||
|
||||
[Desc("Muzzle position relative to turret or body. (forward, right, up) triples")]
|
||||
public readonly WVec Offset = WVec.Zero;
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Turreted(init, this); }
|
||||
}
|
||||
|
||||
public class Turreted : ITick, ISync, INotifyCreated
|
||||
{
|
||||
readonly TurretedInfo info;
|
||||
AttackTurreted attack;
|
||||
IFacing facing;
|
||||
|
||||
[Sync] public int QuantizedFacings = 0;
|
||||
[Sync] public int TurretFacing = 0;
|
||||
public int? DesiredFacing;
|
||||
int realignTick = 0;
|
||||
|
||||
// For subclasses that want to move the turret relative to the body
|
||||
protected WVec localOffset = WVec.Zero;
|
||||
|
||||
public WVec Offset { get { return info.Offset + localOffset; } }
|
||||
public string Name { get { return info.Turret; } }
|
||||
|
||||
public static int GetInitialTurretFacing(ActorInitializer init, int def)
|
||||
{
|
||||
if (init.Contains<TurretFacingInit>())
|
||||
return init.Get<TurretFacingInit, int>();
|
||||
|
||||
if (init.Contains<FacingInit>())
|
||||
return init.Get<FacingInit, int>();
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
public Turreted(ActorInitializer init, TurretedInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
TurretFacing = GetInitialTurretFacing(init, info.InitialFacing);
|
||||
}
|
||||
|
||||
public void Created(Actor self)
|
||||
{
|
||||
attack = self.TraitOrDefault<AttackTurreted>();
|
||||
facing = self.TraitOrDefault<IFacing>();
|
||||
}
|
||||
|
||||
public virtual void Tick(Actor self)
|
||||
{
|
||||
if (attack != null && !attack.IsAttacking)
|
||||
{
|
||||
if (realignTick < info.RealignDelay)
|
||||
realignTick++;
|
||||
else if (info.RealignDelay > -1)
|
||||
DesiredFacing = null;
|
||||
}
|
||||
else
|
||||
realignTick = 0;
|
||||
|
||||
var df = DesiredFacing ?? (facing != null ? facing.Facing : TurretFacing);
|
||||
TurretFacing = Util.TickFacing(TurretFacing, df, info.ROT);
|
||||
}
|
||||
|
||||
public bool FaceTarget(Actor self, Target target)
|
||||
{
|
||||
DesiredFacing = Util.GetFacing(target.CenterPosition - self.CenterPosition, TurretFacing);
|
||||
return TurretFacing == DesiredFacing;
|
||||
}
|
||||
|
||||
// Turret offset in world-space
|
||||
public WVec Position(Actor self)
|
||||
{
|
||||
var body = self.Trait<IBodyOrientation>();
|
||||
var bodyOrientation = body.QuantizeOrientation(self, self.Orientation);
|
||||
return body.LocalToWorld(Offset.Rotate(bodyOrientation));
|
||||
}
|
||||
|
||||
// Orientation in unit-space
|
||||
public WRot LocalOrientation(Actor self)
|
||||
{
|
||||
// Hack: turretFacing is relative to the world, so subtract the body yaw
|
||||
var local = WRot.FromYaw(WAngle.FromFacing(TurretFacing) - self.Orientation.Yaw);
|
||||
|
||||
if (QuantizedFacings == 0)
|
||||
return local;
|
||||
|
||||
// Quantize orientation to match a rendered sprite
|
||||
// Implies no pitch or yaw
|
||||
var facing = Util.QuantizeFacing(local.Yaw.Angle / 4, QuantizedFacings) * (256 / QuantizedFacings);
|
||||
return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing));
|
||||
}
|
||||
}
|
||||
}
|
||||
89
OpenRA.Mods.Common/Traits/Wanders.cs
Normal file
89
OpenRA.Mods.Common/Traits/Wanders.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Wanders around aimlessly while idle.")]
|
||||
abstract class WandersInfo : ITraitInfo
|
||||
{
|
||||
public readonly int WanderMoveRadius = 10;
|
||||
|
||||
[Desc("Number of ticks to wait before decreasing the effective move radius.")]
|
||||
public readonly int TicksToWaitBeforeReducingMoveRadius = 5;
|
||||
|
||||
[Desc("Mimimum ammount of ticks the actor will sit idly before starting to wander.")]
|
||||
public readonly int MinMoveDelayInTicks = 0;
|
||||
|
||||
[Desc("Maximum ammount of ticks the actor will sit idly before starting to wander.")]
|
||||
public readonly int MaxMoveDelayInTicks = 0;
|
||||
|
||||
public abstract object Create(ActorInitializer init);
|
||||
}
|
||||
|
||||
class Wanders : INotifyIdle, INotifyBecomingIdle
|
||||
{
|
||||
readonly Actor self;
|
||||
readonly WandersInfo info;
|
||||
|
||||
int countdown;
|
||||
int ticksIdle;
|
||||
int effectiveMoveRadius;
|
||||
|
||||
public Wanders(Actor self, WandersInfo info)
|
||||
{
|
||||
this.self = self;
|
||||
this.info = info;
|
||||
effectiveMoveRadius = info.WanderMoveRadius;
|
||||
}
|
||||
|
||||
public void OnBecomingIdle(Actor self)
|
||||
{
|
||||
countdown = self.World.SharedRandom.Next(info.MinMoveDelayInTicks, info.MaxMoveDelayInTicks);
|
||||
}
|
||||
|
||||
public void TickIdle(Actor self)
|
||||
{
|
||||
if (--countdown > 0)
|
||||
return;
|
||||
|
||||
var targetPos = PickTargetLocation();
|
||||
if (targetPos != CPos.Zero)
|
||||
DoAction(self, targetPos);
|
||||
}
|
||||
|
||||
CPos PickTargetLocation()
|
||||
{
|
||||
var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255)));
|
||||
var targetCell = self.World.Map.CellContaining(target);
|
||||
|
||||
if (!self.World.Map.Contains(targetCell))
|
||||
{
|
||||
// If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave)
|
||||
if (++ticksIdle % info.TicksToWaitBeforeReducingMoveRadius == 0)
|
||||
effectiveMoveRadius--;
|
||||
|
||||
return CPos.Zero; // We'll be back the next tick; better to sit idle for a few seconds than prolong this tick indefinitely with a loop
|
||||
}
|
||||
|
||||
ticksIdle = 0;
|
||||
effectiveMoveRadius = info.WanderMoveRadius;
|
||||
|
||||
return targetCell;
|
||||
}
|
||||
|
||||
public virtual void DoAction(Actor self, CPos targetPos)
|
||||
{
|
||||
throw new NotImplementedException("Base class Wanders does not implement method DoAction!");
|
||||
}
|
||||
}
|
||||
}
|
||||
224
OpenRA.Mods.Common/Traits/World/DomainIndex.cs
Normal file
224
OpenRA.Mods.Common/Traits/World/DomainIndex.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Identify untraversable regions of the map for faster pathfinding, especially with AI.",
|
||||
"This trait is required. Every mod needs it attached to the world actor.")]
|
||||
class DomainIndexInfo : TraitInfo<DomainIndex> { }
|
||||
|
||||
public class DomainIndex : IWorldLoaded
|
||||
{
|
||||
Dictionary<uint, MovementClassDomainIndex> domainIndexes;
|
||||
|
||||
public void WorldLoaded(World world, WorldRenderer wr)
|
||||
{
|
||||
domainIndexes = new Dictionary<uint, MovementClassDomainIndex>();
|
||||
var movementClasses = new HashSet<uint>(
|
||||
world.Map.Rules.Actors.Where(ai => ai.Value.Traits.Contains<MobileInfo>())
|
||||
.Select(ai => (uint)ai.Value.Traits.Get<MobileInfo>().GetMovementClass(world.TileSet)));
|
||||
|
||||
foreach (var mc in movementClasses)
|
||||
domainIndexes[mc] = new MovementClassDomainIndex(world, mc);
|
||||
}
|
||||
|
||||
public bool IsPassable(CPos p1, CPos p2, uint movementClass)
|
||||
{
|
||||
return domainIndexes[movementClass].IsPassable(p1, p2);
|
||||
}
|
||||
|
||||
/// Regenerate the domain index for a group of cells
|
||||
public void UpdateCells(World world, IEnumerable<CPos> cells)
|
||||
{
|
||||
var dirty = new HashSet<CPos>(cells);
|
||||
foreach (var index in domainIndexes)
|
||||
index.Value.UpdateCells(world, dirty);
|
||||
}
|
||||
}
|
||||
|
||||
class MovementClassDomainIndex
|
||||
{
|
||||
Map map;
|
||||
|
||||
uint movementClass;
|
||||
CellLayer<int> domains;
|
||||
Dictionary<int, HashSet<int>> transientConnections;
|
||||
|
||||
public MovementClassDomainIndex(World world, uint movementClass)
|
||||
{
|
||||
map = world.Map;
|
||||
this.movementClass = movementClass;
|
||||
domains = new CellLayer<int>(world.Map);
|
||||
transientConnections = new Dictionary<int, HashSet<int>>();
|
||||
|
||||
using (new PerfTimer("BuildDomains: {0}".F(world.Map.Title)))
|
||||
BuildDomains(world);
|
||||
}
|
||||
|
||||
public bool IsPassable(CPos p1, CPos p2)
|
||||
{
|
||||
if (!map.Contains(p1) || !map.Contains(p2))
|
||||
return false;
|
||||
|
||||
if (domains[p1] == domains[p2])
|
||||
return true;
|
||||
|
||||
// Even though p1 and p2 are in different domains, it's possible
|
||||
// that some dynamic terrain (i.e. bridges) may connect them.
|
||||
return HasConnection(domains[p1], domains[p2]);
|
||||
}
|
||||
|
||||
public void UpdateCells(World world, HashSet<CPos> dirtyCells)
|
||||
{
|
||||
var neighborDomains = new List<int>();
|
||||
|
||||
foreach (var cell in dirtyCells)
|
||||
{
|
||||
// Select all neighbors inside the map boundries
|
||||
var thisCell = cell; // benign closure hazard
|
||||
var neighbors = CVec.Directions.Select(d => d + thisCell)
|
||||
.Where(c => map.Contains(c));
|
||||
|
||||
var found = false;
|
||||
foreach (var n in neighbors)
|
||||
{
|
||||
if (!dirtyCells.Contains(n))
|
||||
{
|
||||
var neighborDomain = domains[n];
|
||||
if (CanTraverseTile(world, n))
|
||||
neighborDomains.Add(neighborDomain);
|
||||
|
||||
// Set ourselves to the first non-dirty neighbor we find.
|
||||
if (!found)
|
||||
{
|
||||
domains[cell] = neighborDomain;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var c1 in neighborDomains)
|
||||
foreach (var c2 in neighborDomains)
|
||||
CreateConnection(c1, c2);
|
||||
}
|
||||
|
||||
bool HasConnection(int d1, int d2)
|
||||
{
|
||||
// Search our connections graph for a possible route
|
||||
var visited = new HashSet<int>();
|
||||
var toProcess = new Stack<int>();
|
||||
toProcess.Push(d1);
|
||||
|
||||
while (toProcess.Any())
|
||||
{
|
||||
var current = toProcess.Pop();
|
||||
if (!transientConnections.ContainsKey(current))
|
||||
continue;
|
||||
|
||||
foreach (var neighbor in transientConnections[current])
|
||||
{
|
||||
if (neighbor == d2)
|
||||
return true;
|
||||
|
||||
if (!visited.Contains(neighbor))
|
||||
toProcess.Push(neighbor);
|
||||
}
|
||||
|
||||
visited.Add(current);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CreateConnection(int d1, int d2)
|
||||
{
|
||||
if (!transientConnections.ContainsKey(d1))
|
||||
transientConnections[d1] = new HashSet<int>();
|
||||
if (!transientConnections.ContainsKey(d2))
|
||||
transientConnections[d2] = new HashSet<int>();
|
||||
|
||||
transientConnections[d1].Add(d2);
|
||||
transientConnections[d2].Add(d1);
|
||||
}
|
||||
|
||||
bool CanTraverseTile(World world, CPos p)
|
||||
{
|
||||
var terrainOffset = world.Map.GetTerrainIndex(p);
|
||||
return (movementClass & (1 << terrainOffset)) > 0;
|
||||
}
|
||||
|
||||
void BuildDomains(World world)
|
||||
{
|
||||
var map = world.Map;
|
||||
|
||||
var domain = 1;
|
||||
|
||||
var visited = new CellLayer<bool>(map);
|
||||
|
||||
var toProcess = new Queue<CPos>();
|
||||
toProcess.Enqueue(new CPos(map.Bounds.Left, map.Bounds.Top));
|
||||
|
||||
// Flood-fill over each domain
|
||||
while (toProcess.Count != 0)
|
||||
{
|
||||
var start = toProcess.Dequeue();
|
||||
|
||||
// Technically redundant with the check in the inner loop, but prevents
|
||||
// ballooning the domain counter.
|
||||
if (visited[start])
|
||||
continue;
|
||||
|
||||
var domainQueue = new Queue<CPos>();
|
||||
domainQueue.Enqueue(start);
|
||||
|
||||
var currentPassable = CanTraverseTile(world, start);
|
||||
|
||||
// Add all contiguous cells to our domain, and make a note of
|
||||
// any non-contiguous cells for future domains
|
||||
while (domainQueue.Count != 0)
|
||||
{
|
||||
var n = domainQueue.Dequeue();
|
||||
if (visited[n])
|
||||
continue;
|
||||
|
||||
var candidatePassable = CanTraverseTile(world, n);
|
||||
if (candidatePassable != currentPassable)
|
||||
{
|
||||
toProcess.Enqueue(n);
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[n] = true;
|
||||
domains[n] = domain;
|
||||
|
||||
// Don't crawl off the map, or add already-visited cells
|
||||
var neighbors = CVec.Directions.Select(d => n + d)
|
||||
.Where(p => map.Contains(p) && !visited[p]);
|
||||
|
||||
foreach (var neighbor in neighbors)
|
||||
domainQueue.Enqueue(neighbor);
|
||||
}
|
||||
|
||||
domain += 1;
|
||||
}
|
||||
|
||||
Log.Write("debug", "Found {0} domains on map {1}.", domain - 1, map.Title);
|
||||
}
|
||||
}
|
||||
}
|
||||
306
OpenRA.Mods.Common/Traits/World/PathFinder.cs
Normal file
306
OpenRA.Mods.Common/Traits/World/PathFinder.cs
Normal file
@@ -0,0 +1,306 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenRA;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Calculates routes for mobile units based on the A* search algorithm.", " Attach this to the world actor.")]
|
||||
public class PathFinderInfo : ITraitInfo
|
||||
{
|
||||
public object Create(ActorInitializer init) { return new PathFinder(init.world); }
|
||||
}
|
||||
|
||||
public class PathFinder
|
||||
{
|
||||
const int MaxPathAge = 50; /* x 40ms ticks */
|
||||
static readonly List<CPos> EmptyPath = new List<CPos>(0);
|
||||
|
||||
readonly World world;
|
||||
public PathFinder(World world) { this.world = world; }
|
||||
|
||||
class CachedPath
|
||||
{
|
||||
public CPos From;
|
||||
public CPos To;
|
||||
public List<CPos> Result;
|
||||
public int Tick;
|
||||
public Actor Actor;
|
||||
}
|
||||
|
||||
List<CachedPath> cachedPaths = new List<CachedPath>();
|
||||
|
||||
public List<CPos> FindUnitPath(CPos from, CPos target, Actor self)
|
||||
{
|
||||
using (new PerfSample("Pathfinder"))
|
||||
{
|
||||
var cached = cachedPaths.FirstOrDefault(p => p.From == from && p.To == target && p.Actor == self);
|
||||
if (cached != null)
|
||||
{
|
||||
Log.Write("debug", "Actor {0} asked for a path from {1} tick(s) ago", self.ActorID, world.WorldTick - cached.Tick);
|
||||
if (world.WorldTick - cached.Tick > MaxPathAge)
|
||||
cachedPaths.Remove(cached);
|
||||
return new List<CPos>(cached.Result);
|
||||
}
|
||||
|
||||
var mi = self.Info.Traits.Get<MobileInfo>();
|
||||
|
||||
// If a water-land transition is required, bail early
|
||||
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
|
||||
if (domainIndex != null)
|
||||
{
|
||||
var passable = mi.GetMovementClass(world.TileSet);
|
||||
if (!domainIndex.IsPassable(from, target, (uint)passable))
|
||||
return EmptyPath;
|
||||
}
|
||||
|
||||
var fromPoint = PathSearch.FromPoint(world, mi, self, target, from, true)
|
||||
.WithIgnoredActor(self);
|
||||
var fromPointReverse = PathSearch.FromPoint(world, mi, self, from, target, true)
|
||||
.WithIgnoredActor(self)
|
||||
.Reverse();
|
||||
var pb = FindBidiPath(
|
||||
fromPoint,
|
||||
fromPointReverse);
|
||||
|
||||
CheckSanePath2(pb, from, target);
|
||||
|
||||
cachedPaths.RemoveAll(p => world.WorldTick - p.Tick > MaxPathAge);
|
||||
cachedPaths.Add(new CachedPath { From = from, To = target, Actor = self, Result = pb, Tick = world.WorldTick });
|
||||
return new List<CPos>(pb);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CPos> FindUnitPathToRange(CPos src, SubCell srcSub, WPos target, WRange range, Actor self)
|
||||
{
|
||||
using (new PerfSample("Pathfinder"))
|
||||
{
|
||||
var mi = self.Info.Traits.Get<MobileInfo>();
|
||||
var targetCell = self.World.Map.CellContaining(target);
|
||||
var rangeSquared = range.Range * range.Range;
|
||||
|
||||
// Correct for SubCell offset
|
||||
target -= self.World.Map.OffsetOfSubCell(srcSub);
|
||||
|
||||
// Select only the tiles that are within range from the requested SubCell
|
||||
// This assumes that the SubCell does not change during the path traversal
|
||||
var tilesInRange = world.Map.FindTilesInCircle(targetCell, range.Range / 1024 + 1)
|
||||
.Where(t => (world.Map.CenterOfCell(t) - target).LengthSquared <= rangeSquared
|
||||
&& mi.CanEnterCell(self.World, self, t));
|
||||
|
||||
// See if there is any cell within range that does not involve a cross-domain request
|
||||
// Really, we only need to check the circle perimeter, but it's not clear that would be a performance win
|
||||
var domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
|
||||
if (domainIndex != null)
|
||||
{
|
||||
var passable = mi.GetMovementClass(world.TileSet);
|
||||
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(src, t, (uint)passable)));
|
||||
if (!tilesInRange.Any())
|
||||
return EmptyPath;
|
||||
}
|
||||
|
||||
var path = FindBidiPath(
|
||||
PathSearch.FromPoints(world, mi, self, tilesInRange, src, true),
|
||||
PathSearch.FromPoint(world, mi, self, src, targetCell, true).Reverse());
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public List<CPos> FindPath(PathSearch search)
|
||||
{
|
||||
using (new PerfSample("Pathfinder"))
|
||||
{
|
||||
using (search)
|
||||
{
|
||||
List<CPos> path = null;
|
||||
|
||||
while (!search.Queue.Empty)
|
||||
{
|
||||
var p = search.Expand(world);
|
||||
if (search.Heuristic(p) == 0)
|
||||
{
|
||||
path = MakePath(search.CellInfo, p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
|
||||
if (dbg != null)
|
||||
dbg.AddLayer(search.Considered.Select(p => new Pair<CPos, int>(p, search.CellInfo[p].MinCost)), search.MaxCost, search.Owner);
|
||||
|
||||
if (path != null)
|
||||
return path;
|
||||
}
|
||||
|
||||
// no path exists
|
||||
return EmptyPath;
|
||||
}
|
||||
}
|
||||
|
||||
static List<CPos> MakePath(CellLayer<CellInfo> cellInfo, CPos destination)
|
||||
{
|
||||
var ret = new List<CPos>();
|
||||
var pathNode = destination;
|
||||
|
||||
while (cellInfo[pathNode].Path != pathNode)
|
||||
{
|
||||
ret.Add(pathNode);
|
||||
pathNode = cellInfo[pathNode].Path;
|
||||
}
|
||||
|
||||
ret.Add(pathNode);
|
||||
CheckSanePath(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Searches from both ends toward each other
|
||||
public List<CPos> FindBidiPath(PathSearch fromSrc, PathSearch fromDest)
|
||||
{
|
||||
using (new PerfSample("Pathfinder"))
|
||||
{
|
||||
using (fromSrc)
|
||||
using (fromDest)
|
||||
{
|
||||
List<CPos> path = null;
|
||||
|
||||
while (!fromSrc.Queue.Empty && !fromDest.Queue.Empty)
|
||||
{
|
||||
/* make some progress on the first search */
|
||||
var p = fromSrc.Expand(world);
|
||||
|
||||
if (fromDest.CellInfo[p].Seen &&
|
||||
fromDest.CellInfo[p].MinCost < float.PositiveInfinity)
|
||||
{
|
||||
path = MakeBidiPath(fromSrc, fromDest, p);
|
||||
break;
|
||||
}
|
||||
|
||||
/* make some progress on the second search */
|
||||
var q = fromDest.Expand(world);
|
||||
|
||||
if (fromSrc.CellInfo[q].Seen &&
|
||||
fromSrc.CellInfo[q].MinCost < float.PositiveInfinity)
|
||||
{
|
||||
path = MakeBidiPath(fromSrc, fromDest, q);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
|
||||
if (dbg != null)
|
||||
{
|
||||
dbg.AddLayer(fromSrc.Considered.Select(p => new Pair<CPos, int>(p, fromSrc.CellInfo[p].MinCost)), fromSrc.MaxCost, fromSrc.Owner);
|
||||
dbg.AddLayer(fromDest.Considered.Select(p => new Pair<CPos, int>(p, fromDest.CellInfo[p].MinCost)), fromDest.MaxCost, fromDest.Owner);
|
||||
}
|
||||
|
||||
if (path != null)
|
||||
return path;
|
||||
}
|
||||
|
||||
return EmptyPath;
|
||||
}
|
||||
}
|
||||
|
||||
static List<CPos> MakeBidiPath(PathSearch a, PathSearch b, CPos p)
|
||||
{
|
||||
var ca = a.CellInfo;
|
||||
var cb = b.CellInfo;
|
||||
|
||||
var ret = new List<CPos>();
|
||||
|
||||
var q = p;
|
||||
while (ca[q].Path != q)
|
||||
{
|
||||
ret.Add(q);
|
||||
q = ca[q].Path;
|
||||
}
|
||||
|
||||
ret.Add(q);
|
||||
|
||||
ret.Reverse();
|
||||
|
||||
q = p;
|
||||
while (cb[q].Path != q)
|
||||
{
|
||||
q = cb[q].Path;
|
||||
ret.Add(q);
|
||||
}
|
||||
|
||||
CheckSanePath(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
[Conditional("SANITY_CHECKS")]
|
||||
static void CheckSanePath(List<CPos> path)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
return;
|
||||
var prev = path[0];
|
||||
for (var i = 0; i < path.Count; i++)
|
||||
{
|
||||
var d = path[i] - prev;
|
||||
if (Math.Abs(d.X) > 1 || Math.Abs(d.Y) > 1)
|
||||
throw new InvalidOperationException("(PathFinder) path sanity check failed");
|
||||
prev = path[i];
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("SANITY_CHECKS")]
|
||||
static void CheckSanePath2(List<CPos> path, CPos src, CPos dest)
|
||||
{
|
||||
if (path.Count == 0)
|
||||
return;
|
||||
|
||||
if (path[0] != dest)
|
||||
throw new InvalidOperationException("(PathFinder) sanity check failed: doesn't go to dest");
|
||||
if (path[path.Count - 1] != src)
|
||||
throw new InvalidOperationException("(PathFinder) sanity check failed: doesn't come from src");
|
||||
}
|
||||
}
|
||||
|
||||
public struct CellInfo
|
||||
{
|
||||
public int MinCost;
|
||||
public CPos Path;
|
||||
public bool Seen;
|
||||
|
||||
public CellInfo(int minCost, CPos path, bool seen)
|
||||
{
|
||||
MinCost = minCost;
|
||||
Path = path;
|
||||
Seen = seen;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PathDistance : IComparable<PathDistance>
|
||||
{
|
||||
public readonly int EstTotal;
|
||||
public readonly CPos Location;
|
||||
|
||||
public PathDistance(int estTotal, CPos location)
|
||||
{
|
||||
EstTotal = estTotal;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public int CompareTo(PathDistance other)
|
||||
{
|
||||
return Math.Sign(EstTotal - other.EstTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
362
OpenRA.Mods.Common/Traits/World/PathSearch.cs
Normal file
362
OpenRA.Mods.Common/Traits/World/PathSearch.cs
Normal file
@@ -0,0 +1,362 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenRA;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public sealed class PathSearch : IDisposable
|
||||
{
|
||||
public CellLayer<CellInfo> CellInfo;
|
||||
public PriorityQueue<PathDistance> Queue;
|
||||
public Func<CPos, int> Heuristic;
|
||||
public bool CheckForBlocked;
|
||||
public Actor IgnoredActor;
|
||||
public bool InReverse;
|
||||
public HashSet<CPos> Considered;
|
||||
public Player Owner { get { return self.Owner; } }
|
||||
public int MaxCost;
|
||||
|
||||
Actor self;
|
||||
MobileInfo mobileInfo;
|
||||
Func<CPos, int> customCost;
|
||||
Func<CPos, bool> customBlock;
|
||||
int laneBias = 1;
|
||||
|
||||
public PathSearch(World world, MobileInfo mobileInfo, Actor self)
|
||||
{
|
||||
this.self = self;
|
||||
CellInfo = InitCellInfo();
|
||||
this.mobileInfo = mobileInfo;
|
||||
this.self = self;
|
||||
customCost = null;
|
||||
Queue = new PriorityQueue<PathDistance>();
|
||||
Considered = new HashSet<CPos>();
|
||||
MaxCost = 0;
|
||||
}
|
||||
|
||||
public static PathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked)
|
||||
{
|
||||
var search = new PathSearch(world, mi, self)
|
||||
{
|
||||
CheckForBlocked = checkForBlocked
|
||||
};
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
public static PathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
|
||||
{
|
||||
var search = new PathSearch(world, mi, self)
|
||||
{
|
||||
Heuristic = DefaultEstimator(target),
|
||||
CheckForBlocked = checkForBlocked
|
||||
};
|
||||
|
||||
search.AddInitialCell(from);
|
||||
return search;
|
||||
}
|
||||
|
||||
public static PathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
|
||||
{
|
||||
var search = new PathSearch(world, mi, self)
|
||||
{
|
||||
Heuristic = DefaultEstimator(target),
|
||||
CheckForBlocked = checkForBlocked
|
||||
};
|
||||
|
||||
foreach (var sl in froms)
|
||||
search.AddInitialCell(sl);
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
public static Func<CPos, int> DefaultEstimator(CPos destination)
|
||||
{
|
||||
return here =>
|
||||
{
|
||||
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
|
||||
var straight = Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y);
|
||||
|
||||
// HACK: this relies on fp and cell-size assumptions.
|
||||
var h = (3400 * diag / 24) + 100 * (straight - (2 * diag));
|
||||
return (int)(h * 1.001);
|
||||
};
|
||||
}
|
||||
|
||||
public PathSearch Reverse()
|
||||
{
|
||||
InReverse = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithCustomBlocker(Func<CPos, bool> customBlock)
|
||||
{
|
||||
this.customBlock = customBlock;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithIgnoredActor(Actor b)
|
||||
{
|
||||
IgnoredActor = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithHeuristic(Func<CPos, int> h)
|
||||
{
|
||||
Heuristic = h;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithCustomCost(Func<CPos, int> w)
|
||||
{
|
||||
customCost = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithoutLaneBias()
|
||||
{
|
||||
laneBias = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch FromPoint(CPos from)
|
||||
{
|
||||
AddInitialCell(from);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Sets of neighbors for each incoming direction. These exclude the neighbors which are guaranteed
|
||||
// to be reached more cheaply by a path through our parent cell which does not include the current cell.
|
||||
// For horizontal/vertical directions, the set is the three cells 'ahead'. For diagonal directions, the set
|
||||
// is the three cells ahead, plus the two cells to the side, which we cannot exclude without knowing if
|
||||
// the cell directly between them and our parent is passable.
|
||||
static CVec[][] directedNeighbors = {
|
||||
new CVec[] { new CVec(-1, -1), new CVec(0, -1), new CVec(1, -1), new CVec(-1, 0), new CVec(-1, 1) },
|
||||
new CVec[] { new CVec(-1, -1), new CVec(0, -1), new CVec(1, -1) },
|
||||
new CVec[] { new CVec(-1, -1), new CVec(0, -1), new CVec(1, -1), new CVec(1, 0), new CVec(1, 1) },
|
||||
new CVec[] { new CVec(-1, -1), new CVec(-1, 0), new CVec(-1, 1) },
|
||||
CVec.Directions,
|
||||
new CVec[] { new CVec(1, -1), new CVec(1, 0), new CVec(1, 1) },
|
||||
new CVec[] { new CVec(-1, -1), new CVec(-1, 0), new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) },
|
||||
new CVec[] { new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) },
|
||||
new CVec[] { new CVec(1, -1), new CVec(1, 0), new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) },
|
||||
};
|
||||
|
||||
static CVec[] GetNeighbors(CPos p, CPos prev)
|
||||
{
|
||||
var dx = p.X - prev.X;
|
||||
var dy = p.Y - prev.Y;
|
||||
var index = dy * 3 + dx + 4;
|
||||
|
||||
return directedNeighbors[index];
|
||||
}
|
||||
|
||||
public CPos Expand(World world)
|
||||
{
|
||||
var p = Queue.Pop();
|
||||
while (CellInfo[p.Location].Seen)
|
||||
{
|
||||
if (Queue.Empty)
|
||||
return p.Location;
|
||||
|
||||
p = Queue.Pop();
|
||||
}
|
||||
|
||||
var pCell = CellInfo[p.Location];
|
||||
pCell.Seen = true;
|
||||
CellInfo[p.Location] = pCell;
|
||||
|
||||
var thisCost = mobileInfo.MovementCostForCell(world, p.Location);
|
||||
|
||||
if (thisCost == int.MaxValue)
|
||||
return p.Location;
|
||||
|
||||
if (customCost != null)
|
||||
{
|
||||
var c = customCost(p.Location);
|
||||
if (c == int.MaxValue)
|
||||
return p.Location;
|
||||
}
|
||||
|
||||
// This current cell is ok; check useful immediate directions:
|
||||
Considered.Add(p.Location);
|
||||
|
||||
var directions = GetNeighbors(p.Location, pCell.Path);
|
||||
|
||||
for (var i = 0; i < directions.Length; ++i)
|
||||
{
|
||||
var d = directions[i];
|
||||
|
||||
var newHere = p.Location + d;
|
||||
|
||||
// Is this direction flat-out unusable or already seen?
|
||||
if (!world.Map.Contains(newHere))
|
||||
continue;
|
||||
|
||||
if (CellInfo[newHere].Seen)
|
||||
continue;
|
||||
|
||||
// Now we may seriously consider this direction using heuristics:
|
||||
var costHere = mobileInfo.MovementCostForCell(world, newHere);
|
||||
|
||||
if (costHere == int.MaxValue)
|
||||
continue;
|
||||
|
||||
if (!mobileInfo.CanEnterCell(world, self, newHere, IgnoredActor, CheckForBlocked ? CellConditions.TransientActors : CellConditions.None))
|
||||
continue;
|
||||
|
||||
if (customBlock != null && customBlock(newHere))
|
||||
continue;
|
||||
|
||||
var est = Heuristic(newHere);
|
||||
if (est == int.MaxValue)
|
||||
continue;
|
||||
|
||||
var cellCost = costHere;
|
||||
if (d.X * d.Y != 0)
|
||||
cellCost = (cellCost * 34) / 24;
|
||||
|
||||
var userCost = 0;
|
||||
if (customCost != null)
|
||||
{
|
||||
userCost = customCost(newHere);
|
||||
cellCost += userCost;
|
||||
}
|
||||
|
||||
// directional bonuses for smoother flow!
|
||||
if (laneBias != 0)
|
||||
{
|
||||
var ux = newHere.X + (InReverse ? 1 : 0) & 1;
|
||||
var uy = newHere.Y + (InReverse ? 1 : 0) & 1;
|
||||
|
||||
if (ux == 0 && d.Y < 0)
|
||||
cellCost += laneBias;
|
||||
else if (ux == 1 && d.Y > 0)
|
||||
cellCost += laneBias;
|
||||
|
||||
if (uy == 0 && d.X < 0)
|
||||
cellCost += laneBias;
|
||||
else if (uy == 1 && d.X > 0)
|
||||
cellCost += laneBias;
|
||||
}
|
||||
|
||||
var newCost = CellInfo[p.Location].MinCost + cellCost;
|
||||
|
||||
// Cost is even higher; next direction:
|
||||
if (newCost > CellInfo[newHere].MinCost)
|
||||
continue;
|
||||
|
||||
var hereCell = CellInfo[newHere];
|
||||
hereCell.Path = p.Location;
|
||||
hereCell.MinCost = newCost;
|
||||
CellInfo[newHere] = hereCell;
|
||||
|
||||
Queue.Add(new PathDistance(newCost + est, newHere));
|
||||
|
||||
if (newCost > MaxCost)
|
||||
MaxCost = newCost;
|
||||
|
||||
Considered.Add(newHere);
|
||||
}
|
||||
|
||||
return p.Location;
|
||||
}
|
||||
|
||||
public void AddInitialCell(CPos location)
|
||||
{
|
||||
if (!self.World.Map.Contains(location))
|
||||
return;
|
||||
|
||||
CellInfo[location] = new CellInfo(0, location, false);
|
||||
Queue.Add(new PathDistance(Heuristic(location), location));
|
||||
}
|
||||
|
||||
static readonly Queue<CellLayer<CellInfo>> CellInfoPool = new Queue<CellLayer<CellInfo>>();
|
||||
static readonly object DefaultCellInfoLayerSync = new object();
|
||||
static CellLayer<CellInfo> defaultCellInfoLayer;
|
||||
|
||||
static CellLayer<CellInfo> GetFromPool()
|
||||
{
|
||||
lock (CellInfoPool)
|
||||
return CellInfoPool.Dequeue();
|
||||
}
|
||||
|
||||
static void PutBackIntoPool(CellLayer<CellInfo> ci)
|
||||
{
|
||||
lock (CellInfoPool)
|
||||
CellInfoPool.Enqueue(ci);
|
||||
}
|
||||
|
||||
CellLayer<CellInfo> InitCellInfo()
|
||||
{
|
||||
CellLayer<CellInfo> result = null;
|
||||
var map = self.World.Map;
|
||||
var mapSize = new Size(map.MapSize.X, map.MapSize.Y);
|
||||
|
||||
// HACK: Uses a static cache so that double-ended searches (which have two PathSearch instances)
|
||||
// can implicitly share data. The PathFinder should allocate the CellInfo array and pass it
|
||||
// explicitly to the things that need to share it.
|
||||
while (CellInfoPool.Count > 0)
|
||||
{
|
||||
var cellInfo = GetFromPool();
|
||||
if (cellInfo.Size != mapSize || cellInfo.Shape != map.TileShape)
|
||||
{
|
||||
Log.Write("debug", "Discarding old pooled CellInfo of wrong size.");
|
||||
continue;
|
||||
}
|
||||
|
||||
result = cellInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
result = new CellLayer<CellInfo>(map);
|
||||
|
||||
lock (DefaultCellInfoLayerSync)
|
||||
{
|
||||
if (defaultCellInfoLayer == null ||
|
||||
defaultCellInfoLayer.Size != mapSize ||
|
||||
defaultCellInfoLayer.Shape != map.TileShape)
|
||||
{
|
||||
defaultCellInfoLayer = new CellLayer<CellInfo>(map);
|
||||
for (var v = 0; v < mapSize.Height; v++)
|
||||
for (var u = 0; u < mapSize.Width; u++)
|
||||
defaultCellInfoLayer[u, v] = new CellInfo(int.MaxValue, Map.MapToCell(map.TileShape, new CPos(u, v)), false);
|
||||
}
|
||||
|
||||
result.CopyValuesFrom(defaultCellInfoLayer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
disposed = true;
|
||||
|
||||
PutBackIntoPool(CellInfo);
|
||||
CellInfo = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~PathSearch() { Dispose(); }
|
||||
}
|
||||
}
|
||||
88
OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs
Normal file
88
OpenRA.Mods.Common/Traits/World/PathfinderDebugOverlay.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenRA;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Required for the A* PathDebug from DeveloperMode. Attach this to the world actor.")]
|
||||
public class PathfinderDebugOverlayInfo : TraitInfo<PathfinderDebugOverlay> { }
|
||||
public class PathfinderDebugOverlay : IRenderOverlay, IWorldLoaded
|
||||
{
|
||||
Dictionary<Player, CellLayer<int>> layers;
|
||||
int refreshTick;
|
||||
World world;
|
||||
public bool Visible;
|
||||
|
||||
public void WorldLoaded(World w, WorldRenderer wr)
|
||||
{
|
||||
world = w;
|
||||
refreshTick = 0;
|
||||
layers = new Dictionary<Player, CellLayer<int>>(8);
|
||||
|
||||
// Enabled via Cheats menu
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
public void AddLayer(IEnumerable<Pair<CPos, int>> cellWeights, int maxWeight, Player pl)
|
||||
{
|
||||
if (maxWeight == 0) return;
|
||||
|
||||
CellLayer<int> layer;
|
||||
if (!layers.TryGetValue(pl, out layer))
|
||||
{
|
||||
layer = new CellLayer<int>(world.Map);
|
||||
layers.Add(pl, layer);
|
||||
}
|
||||
|
||||
foreach (var p in cellWeights)
|
||||
layer[p.First] = Math.Min(128, layer[p.First] + (maxWeight - p.Second) * 64 / maxWeight);
|
||||
}
|
||||
|
||||
public void Render(WorldRenderer wr)
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
var qr = Game.Renderer.WorldQuadRenderer;
|
||||
var doDim = refreshTick - world.WorldTick <= 0;
|
||||
if (doDim) refreshTick = world.WorldTick + 20;
|
||||
|
||||
foreach (var pair in layers)
|
||||
{
|
||||
var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise;
|
||||
var layer = pair.Value;
|
||||
|
||||
// Only render quads in viewing range:
|
||||
foreach (var cell in wr.Viewport.VisibleCells)
|
||||
{
|
||||
if (layer[cell] <= 0)
|
||||
continue;
|
||||
|
||||
var w = Math.Max(0, Math.Min(layer[cell], 128));
|
||||
if (doDim)
|
||||
layer[cell] = layer[cell] * 5 / 6;
|
||||
|
||||
// TODO: This doesn't make sense for isometric terrain
|
||||
var pos = wr.world.Map.CenterOfCell(cell);
|
||||
var tl = wr.ScreenPxPosition(pos - new WVec(512, 512, 0));
|
||||
var br = wr.ScreenPxPosition(pos + new WVec(511, 511, 0));
|
||||
qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
IEnumerable<IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition);
|
||||
}
|
||||
|
||||
public interface INotifyAttack { void Attacking(Actor self, Target target, Armament a, Barrel barrel); }
|
||||
public interface INotifyChat { bool OnChat(string from, string message); }
|
||||
public interface INotifyParachuteLanded { void OnLanded(); }
|
||||
public interface IRenderActorPreviewInfo { IEnumerable<IActorPreview> RenderPreview(ActorPreviewInitializer init); }
|
||||
|
||||
Reference in New Issue
Block a user