Added attack move

This commit is contained in:
max621
2010-10-07 00:24:47 -05:00
committed by Chris Forbes
parent 566303a8f1
commit c150fd9475
16 changed files with 228 additions and 58 deletions

View File

@@ -41,6 +41,9 @@ namespace OpenRA.GameRules
if (!Voices.ContainsKey("Attack")) if (!Voices.ContainsKey("Attack"))
Voices.Add("Attack", Voices["Move"]); Voices.Add("Attack", Voices["Move"]);
if (!Voices.ContainsKey("AttackMove"))
Voices.Add("AttackMove", Voices["Move"]);
Pools = Lazy.New(() => Voices.ToDictionary( a => a.Key, a => new VoicePool(a.Value) )); Pools = Lazy.New(() => Voices.ToDictionary( a => a.Key, a => new VoicePool(a.Value) ));
} }

View File

@@ -11,18 +11,26 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Orders namespace OpenRA.Orders
{ {
public class GenericSelectTarget : IOrderGenerator public class GenericSelectTarget : IOrderGenerator
{ {
readonly Actor subject; readonly IEnumerable<Actor> subjects;
readonly string order; readonly string order;
readonly string cursor; readonly string cursor;
public GenericSelectTarget(IEnumerable<Actor> subjects, string order, string cursor)
{
this.subjects = subjects;
this.order = order;
this.cursor = cursor;
}
public GenericSelectTarget(Actor subject, string order, string cursor) public GenericSelectTarget(Actor subject, string order, string cursor)
{ {
this.subject = subject; this.subjects = new Actor[] { subject };
this.order = order; this.order = order;
this.cursor = cursor; this.cursor = cursor;
} }
@@ -36,16 +44,35 @@ namespace OpenRA.Orders
IEnumerable<Order> OrderInner(World world, int2 xy, MouseInput mi) IEnumerable<Order> OrderInner(World world, int2 xy, MouseInput mi)
{ {
if( mi.Button == MouseButton.Left && world.Map.IsInMap( xy ) ) if (mi.Button == MouseButton.Left && world.Map.IsInMap(xy))
{ {
world.CancelInputMode(); world.CancelInputMode();
yield return new Order( order, subject, xy ); foreach (var subject in subjects)
yield return new Order(order, subject, xy);
} }
} }
public virtual void Tick(World world) { } public virtual void Tick(World world) { }
public void RenderAfterWorld(WorldRenderer wr, World world) { }
public void RenderBeforeWorld(WorldRenderer wr, World world) { } public void RenderBeforeWorld(WorldRenderer wr, World world)
{
foreach (var a in world.Selection.Actors)
if (!a.Destroyed)
foreach (var t in a.TraitsImplementing<IPreRenderSelection>())
t.RenderBeforeWorld(wr, a);
Game.Renderer.Flush();
}
public void RenderAfterWorld(WorldRenderer wr, World world)
{
foreach (var a in world.Selection.Actors)
if (!a.Destroyed)
foreach (var t in a.TraitsImplementing<IPostRenderSelection>())
t.RenderAfterWorld(wr, a);
Game.Renderer.Flush();
}
public string GetCursor(World world, int2 xy, MouseInput mi) { return world.Map.IsInMap(xy) ? cursor : "generic-blocked"; } public string GetCursor(World world, int2 xy, MouseInput mi) { return world.Map.IsInMap(xy) ? cursor : "generic-blocked"; }
} }
@@ -67,5 +94,4 @@ namespace OpenRA.Orders
world.CancelInputMode(); world.CancelInputMode();
} }
} }
} }

View File

@@ -179,8 +179,9 @@ namespace OpenRA.Traits
return; return;
if( !order.Queued ) self.CancelActivity(); if( !order.Queued ) self.CancelActivity();
self.QueueActivity(new Activities.Move(currentLocation, 8)); self.QueueActivity(new Activities.Move(currentLocation, 8));
if (self.Owner == self.World.LocalPlayer) if (self.Owner == self.World.LocalPlayer)
self.World.AddFrameEndTask(w => self.World.AddFrameEndTask(w =>
{ {

View File

@@ -46,6 +46,7 @@ namespace OpenRA.Widgets
} }
float2 dragStart, dragEnd; float2 dragStart, dragEnd;
public override bool HandleInputInner(MouseInput mi) public override bool HandleInputInner(MouseInput mi)
{ {
var xy = Game.viewport.ViewToWorld(mi); var xy = Game.viewport.ViewToWorld(mi);
@@ -139,8 +140,20 @@ namespace OpenRA.Widgets
GotoNextBase(); GotoNextBase();
return true; return true;
} }
if (e.KeyChar == 'a')
{
StartAttackMoveOrder();
return true;
}
} }
return false; return false;
}
public void StartAttackMoveOrder()
{
if (world.Selection.Actors.Count() > 0)
world.OrderGenerator = new GenericSelectTarget(world.Selection.Actors, "AttackMove", "attackmove");
} }
public void GotoNextBase() public void GotoNextBase()

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenRA.Traits;
using OpenRA.Traits.Activities;
namespace OpenRA.Mods.RA.Activities
{
public class AttackMove : Move
{
public AttackMove(int2 destination) : base(destination) { }
public AttackMove(int2 destination, int nearEnough) : base(destination, nearEnough) { }
public AttackMove(int2 destination, Actor ignoreBuilding) : base(destination, ignoreBuilding) { }
public AttackMove(Actor target, int range) : base(target, range) { }
public AttackMove(Target target, int range) : base(target, range) { }
public AttackMove(Func<List<int2>> getPath) : base(getPath) { }
}
}

View File

@@ -36,11 +36,17 @@ namespace OpenRA.Mods.RA
public readonly bool AlignIdleTurrets = false; public readonly bool AlignIdleTurrets = false;
public readonly bool CanAttackGround = true; public readonly bool CanAttackGround = true;
public readonly float ScanTimeAverage = 2f;
public readonly float ScanTimeSpread = .5f;
public virtual object Create(ActorInitializer init) { return new AttackBase(init.self); } public virtual object Create(ActorInitializer init) { return new AttackBase(init.self); }
} }
public class AttackBase : IIssueOrder, IResolveOrder, ITick, IExplodeModifier, IOrderVoice public class AttackBase : IIssueOrder, IResolveOrder, ITick, IExplodeModifier, IOrderVoice
{ {
[Sync]
int nextScanTime = 0;
public bool IsAttacking { get; internal set; } public bool IsAttacking { get; internal set; }
public Target target; public Target target;
@@ -228,6 +234,7 @@ namespace OpenRA.Mods.RA
if (self.HasTrait<Turreted>() && self.Info.Traits.Get<AttackBaseInfo>().AlignIdleTurrets) if (self.HasTrait<Turreted>() && self.Info.Traits.Get<AttackBaseInfo>().AlignIdleTurrets)
self.Trait<Turreted>().desiredFacing = null; self.Trait<Turreted>().desiredFacing = null;
} }
} }
public string VoicePhraseForOrder(Actor self, Order order) public string VoicePhraseForOrder(Actor self, Order order)
@@ -251,6 +258,48 @@ namespace OpenRA.Mods.RA
public Weapon ChooseWeaponForTarget(Target t) { return Weapons.FirstOrDefault(w => w.IsValidAgainst(self.World, t)); } public Weapon ChooseWeaponForTarget(Target t) { return Weapons.FirstOrDefault(w => w.IsValidAgainst(self.World, t)); }
public void AttackTarget(Actor self, Actor target, bool allowMovement)
{
var attack = self.Trait<AttackBase>();
if (target != null)
{
if (allowMovement)
attack.ResolveOrder(self, new Order("Attack", self, target));
else
attack.target = Target.FromActor(target); // for turreted things on rails.
}
}
public void ScanAndAttack(Actor self, bool allowMovement)
{
if (--nextScanTime <= 0)
{
var attack = self.Trait<AttackBase>();
var range = attack.GetMaximumRange();
if (!attack.target.IsValid ||
(Util.CellContaining(attack.target.CenterLocation) - self.Location).LengthSquared > range * range)
AttackTarget(self, ChooseTarget(self, range), allowMovement);
var info = self.Info.Traits.Get<AttackBaseInfo>();
nextScanTime = (int)(25 * (info.ScanTimeAverage +
(self.World.SharedRandom.NextDouble() * 2 - 1) * info.ScanTimeSpread));
}
}
Actor ChooseTarget(Actor self, float range)
{
var inRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range);
var attack = self.Trait<AttackBase>();
return inRange
.Where(a => a.Owner != null && self.Owner.Stances[a.Owner] == Stance.Enemy)
.Where(a => attack.HasAnyValidWeapons(Target.FromActor(a)))
.Where(a => !a.HasTrait<Cloak>() || a.Trait<Cloak>().IsVisible(a, self.Owner))
.OrderBy(a => (a.Location - self.Location).LengthSquared)
.FirstOrDefault();
}
class AttackOrderTargeter : IOrderTargeter class AttackOrderTargeter : IOrderTargeter
{ {
readonly bool isHeal; readonly bool isHeal;
@@ -292,6 +341,6 @@ namespace OpenRA.Mods.RA
return false; return false;
} }
} }
} }
} }

View File

@@ -0,0 +1,74 @@
using System;
using System.Drawing;
using OpenRA.Traits;
using OpenRA.Effects;
namespace OpenRA.Mods.RA
{
class AttackMoveInfo : TraitInfo<AttackMove>
{
//public object Create(ActorInitializer init) { return new AttackMove(init.self); }
public readonly bool JustMove = false;
}
class AttackMove : ITick, IResolveOrder, IOrderVoice
{
public bool AttackMoving { get; set; }
public string VoicePhraseForOrder(Actor self, Order order)
{
if (order.OrderString == "AttackMove")
{
return "AttackMove";
}
return null;
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "AttackMove")
{
self.CancelActivity();
//if we are just moving, we don't turn on attackmove and this becomes a regular move order
if (!self.Info.Traits.Get<AttackMoveInfo>().JustMove)
{
AttackMoving = true;
}
Order newOrder = new Order("Move", order.Subject, order.TargetLocation);
self.Trait<Mobile>().ResolveOrder(self, newOrder);
if (self.Owner == self.World.LocalPlayer)
self.World.AddFrameEndTask(w =>
{
if (order.TargetActor != null)
w.Add(new FlashTarget(order.TargetActor));
var line = self.TraitOrDefault<DrawLineToTarget>();
if (line != null)
if (order.TargetActor != null) line.SetTarget(self, Target.FromOrder(order), Color.Red);
else line.SetTarget(self, Target.FromOrder(order), Color.Red);
});
if (self.Owner == self.Owner.World.LocalPlayer)
self.World.CancelInputMode();
}
else
{
AttackMoving = false; //cancel attack move state for other orders
}
}
public void Tick(Actor self)
{
if (self.Info.Traits.Get<AttackMoveInfo>().JustMove) return;
if (!self.HasTrait<AttackBase>())
{
Game.Debug("AttackMove: {0} has no AttackBase trait".F(self.ToString()));
return;
}
if (!self.IsIdle && (self.HasTrait<AttackMove>() && !(self.Trait<AttackMove>().AttackMoving))) return;
self.Trait<AttackBase>().ScanAndAttack(self, true);
}
}
}

View File

@@ -8,65 +8,22 @@
*/ */
#endregion #endregion
using System.Linq;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.RA namespace OpenRA.Mods.RA
{ {
class AutoTargetInfo : TraitInfo<AutoTarget> class AutoTargetInfo : TraitInfo<AutoTarget>
{ {
public readonly float ScanTimeAverage = 2f;
public readonly float ScanTimeSpread = .5f;
public readonly bool AllowMovement = true; public readonly bool AllowMovement = true;
} }
class AutoTarget : ITick, INotifyDamage class AutoTarget : ITick, INotifyDamage
{ {
[Sync]
int nextScanTime = 0;
void AttackTarget(Actor self, Actor target)
{
var attack = self.Trait<AttackBase>();
if (target != null)
{
if (self.Info.Traits.Get<AutoTargetInfo>().AllowMovement)
attack.ResolveOrder(self, new Order("Attack", self, target));
else
attack.target = Target.FromActor(target); // for turreted things on rails.
}
}
public void Tick(Actor self) public void Tick(Actor self)
{ {
if (!self.IsIdle && self.Info.Traits.Get<AutoTargetInfo>().AllowMovement) return; if (!self.IsIdle && self.Info.Traits.Get<AutoTargetInfo>().AllowMovement) return;
if (--nextScanTime <= 0) self.Trait<AttackBase>().ScanAndAttack(self, self.Info.Traits.Get<AutoTargetInfo>().AllowMovement);
{
var attack = self.Trait<AttackBase>();
var range = attack.GetMaximumRange();
if (!attack.target.IsValid ||
(Util.CellContaining(attack.target.CenterLocation) - self.Location).LengthSquared > range * range)
AttackTarget(self, ChooseTarget(self, range));
var info = self.Info.Traits.Get<AutoTargetInfo>();
nextScanTime = (int)(25 * (info.ScanTimeAverage +
(self.World.SharedRandom.NextDouble() * 2 - 1) * info.ScanTimeSpread));
}
}
Actor ChooseTarget(Actor self, float range)
{
var inRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range);
var attack = self.Trait<AttackBase>();
return inRange
.Where(a => a.Owner != null && self.Owner.Stances[ a.Owner ] == Stance.Enemy)
.Where(a => attack.HasAnyValidWeapons(Target.FromActor(a)))
.Where(a => !a.HasTrait<Cloak>() || a.Trait<Cloak>().IsVisible(a,self.Owner))
.OrderBy(a => (a.Location - self.Location).LengthSquared)
.FirstOrDefault();
} }
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)
@@ -83,7 +40,7 @@ namespace OpenRA.Mods.RA
if (e.Damage < 0) return; // don't retaliate against healers if (e.Damage < 0) return; // don't retaliate against healers
AttackTarget(self, e.Attacker); self.Trait<AttackBase>().AttackTarget(self, e.Attacker, self.Info.Traits.Get<AutoTargetInfo>().AllowMovement);
} }
} }
} }

View File

@@ -77,6 +77,7 @@
<Compile Include="Activities\UnloadCargo.cs" /> <Compile Include="Activities\UnloadCargo.cs" />
<Compile Include="Activities\Wait.cs" /> <Compile Include="Activities\Wait.cs" />
<Compile Include="AttackBase.cs" /> <Compile Include="AttackBase.cs" />
<Compile Include="AttackMoveTrait.cs" />
<Compile Include="Buildable.cs" /> <Compile Include="Buildable.cs" />
<Compile Include="Combat.cs" /> <Compile Include="Combat.cs" />
<Compile Include="Crates\CloakCrateAction.cs" /> <Compile Include="Crates\CloakCrateAction.cs" />

View File

@@ -26,6 +26,7 @@
DrawLineToTarget: DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
Notification: unitlost.aud Notification: unitlost.aud
AttackMove:
^Tank: ^Tank:
AppearsOnRadar: AppearsOnRadar:
@@ -55,6 +56,7 @@
DrawLineToTarget: DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
Notification: unitlost.aud Notification: unitlost.aud
AttackMove:
^Helicopter: ^Helicopter:
AppearsOnRadar: AppearsOnRadar:
@@ -100,6 +102,7 @@
Queue: Infantry Queue: Infantry
RenderInfantry: RenderInfantry:
AutoTarget: AutoTarget:
AttackMove:
Passenger: Passenger:
CargoType: Infantry CargoType: Infantry
HiddenUnderFog: HiddenUnderFog:
@@ -164,6 +167,7 @@
DrawLineToTarget: DrawLineToTarget:
ActorLostNotification: ActorLostNotification:
Notification: unitlost.aud Notification: unitlost.aud
AttackMove:
^Building: ^Building:
AppearsOnRadar: AppearsOnRadar:

View File

@@ -153,6 +153,8 @@ E6:
EngineerRepair: EngineerRepair:
EngineerCapture: EngineerCapture:
-AutoTarget: -AutoTarget:
AttackMove:
JustMove: true
IdleAnimation: IdleAnimation:
Animations: idle1,idle2 Animations: idle1,idle2

View File

@@ -28,6 +28,7 @@ MCV:
NoTransformSounds: deploy1.aud NoTransformSounds: deploy1.aud
RenderUnit: RenderUnit:
MustBeDestroyed: MustBeDestroyed:
-AttackMove:
HARV: HARV:
Inherits: ^Tank Inherits: ^Tank
@@ -58,6 +59,7 @@ HARV:
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
RenderUnit: RenderUnit:
-AttackMove:
APC: APC:
Inherits: ^Tank Inherits: ^Tank
@@ -650,6 +652,8 @@ LST:
Cargo: Cargo:
Types: Infantry, Vehicle Types: Infantry, Vehicle
Passengers: 5 Passengers: 5
AttackMove:
JustMove: true
LTNK.Husk: LTNK.Husk:
Inherits: ^Husk Inherits: ^Husk

View File

@@ -18,6 +18,7 @@
Passenger: Passenger:
CargoType: Vehicle CargoType: Vehicle
IronCurtainable: IronCurtainable:
AttackMove:
HiddenUnderFog: HiddenUnderFog:
GainsExperience: GainsExperience:
GivesExperience: GivesExperience:
@@ -44,7 +45,8 @@
Chronoshiftable: Chronoshiftable:
Passenger: Passenger:
CargoType: Vehicle CargoType: Vehicle
IronCurtainable: IronCurtainable:
AttackMove:
HiddenUnderFog: HiddenUnderFog:
GainsExperience: GainsExperience:
GivesExperience: GivesExperience:
@@ -73,6 +75,7 @@
TargetTypes: Ground TargetTypes: Ground
RenderInfantry: RenderInfantry:
AutoTarget: AutoTarget:
AttackMove:
Passenger: Passenger:
CargoType: Infantry CargoType: Infantry
HiddenUnderFog: HiddenUnderFog:
@@ -96,6 +99,7 @@
DetectCloaked: DetectCloaked:
Range: 3 Range: 3
HiddenUnderFog: HiddenUnderFog:
AttackMove:
GainsExperience: GainsExperience:
GivesExperience: GivesExperience:
DrawLineToTarget: DrawLineToTarget:

View File

@@ -158,6 +158,8 @@ E6:
EngineerCapture: EngineerCapture:
TakeCover: TakeCover:
-AutoTarget: -AutoTarget:
AttackMove:
JustMove: true
IdleAnimation: IdleAnimation:
Animations: idle1,idle2 Animations: idle1,idle2
@@ -190,6 +192,8 @@ SPY:
Spy: Spy:
-RenderInfantry: -RenderInfantry:
-AutoTarget: -AutoTarget:
AttackMove:
JustMove: true
IdleAnimation: IdleAnimation:
Animations: idle1,idle2 Animations: idle1,idle2
@@ -253,6 +257,8 @@ MEDI:
PrimaryWeapon: Heal PrimaryWeapon: Heal
TakeCover: TakeCover:
-AutoTarget: -AutoTarget:
AttackMove:
JustMove: true
IdleAnimation: IdleAnimation:
Animations: idle1,idle2 Animations: idle1,idle2

View File

@@ -39,7 +39,6 @@ GAP:
# Prerequisites: atek # Prerequisites: atek
# Owner: allies # Owner: allies
# Cost: 500 # Cost: 500
# Hotkey: g
Building: Building:
Power: -60 Power: -60
Footprint: _ x Footprint: _ x
@@ -268,7 +267,7 @@ AGUN:
BuildPaletteOrder: 50 BuildPaletteOrder: 50
Prerequisites: dome Prerequisites: dome
Owner: allies Owner: allies
Hotkey: a Hotkey: g
Valued: Valued:
Cost: 600 Cost: 600
Tooltip: Tooltip:
@@ -679,7 +678,7 @@ AFLD:
BuildPaletteOrder: 50 BuildPaletteOrder: 50
Prerequisites: dome Prerequisites: dome
Owner: soviet Owner: soviet
Hotkey: a Hotkey: g
Valued: Valued:
Cost: 1100 Cost: 1100
Tooltip: Tooltip:

View File

@@ -270,6 +270,7 @@ HARV:
RevealsShroud: RevealsShroud:
Range: 4 Range: 4
RenderUnit: RenderUnit:
-AttackMove:
MCV: MCV:
Inherits: ^Vehicle Inherits: ^Vehicle
@@ -303,6 +304,7 @@ MCV:
RenderUnit: RenderUnit:
MustBeDestroyed: MustBeDestroyed:
BaseBuilding: BaseBuilding:
-AttackMove:
JEEP: JEEP:
Inherits: ^Vehicle Inherits: ^Vehicle
@@ -394,6 +396,8 @@ MNLY.AP:
MineImmune: MineImmune:
LimitedAmmo: LimitedAmmo:
Ammo: 5 Ammo: 5
AttackMove:
JustMove: true
MNLY.AT: MNLY.AT:
Inherits: ^Tank Inherits: ^Tank
@@ -423,6 +427,8 @@ MNLY.AT:
MineImmune: MineImmune:
LimitedAmmo: LimitedAmmo:
Ammo: 5 Ammo: 5
AttackMove:
JustMove: true
TRUK: TRUK:
Inherits: ^Vehicle Inherits: ^Vehicle
@@ -625,6 +631,8 @@ LST:
Passengers: 5 Passengers: 5
IronCurtainable: IronCurtainable:
RepairableNear: RepairableNear:
AttackMove:
JustMove: true
PT: PT:
Inherits: ^Ship Inherits: ^Ship