Merge pull request #2770 from pchote/turret-cleanup

Weapon / Turret refactoring
This commit is contained in:
Matthias Mailänder
2013-03-26 14:38:56 -07:00
81 changed files with 4023 additions and 820 deletions

View File

@@ -200,9 +200,10 @@ namespace OpenRA.Mods.RA.AI
return RelativeValue(own, enemy, 100, SumOfValues<AttackBase>, (Actor a) =>
{
int sumOfDamage = 0;
foreach (var weap in a.Trait<AttackBase>().Weapons)
if (weap.Info.Warheads[0] != null)
sumOfDamage += weap.Info.Warheads[0].Damage;
var arms = a.TraitsImplementing<Armament>();
foreach (var arm in arms)
if (arm.Weapon.Warheads[0] != null)
sumOfDamage += arm.Weapon.Warheads[0].Damage;
return sumOfDamage;
});
}

View File

@@ -230,12 +230,13 @@ namespace OpenRA.Mods.RA.AI
if (!target.HasTrait<TargetableUnit<TargetableUnitInfo>>() &&
!target.HasTrait<TargetableBuilding>()) return false;
foreach (var weap in a.Trait<AttackBase>().Weapons)
var arms = a.TraitsImplementing<Armament>();
foreach (var arm in arms)
if (target.HasTrait<TargetableUnit<TargetableUnitInfo>>() &&
weap.Info.ValidTargets.Intersect(target.Trait<TargetableUnit<TargetableUnitInfo>>().TargetTypes) != null)
arm.Weapon.ValidTargets.Intersect(target.Trait<TargetableUnit<TargetableUnitInfo>>().TargetTypes) != null)
return true;
else if (target.HasTrait<TargetableBuilding>() &&
weap.Info.ValidTargets.Intersect(target.Trait<TargetableBuilding>().TargetTypes) != null)
arm.Weapon.ValidTargets.Intersect(target.Trait<TargetableBuilding>().TargetTypes) != null)
return true;
return false;
}
@@ -253,12 +254,15 @@ namespace OpenRA.Mods.RA.AI
foreach (var unit in units)
if (unit != null && unit.HasTrait<AttackBase>() && !unit.HasTrait<Aircraft>()
&& !unit.IsDisabled())
foreach (var weap in unit.Trait<AttackBase>().Weapons)
if (weap.Info.ValidTargets.Contains("Air"))
{
var arms = unit.TraitsImplementing<Armament>();
foreach (var a in arms)
if (a.Weapon.ValidTargets.Contains("Air"))
{
missileUnitsCount++;
break;
}
}
return missileUnitsCount;
}

198
OpenRA.Mods.RA/Armament.cs Executable file
View File

@@ -0,0 +1,198 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.FileFormats;
using OpenRA.GameRules;
using OpenRA.Mods.RA.Render;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class Barrel
{
public PVecInt TurretSpaceOffset; // position in turret space
public PVecInt ScreenSpaceOffset; // screen-space hack to make things line up good.
public int Facing; // deviation from turret facing
}
public class ArmamentInfo : ITraitInfo, Requires<AttackBaseInfo>
{
[WeaponReference]
[Desc("Has to be defined here and in weapons.yaml.")]
public readonly string Weapon = null;
public readonly string Turret = "primary";
public readonly int Recoil = 0;
public readonly int FireDelay = 0;
public readonly float RecoilRecovery = 0.2f;
public readonly int[] LocalOffset = { };
public object Create(ActorInitializer init) { return new Armament(init.self, this); }
}
public class Armament : ITick
{
public readonly ArmamentInfo Info;
public readonly WeaponInfo Weapon;
public readonly Barrel[] Barrels;
Lazy<Turreted> Turret;
public float Recoil { get; private set; }
public int FireDelay { get; private set; }
public int Burst { get; private set; }
public Armament(Actor self, ArmamentInfo info)
{
Info = info;
// We can't soft-depend on TraitInfo, so we have to wait
// until runtime to cache this
Turret = Lazy.New(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.info.Turret == info.Turret));
Weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()];
Burst = Weapon.Burst;
var barrels = new List<Barrel>();
for (var i = 0; i < info.LocalOffset.Length / 5; i++)
barrels.Add(new Barrel
{
TurretSpaceOffset = new PVecInt(info.LocalOffset[5 * i], info.LocalOffset[5 * i + 1]),
ScreenSpaceOffset = new PVecInt(info.LocalOffset[5 * i + 2], info.LocalOffset[5 * i + 3]),
Facing = info.LocalOffset[5 * i + 4],
});
// if no barrels specified, the default is "turret position; turret facing".
if (barrels.Count == 0)
barrels.Add(new Barrel { TurretSpaceOffset = PVecInt.Zero, ScreenSpaceOffset = PVecInt.Zero, Facing = 0 });
Barrels = barrels.ToArray();
}
public void Tick(Actor self)
{
if (FireDelay > 0)
--FireDelay;
Recoil = Math.Max(0f, Recoil - Info.RecoilRecovery);
}
public void CheckFire(Actor self, AttackBase attack, IMove move, IFacing facing, Target target)
{
if (FireDelay > 0) return;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
return;
if (!Combat.IsInRange(self.CenterLocation, Weapon.Range, target)) return;
if (Combat.IsInRange(self.CenterLocation, Weapon.MinRange, target)) return;
if (!IsValidAgainst(self.World, target)) return;
var barrel = Barrels[Burst % Barrels.Length];
var destMove = target.IsActor ? target.Actor.TraitOrDefault<IMove>() : null;
var args = new ProjectileArgs
{
weapon = Weapon,
firedBy = self,
target = target,
src = (self.CenterLocation + (PVecInt)MuzzlePxPosition(self, facing, barrel).ToInt2()),
srcAltitude = move != null ? move.Altitude : 0,
dest = target.CenterLocation,
destAltitude = destMove != null ? destMove.Altitude : 0,
facing = barrel.Facing +
(Turret.Value != null ? Turret.Value.turretFacing :
facing != null ? facing.Facing : Util.GetFacing(target.CenterLocation - self.CenterLocation, 0)),
firepowerModifier = self.TraitsImplementing<IFirepowerModifier>()
.Select(a => a.GetFirepowerModifier())
.Product()
};
attack.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) + ".aud", self.CenterLocation);
}
});
foreach (var na in self.TraitsImplementing<INotifyAttack>())
na.Attacking(self, target);
Recoil = Info.Recoil;
if (--Burst > 0)
FireDelay = Weapon.BurstDelay;
else
{
FireDelay = Weapon.ROF;
Burst = Weapon.Burst;
}
}
public bool IsValidAgainst(World world, Target target)
{
if (target.IsActor)
return Combat.WeaponValidForTarget(Weapon, target.Actor);
else
return Combat.WeaponValidForTarget(Weapon, world, target.CenterLocation.ToCPos());
}
public bool IsReloading { get { return FireDelay > 0; } }
PVecFloat GetUnitspaceBarrelOffset(Actor self, IFacing facing, Barrel b)
{
if (Turret.Value == null && facing == null)
return PVecFloat.Zero;
var turretFacing = Turret.Value != null ? Turret.Value.turretFacing : facing.Facing;
return (PVecFloat)Util.RotateVectorByFacing(b.TurretSpaceOffset.ToFloat2(), turretFacing, .7f);
}
public PVecFloat MuzzlePxPosition(Actor self, IFacing facing, Barrel b)
{
PVecFloat pos = b.ScreenSpaceOffset;
// local facing offset doesn't make sense for actors that don't rotate
if (Turret.Value == null && facing == null)
return pos;
if (Turret.Value != null)
pos += Turret.Value.PxPosition(self, facing);
// Add local unitspace/turretspace offset
var f = Turret.Value != null ? Turret.Value.turretFacing : facing.Facing;
// This is going away, so no point adding unnecessary usings
var ru = self.TraitOrDefault<RenderUnit>();
var numDirs = (ru != null) ? ru.anim.CurrentSequence.Facings : 8;
var quantizedFacing = Util.QuantizeFacing(f, numDirs) * (256 / numDirs);
pos += (PVecFloat)Util.RotateVectorByFacing(b.TurretSpaceOffset.ToFloat2(), quantizedFacing, .7f);
return pos;
}
public PVecFloat RecoilPxOffset(Actor self, int facing)
{
var localRecoil = new float2(0, Recoil);
return (PVecFloat)Util.RotateVectorByFacing(localRecoil, facing, .7f);
}
}
}

View File

@@ -19,70 +19,34 @@ namespace OpenRA.Mods.RA
{
public abstract class AttackBaseInfo : ITraitInfo
{
[WeaponReference]
[Desc("Has to be defined here and in weapons.yaml.")]
public readonly string PrimaryWeapon = null;
[WeaponReference]
public readonly string SecondaryWeapon = null;
public readonly int PrimaryRecoil = 0;
public readonly int SecondaryRecoil = 0;
public readonly float PrimaryRecoilRecovery = 0.2f;
public readonly float SecondaryRecoilRecovery = 0.2f;
public readonly int[] PrimaryLocalOffset = { };
public readonly int[] SecondaryLocalOffset = { };
public readonly int[] PrimaryOffset = { 0, 0 };
public readonly int[] SecondaryOffset = null;
public readonly int FireDelay = 0;
public readonly bool AlignIdleTurrets = false;
public readonly bool CanAttackGround = true;
public readonly int MinimumScanTimeInterval = 30;
public readonly int MaximumScanTimeInterval = 60;
public abstract object Create(ActorInitializer init);
public float GetMaximumRange()
{
var priRange = PrimaryWeapon != null ? Rules.Weapons[PrimaryWeapon.ToLowerInvariant()].Range : 0;
var secRange = SecondaryWeapon != null ? Rules.Weapons[SecondaryWeapon.ToLowerInvariant()].Range : 0;
return Math.Max(priRange, secRange);
}
}
public abstract class AttackBase : IIssueOrder, IResolveOrder, ITick, IExplodeModifier, IOrderVoice, ISync
{
[Sync] public bool IsAttacking { get; internal set; }
public List<Weapon> Weapons = new List<Weapon>();
public List<Turret> Turrets = new List<Turret>();
readonly Actor self;
Lazy<IEnumerable<Armament>> armaments;
protected IEnumerable<Armament> Armaments { get { return armaments.Value; } }
public AttackBase(Actor self)
{
this.self = self;
var info = self.Info.Traits.Get<AttackBaseInfo>();
Turrets.Add(new Turret(info.PrimaryOffset, info.PrimaryRecoilRecovery));
if (info.SecondaryOffset != null)
Turrets.Add(new Turret(info.SecondaryOffset, info.SecondaryRecoilRecovery));
if (info.PrimaryWeapon != null)
Weapons.Add(new Weapon(info.PrimaryWeapon,
Turrets[0], info.PrimaryLocalOffset, info.PrimaryRecoil));
if (info.SecondaryWeapon != null)
Weapons.Add(new Weapon(info.SecondaryWeapon,
info.SecondaryOffset != null ? Turrets[1] : Turrets[0], info.SecondaryLocalOffset, info.SecondaryRecoil));
armaments = Lazy.New(() => self.TraitsImplementing<Armament>());
}
protected virtual bool CanAttack(Actor self, Target target)
{
if (!self.IsInWorld) return false;
if (!target.IsValid) return false;
if (Weapons.All(w => w.IsReloading)) return false;
if (Armaments.All(a => a.IsReloading)) return false;
if (self.IsDisabled()) return false;
if (target.IsActor && target.Actor.HasTrait<ITargetable>() &&
@@ -94,15 +58,12 @@ namespace OpenRA.Mods.RA
public bool ShouldExplode(Actor self) { return !IsReloading(); }
public bool IsReloading() { return Weapons.Any(w => w.IsReloading); }
public bool IsReloading() { return Armaments.Any(a => a.IsReloading); }
List<Pair<int, Action>> delayedActions = new List<Pair<int, Action>>();
public virtual void Tick(Actor self)
{
foreach (var w in Weapons)
w.Tick();
for (var i = 0; i < delayedActions.Count; i++)
{
var x = delayedActions[i];
@@ -127,20 +88,20 @@ namespace OpenRA.Mods.RA
var move = self.TraitOrDefault<IMove>();
var facing = self.TraitOrDefault<IFacing>();
foreach (var w in Weapons)
w.CheckFire(self, this, move, facing, target);
foreach (var a in Armaments)
a.CheckFire(self, this, move, facing, target);
}
public virtual int FireDelay( Actor self, Target target, AttackBaseInfo info )
{
return info.FireDelay;
}
bool IsHeal { get { return Weapons[ 0 ].Info.Warheads[ 0 ].Damage < 0; } }
public IEnumerable<IOrderTargeter> Orders
{
get { yield return new AttackOrderTargeter( "Attack", 6, IsHeal ); }
get
{
if (Armaments.Count() == 0)
yield break;
bool isHeal = Armaments.First().Weapon.Warheads[0].Damage < 0;
yield return new AttackOrderTargeter("Attack", 6, isHeal);
}
}
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued )
@@ -163,12 +124,6 @@ namespace OpenRA.Mods.RA
self.SetTargetLine(target, Color.Red);
AttackTarget(target, order.Queued, order.OrderString == "Attack");
}
else
{
/* hack */
if (self.HasTrait<Turreted>() && self.Info.Traits.Get<AttackBaseInfo>().AlignIdleTurrets)
self.Trait<Turreted>().desiredFacing = null;
}
}
public string VoicePhraseForOrder(Actor self, Order order)
@@ -178,10 +133,10 @@ namespace OpenRA.Mods.RA
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove);
public bool HasAnyValidWeapons(Target t) { return Weapons.Any(w => w.IsValidAgainst(self.World, t)); }
public float GetMaximumRange() { return Weapons.Max(w => w.Info.Range); }
public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.IsValidAgainst(self.World, t)); }
public float GetMaximumRange() { return Armaments.Select(a => a.Weapon.Range).Aggregate(0f, Math.Max); }
public Weapon ChooseWeaponForTarget(Target t) { return Weapons.FirstOrDefault(w => w.IsValidAgainst(self.World, t)); }
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.IsValidAgainst(self.World, t)); }
public void AttackTarget( Target target, bool queued, bool allowMove )
{

View File

@@ -42,10 +42,10 @@ namespace OpenRA.Mods.RA
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var weapon = ChooseWeaponForTarget(newTarget);
if( weapon == null )
var weapon = ChooseArmamentForTarget(newTarget);
if (weapon == null)
return null;
return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range), allowMove);
return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove);
}
}
}

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Linq;
using OpenRA.Mods.RA.Activities;
using OpenRA.Traits;
@@ -28,10 +29,15 @@ namespace OpenRA.Mods.RA
public override void DoAttack(Actor self, Target target)
{
if( !CanAttack( self, target ) ) return;
if (!CanAttack(self, target))
return;
var weapon = Weapons[0].Info;
if( !Combat.IsInRange( self.CenterLocation, weapon.Range, target ) ) return;
var a = ChooseArmamentForTarget(target);
if (a == null)
return;
if (!Combat.IsInRange(self.CenterLocation, a.Weapon.Range, target))
return;
self.CancelActivity();
self.QueueActivity(new Leap(self, target));
@@ -39,10 +45,10 @@ namespace OpenRA.Mods.RA
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var weapon = ChooseWeaponForTarget(newTarget);
if( weapon == null )
var a = ChooseArmamentForTarget(newTarget);
if (a == null)
return null;
return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range), allowMove);
return new Activities.Attack(newTarget, Math.Max(0, (int)a.Weapon.Range), allowMove);
}
}
}

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Linq;
using OpenRA.Traits;
using OpenRA.Mods.RA.Activities;
@@ -26,15 +27,19 @@ namespace OpenRA.Mods.RA
public override void DoAttack(Actor self, Target target)
{
if (!CanAttack (self, target)) return;
if (!CanAttack(self, target)) return;
var weapon = Weapons[0].Info;
if (!Combat.IsInRange(self.CenterLocation, weapon.Range, target)) return;
var arm = Armaments.FirstOrDefault();
if (arm == null)
return;
if (!Combat.IsInRange(self.CenterLocation, arm.Weapon.Range, target))
return;
var move = self.TraitOrDefault<IMove>();
var facing = self.TraitOrDefault<IFacing>();
foreach (var w in Weapons)
w.CheckFire(self, this, move, facing, target);
foreach (var a in Armaments)
a.CheckFire(self, this, move, facing, target);
if (target.Actor != null)
target.Actor.ChangeOwner(self.Owner);

View File

@@ -29,10 +29,10 @@ namespace OpenRA.Mods.RA
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var weapon = ChooseWeaponForTarget(newTarget);
if( weapon == null )
var weapon = ChooseArmamentForTarget(newTarget);
if (weapon == null)
return null;
return new Activities.Heal(newTarget, Math.Max(0, (int)weapon.Info.Range), allowMove);
return new Activities.Heal(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove);
}
}
}

View File

@@ -8,6 +8,7 @@
*/
#endregion
using System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Render;
@@ -30,11 +31,13 @@ namespace OpenRA.Mods.RA
AttackPopupTurretedInfo Info;
int IdleTicks = 0;
PopupState State = PopupState.Open;
Turreted turret;
public AttackPopupTurreted(ActorInitializer init, AttackPopupTurretedInfo info) : base(init.self)
{
Info = info;
buildComplete = init.Contains<SkipMakeAnimsInit>();
turret = turrets.FirstOrDefault();
}
protected override bool CanAttack( Actor self, Target target )

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Buildings;
@@ -25,12 +26,12 @@ namespace OpenRA.Mods.RA
class AttackTurreted : AttackBase, INotifyBuildComplete, ISync
{
protected Target target;
protected Turreted turret;
protected IEnumerable<Turreted> turrets;
[Sync] protected bool buildComplete;
public AttackTurreted(Actor self) : base(self)
{
turret = self.Trait<Turreted>();
turrets = self.TraitsImplementing<Turreted>();
}
protected override bool CanAttack( Actor self, Target target )
@@ -39,7 +40,12 @@ namespace OpenRA.Mods.RA
return false;
if (!target.IsValid) return false;
if (!turret.FaceTarget(self, target)) return false;
bool canAttack = false;
foreach (var t in turrets)
if (t.FaceTarget(self, target))
canAttack = true;
if (!canAttack) return false;
return base.CanAttack( self, target );
}
@@ -85,7 +91,7 @@ namespace OpenRA.Mods.RA
var attack = self.Trait<AttackTurreted>();
const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */
var weapon = attack.ChooseWeaponForTarget(target);
var weapon = attack.ChooseArmamentForTarget(target);
if (weapon != null)
{
@@ -93,7 +99,7 @@ namespace OpenRA.Mods.RA
if (allowMove && self.HasTrait<Mobile>() && !self.Info.Traits.Get<MobileInfo>().OnRails)
return Util.SequenceActivities(
new Follow( target, Math.Max( 0, (int)weapon.Info.Range - RangeTolerance ) ),
new Follow( target, Math.Max( 0, (int)weapon.Weapon.Range - RangeTolerance ) ),
this );
}

View File

@@ -208,48 +208,6 @@ namespace OpenRA.Mods.RA
return false;
}
static PVecFloat GetRecoil(Actor self, float recoil)
{
if (!self.HasTrait<RenderUnitTurreted>())
return PVecFloat.Zero;
var facing = self.Trait<Turreted>().turretFacing;
var localRecoil = new float2(0, recoil); // vector in turret-space.
return (PVecFloat)Util.RotateVectorByFacing(localRecoil, facing, .7f);
}
public static PVecFloat GetTurretPosition(Actor self, IFacing facing, Turret turret)
{
if (facing == null) return turret.ScreenSpacePosition; /* things that don't have a rotating base don't need the turrets repositioned */
var ru = self.TraitOrDefault<RenderUnit>();
var numDirs = (ru != null) ? ru.anim.CurrentSequence.Facings : 8;
var bodyFacing = facing.Facing;
var quantizedFacing = Util.QuantizeFacing(bodyFacing, numDirs) * (256 / numDirs);
return (PVecFloat)Util.RotateVectorByFacing(turret.UnitSpacePosition.ToFloat2(), quantizedFacing, .7f)
+ GetRecoil(self, turret.Recoil)
+ (PVecFloat)turret.ScreenSpacePosition.ToFloat2();
}
static PVecFloat GetUnitspaceBarrelOffset(Actor self, IFacing facing, Turret turret, Barrel barrel)
{
var turreted = self.TraitOrDefault<Turreted>();
if (turreted == null && facing == null)
return PVecFloat.Zero;
var turretFacing = turreted != null ? turreted.turretFacing : facing.Facing;
return (PVecFloat)Util.RotateVectorByFacing(barrel.TurretSpaceOffset.ToFloat2(), turretFacing, .7f);
}
// gets the screen-space position of a barrel.
public static PVecFloat GetBarrelPosition(Actor self, IFacing facing, Turret turret, Barrel barrel)
{
return GetTurretPosition(self, facing, turret) + barrel.ScreenSpaceOffset
+ GetUnitspaceBarrelOffset(self, facing, turret, barrel);
}
public static bool IsInRange( PPos attackOrigin, float range, Actor target )
{
var rsq = range * range * Game.CellSize * Game.CellSize;

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA
public void Tick(Actor self)
{
history.Tick(self.CenterLocation - new PVecInt(0, move.Altitude) - (PVecInt)Combat.GetTurretPosition(self, facing, contrailTurret).ToInt2());
history.Tick(self.CenterLocation - new PVecInt(0, move.Altitude) - (PVecInt)contrailTurret.PxPosition(self, facing).ToInt2());
}
public void RenderAfterWorld(WorldRenderer wr, Actor self) { history.Render(self); }

View File

@@ -55,7 +55,9 @@ namespace OpenRA.Mods.RA
if (facing != null)
td.Add(new FacingInit( facing.Facing ));
var turreted = self.TraitOrDefault<Turreted>();
// TODO: This will only take the first turret if there are multiple
// This isn't a problem with the current units, but may be a problem for mods
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
if (turreted != null)
td.Add( new TurretFacingInit(turreted.turretFacing) );

View File

@@ -368,7 +368,6 @@
<Compile Include="Turreted.cs" />
<Compile Include="Valued.cs" />
<Compile Include="WaterPaletteRotation.cs" />
<Compile Include="Weapon.cs" />
<Compile Include="Widgets\BuildPaletteWidget.cs" />
<Compile Include="Widgets\Logic\ModBrowserLogic.cs" />
<Compile Include="Widgets\Logic\ColorPickerLogic.cs" />
@@ -420,6 +419,7 @@
<Compile Include="Widgets\ColorPreviewManagerWidget.cs" />
<Compile Include="FogPalette.cs" />
<Compile Include="Infiltrates.cs" />
<Compile Include="Armament.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -24,19 +24,17 @@ namespace OpenRA.Mods.RA.Render
public RenderBuildingSeparateTurret(ActorInitializer init, RenderBuildingInfo info)
: base(init, info)
{
var turreted = init.self.Trait<Turreted>();
var attack = init.self.Trait<AttackBase>();
var self = init.self;
var turreted = self.TraitsImplementing<Turreted>();
var turretAnim = new Animation(GetImage(init.self), () => turreted.turretFacing);
turretAnim.Play("turret");
for( var i = 0; i < attack.Turrets.Count; i++ )
var i = 0;
foreach (var t in turreted)
{
var turret = attack.Turrets[i];
anims.Add( "turret_{0}".F(i),
new AnimationWithOffset(turretAnim,
() => Combat.GetTurretPosition(init.self, null, turret).ToFloat2(),
null));
var anim = new Animation(GetImage(self), () => t.turretFacing);
anim.Play("turret");
anims.Add("turret_{0}".F(i++), new AnimationWithOffset(anim,
() => t.PxPosition(self, null).ToFloat2(), null));
}
}
}

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Linq;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Traits;
@@ -26,7 +27,8 @@ namespace OpenRA.Mods.RA.Render
static Func<int> MakeTurretFacingFunc(Actor self)
{
var turreted = self.Trait<Turreted>();
// Turret artwork is baked into the sprite, so only the first turret makes sense.
var turreted = self.TraitsImplementing<Turreted>().FirstOrDefault();
return () => turreted.turretFacing;
}
}

View File

@@ -31,9 +31,10 @@ namespace OpenRA.Mods.RA.Render
spinnerAnim.PlayRepeating("spinner");
var turret = new Turret(info.Offset);
anims.Add("spinner", new AnimationWithOffset(
spinnerAnim,
() => Combat.GetTurretPosition( self, facing, new Turret(info.Offset)).ToFloat2(),
() => turret.PxPosition(self, facing).ToFloat2(),
null ) { ZOffset = 1 } );
}
}

View File

@@ -8,6 +8,8 @@
*/
#endregion
using System;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -24,20 +26,30 @@ namespace OpenRA.Mods.RA.Render
: base(self)
{
var facing = self.Trait<IFacing>();
var turreted = self.Trait<Turreted>();
var attack = self.Trait<AttackBase>();
var turreted = self.TraitsImplementing<Turreted>();
var turretAnim = new Animation(GetImage(self), () => turreted.turretFacing );
turretAnim.Play( "turret" );
for( var i = 0; i < attack.Turrets.Count; i++ )
var i = 0;
foreach (var t in turreted)
{
var turret = attack.Turrets[i];
anims.Add( "turret_{0}".F(i),
new AnimationWithOffset( turretAnim,
() => Combat.GetTurretPosition( self, facing, turret ).ToFloat2(),
null));
var turret = t;
var anim = new Animation(GetImage(self), () => turret.turretFacing);
anim.Play("turret");
anims.Add("turret_{0}".F(i++), new AnimationWithOffset(anim,
() => turret.PxPosition(self, facing).ToFloat2() + RecoilOffset(self, turret), null));
}
}
float2 RecoilOffset(Actor self, Turreted t)
{
var a = self.TraitsImplementing<Armament>()
.OrderByDescending(w => w.Recoil)
.FirstOrDefault(w => w.Info.Turret == t.info.Turret);
if (a == null)
return float2.Zero;
return a.RecoilPxOffset(self, t.turretFacing).ToFloat2();
}
}
}

View File

@@ -8,10 +8,12 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
using System;
using OpenRA.Mods.RA;
namespace OpenRA.Mods.RA.Render
{
@@ -27,25 +29,25 @@ namespace OpenRA.Mods.RA.Render
public WithMuzzleFlash(Actor self)
{
var attack = self.Trait<AttackBase>();
var render = self.Trait<RenderSimple>();
var facing = self.TraitOrDefault<IFacing>();
var turreted = self.TraitOrDefault<Turreted>();
var getFacing = turreted != null ? () => turreted.turretFacing :
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
foreach (var w in attack.Weapons)
foreach( var b in w.Barrels )
var arms = self.TraitsImplementing<Armament>();
foreach (var a in arms)
foreach(var b in a.Barrels)
{
var barrel = b;
var turret = w.Turret;
var turreted = self.TraitsImplementing<Turreted>()
.FirstOrDefault(t => t.info.Turret == a.Info.Turret);
var getFacing = turreted != null ? () => turreted.turretFacing :
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
var muzzleFlash = new Animation(render.GetImage(self), getFacing);
muzzleFlash.Play("muzzle");
muzzleFlashes.Add("muzzle{0}".F(muzzleFlashes.Count), new AnimationWithOffset(
muzzleFlash,
() => Combat.GetBarrelPosition(self, facing, turret, barrel).ToFloat2(),
() => a.MuzzlePxPosition(self, facing, barrel).ToFloat2(),
() => !isShowing));
}
}

View File

@@ -30,9 +30,10 @@ namespace OpenRA.Mods.RA.Render
rotorAnim = new Animation(rs.GetImage(self));
rotorAnim.PlayRepeating("rotor");
var turret = new Turret(info.Offset);
rs.anims.Add(info.Id, new AnimationWithOffset(
rotorAnim,
() => Combat.GetTurretPosition( self, facing, new Turret(info.Offset)).ToFloat2(),
() => turret.PxPosition(self, facing).ToFloat2(),
null ) { ZOffset = 1 } );
}

View File

@@ -9,6 +9,7 @@
#endregion
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -26,11 +27,11 @@ namespace OpenRA.Mods.RA
public void Render(WorldRenderer wr, World w, ActorInfo ai, PPos centerLocation)
{
wr.DrawRangeCircleWithContrast(
Color.FromArgb(128, Color.Yellow),
centerLocation.ToFloat2(),
ai.Traits.Get<AttackBaseInfo>().GetMaximumRange(),
Color.FromArgb(96, Color.Black),
1);
Color.FromArgb(128, Color.Yellow), centerLocation.ToFloat2(),
ai.Traits.WithInterface<ArmamentInfo>()
.Select(a => Rules.Weapons[a.Weapon.ToLowerInvariant()].Range).Max(),
Color.FromArgb(96, Color.Black), 1
);
foreach (var a in w.ActorsWithTrait<RenderRangeCircle>())
if (a.Actor.Owner == a.Actor.World.LocalPlayer)

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA
{
var facing = self.Trait<IFacing>();
var altitude = new PVecInt(0, move.Altitude);
position = (self.CenterLocation - (PVecInt)Combat.GetTurretPosition(self, facing, smokeTurret).ToInt2());
position = (self.CenterLocation - (PVecInt)smokeTurret.PxPosition(self, facing).ToInt2());
if (self.World.RenderedShroud.IsVisible(position.ToCPos()))
self.World.AddFrameEndTask(

View File

@@ -14,23 +14,24 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class TakeCoverInfo : ITraitInfo
public class TakeCoverInfo : TurretedInfo
{
public readonly int ProneTime = 100; /* ticks, =4s */
public readonly float ProneDamage = .5f;
public readonly decimal ProneSpeed = .5m;
public readonly int[] BarrelOffset = null;
public readonly int[] ProneOffset = {0,-2,0,4};
public object Create(ActorInitializer init) { return new TakeCover(this); }
public override object Create(ActorInitializer init) { return new TakeCover(init, this); }
}
// Infantry prone behavior
public class TakeCover : ITick, INotifyDamage, IDamageModifier, ISpeedModifier, ISync
public class TakeCover : Turreted, ITick, INotifyDamage, IDamageModifier, ISpeedModifier, ISync
{
TakeCoverInfo Info;
[Sync] int remainingProneTime = 0;
public TakeCover(TakeCoverInfo info)
public TakeCover(ActorInitializer init, TakeCoverInfo info)
: base(init, info)
{
Info = info;
}
@@ -42,36 +43,16 @@ namespace OpenRA.Mods.RA
if (e.Damage > 0 && (e.Warhead == null || !e.Warhead.PreventProne)) /* Don't go prone when healed */
{
if (!IsProne)
ApplyBarrelOffset(self, true);
turret = new Turret(Info.ProneOffset);
remainingProneTime = Info.ProneTime;
}
}
public void Tick(Actor self)
public override void Tick(Actor self)
{
if (IsProne)
{
if (--remainingProneTime == 0)
ApplyBarrelOffset(self, false);
}
}
public void ApplyBarrelOffset(Actor self, bool isProne)
{
if (Info.BarrelOffset == null)
return;
var ab = self.TraitOrDefault<AttackBase>();
if (ab == null)
return;
var sign = isProne ? 1 : -1;
foreach (var w in ab.Weapons)
foreach (var b in w.Barrels)
{
b.TurretSpaceOffset += sign * new PVecInt(Info.BarrelOffset[0], Info.BarrelOffset[1]);
b.ScreenSpaceOffset += sign * new PVecInt(Info.BarrelOffset[2], Info.BarrelOffset[3]);
}
base.Tick(self);
if (IsProne && --remainingProneTime == 0)
turret = new Turret(Info.Offset);
}
public float GetDamageModifier(Actor attacker, WarheadInfo warhead )

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA
alt = 0;
facing = Turreted.GetInitialTurretFacing( init, 0 );
pos = Combat.GetTurretPosition(self, ifacing, new Turret(info.Offset)).ToFloat2();
pos = new Turret(info.Offset).PxPosition(self, ifacing).ToFloat2();
v = Game.CosmeticRandom.Gauss2D(1) * info.Spread.RelOffset();
dfacing = Game.CosmeticRandom.Gauss1D(2) * info.ROT;

View File

@@ -8,23 +8,29 @@
*/
#endregion
using System.Collections.Generic;
using OpenRA.Mods.RA.Render;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class TurretedInfo : ITraitInfo, UsesInit<TurretFacingInit>
{
public readonly string Turret = "primary";
public readonly int ROT = 255;
public readonly int InitialFacing = 128;
public readonly int[] Offset = {0,0};
public readonly bool AlignWhenIdle = false;
public object Create(ActorInitializer init) { return new Turreted(init, this); }
public virtual object Create(ActorInitializer init) { return new Turreted(init, this); }
}
public class Turreted : ITick, ISync
public class Turreted : ITick, ISync, IResolveOrder
{
[Sync] public int turretFacing = 0;
public int? desiredFacing;
TurretedInfo info;
public TurretedInfo info;
protected Turret turret;
IFacing facing;
public static int GetInitialTurretFacing(ActorInitializer init, int def)
@@ -43,9 +49,10 @@ namespace OpenRA.Mods.RA
this.info = info;
turretFacing = GetInitialTurretFacing(init, info.InitialFacing);
facing = init.self.TraitOrDefault<IFacing>();
turret = new Turret(info.Offset);
}
public void Tick(Actor self)
public virtual void Tick(Actor self)
{
var df = desiredFacing ?? ( facing != null ? facing.Facing : turretFacing );
turretFacing = Util.TickFacing(turretFacing, df, info.ROT);
@@ -56,5 +63,42 @@ namespace OpenRA.Mods.RA
desiredFacing = Util.GetFacing( target.CenterLocation - self.CenterLocation, turretFacing );
return turretFacing == desiredFacing;
}
public virtual void ResolveOrder(Actor self, Order order)
{
if (info.AlignWhenIdle && order.OrderString != "Attack" && order.OrderString != "AttackHold")
desiredFacing = null;
}
public PVecFloat PxPosition(Actor self, IFacing facing)
{
return turret.PxPosition(self, facing);
}
}
public class Turret
{
public PVecInt UnitSpacePosition; // where, in the unit's local space.
public PVecInt ScreenSpacePosition; // screen-space hack to make things line up good.
public Turret(int[] offset)
{
ScreenSpacePosition = (PVecInt) offset.AbsOffset().ToInt2();
UnitSpacePosition = (PVecInt) offset.RelOffset().ToInt2();
}
public PVecFloat PxPosition(Actor self, IFacing facing)
{
// Things that don't have a rotating base don't need the turrets repositioned
if (facing == null) return ScreenSpacePosition;
var ru = self.TraitOrDefault<RenderUnit>();
var numDirs = (ru != null) ? ru.anim.CurrentSequence.Facings : 8;
var bodyFacing = facing.Facing;
var quantizedFacing = Util.QuantizeFacing(bodyFacing, numDirs) * (256 / numDirs);
return (PVecFloat)Util.RotateVectorByFacing(UnitSpacePosition.ToFloat2(), quantizedFacing, .7f)
+ (PVecFloat)ScreenSpacePosition.ToFloat2();
}
}
}

View File

@@ -1,162 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Traits;
namespace OpenRA.Mods.RA
{
public class Barrel
{
public PVecInt TurretSpaceOffset; // position in turret space
public PVecInt ScreenSpaceOffset; // screen-space hack to make things line up good.
public int Facing; // deviation from turret facing
}
public class Turret
{
public float Recoil = 0.0f; // remaining recoil
public float RecoilRecovery = 0.2f; // recoil recovery rate
public PVecInt UnitSpacePosition; // where, in the unit's local space.
public PVecInt ScreenSpacePosition; // screen-space hack to make things line up good.
public Turret(int[] offset, float recoilRecovery)
{
ScreenSpacePosition = (PVecInt) offset.AbsOffset().ToInt2();
UnitSpacePosition = (PVecInt) offset.RelOffset().ToInt2();
RecoilRecovery = recoilRecovery;
}
public Turret(int[] offset) : this(offset, 0) {}
}
public class Weapon
{
public WeaponInfo Info;
public int FireDelay = 0; // time (in frames) until the weapon can fire again
public int Burst = 0; // burst counter
public int Recoil = 0;
public Barrel[] Barrels; // where projectiles are spawned, in local turret space.
public Turret Turret; // where this weapon is mounted -- possibly shared
public Weapon(string weaponName, Turret turret, int[] localOffset, int recoil)
{
Info = Rules.Weapons[weaponName.ToLowerInvariant()];
Burst = Info.Burst;
Turret = turret;
Recoil = recoil;
var barrels = new List<Barrel>();
for (var i = 0; i < localOffset.Length / 5; i++)
barrels.Add(new Barrel
{
TurretSpaceOffset = new PVecInt(localOffset[5 * i], localOffset[5 * i + 1]),
ScreenSpaceOffset = new PVecInt(localOffset[5 * i + 2], localOffset[5 * i + 3]),
Facing = localOffset[5 * i + 4]
});
// if no barrels specified, the default is "turret position; turret facing".
if (barrels.Count == 0)
barrels.Add(new Barrel { TurretSpaceOffset = PVecInt.Zero, ScreenSpaceOffset = PVecInt.Zero, Facing = 0 });
Barrels = barrels.ToArray();
}
public bool IsReloading { get { return FireDelay > 0; } }
public void Tick()
{
if (FireDelay > 0) --FireDelay;
Turret.Recoil = Math.Max(0f, Turret.Recoil - Turret.RecoilRecovery);
}
public bool IsValidAgainst(World world, Target target)
{
if( target.IsActor )
return Combat.WeaponValidForTarget( Info, target.Actor );
else
return Combat.WeaponValidForTarget( Info, world, target.CenterLocation.ToCPos() );
}
public void FiredShot()
{
Turret.Recoil = this.Recoil;
if (--Burst > 0)
FireDelay = Info.BurstDelay;
else
{
FireDelay = Info.ROF;
Burst = Info.Burst;
}
}
public void CheckFire(Actor self, AttackBase attack, IMove move, IFacing facing, Target target)
{
if (FireDelay > 0) return;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
return;
if (!Combat.IsInRange(self.CenterLocation, Info.Range, target)) return;
if (Combat.IsInRange(self.CenterLocation, Info.MinRange, target)) return;
if (!IsValidAgainst(self.World, target)) return;
var barrel = Barrels[Burst % Barrels.Length];
var destMove = target.IsActor ? target.Actor.TraitOrDefault<IMove>() : null;
var turreted = self.TraitOrDefault<Turreted>();
var args = new ProjectileArgs
{
weapon = Info,
firedBy = self,
target = target,
src = (self.CenterLocation + (PVecInt)Combat.GetBarrelPosition(self, facing, Turret, barrel).ToInt2()),
srcAltitude = move != null ? move.Altitude : 0,
dest = target.CenterLocation,
destAltitude = destMove != null ? destMove.Altitude : 0,
facing = barrel.Facing +
(turreted != null ? turreted.turretFacing :
facing != null ? facing.Facing : Util.GetFacing(target.CenterLocation - self.CenterLocation, 0)),
firepowerModifier = self.TraitsImplementing<IFirepowerModifier>()
.Select(a => a.GetFirepowerModifier())
.Product()
};
attack.ScheduleDelayedAction( attack.FireDelay( self, target, self.Info.Traits.Get<AttackBaseInfo>() ), () =>
{
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) + ".aud", self.CenterLocation);
}
});
foreach (var na in self.TraitsImplementing<INotifyAttack>())
na.Attacking(self, target);
FiredShot();
}
}
}