Merge pull request #4900 from pchote/garrison

Add garrison support for player-owned transports
This commit is contained in:
ScottNZ
2014-03-21 23:23:26 +13:00
38 changed files with 818 additions and 476 deletions

View File

@@ -122,6 +122,9 @@ NEW:
Added IEffectiveOwner interface for traits/logic that temporarily alter an actor's apparent owner.
Renamed Spy trait to Disguise, SpyToolTip trait to DisguiseToolTip, and RenderSpy trait to RenderDisguise.
Overhauled the internal map management and preview generation code.
Muzzleflash definitions have moved from WithMuzzleFlash to each Armament. Only one WithMuzzleFlash is now required per actor.
Added an AttackGarrisoned trait that allows passengers to fire through a set of defined ports.
Maps can define initial cargo for actors by adding "Cargo: actora, actorb, ..." to the actor reference.
Server:
Message of the day is now shared between all mods and a default motd.txt gets created in the user directory.
Asset Browser:

View File

@@ -123,7 +123,7 @@ namespace OpenRA.Graphics
// added for contrails
foreach (var a in world.ActorsWithTrait<IPostRender>())
if (!a.Actor.Destroyed)
if (a.Actor.IsInWorld && !a.Actor.Destroyed)
a.Trait.RenderAfterWorld(this, a.Actor);
if (world.OrderGenerator != null)

View File

@@ -67,7 +67,7 @@ namespace OpenRA.Mods.RA.Activities
var cargo = self.TraitOrDefault<Cargo>();
if (cargo != null)
init.Add( new CargoInit( cargo.Passengers.ToArray() ) );
init.Add( new RuntimeCargoInit( cargo.Passengers.ToArray() ) );
var a = w.CreateActor( ToActor, init );

View File

@@ -44,16 +44,25 @@ namespace OpenRA.Mods.RA
[Desc("Recoil recovery per-frame")]
public readonly WRange RecoilRecovery = new WRange(9);
[Desc("Muzzle flash sequence to render")]
public readonly string MuzzleSequence = null;
[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 : ITick
public class Armament : ITick, IExplodeModifier
{
public readonly ArmamentInfo Info;
public readonly WeaponInfo Weapon;
public readonly Barrel[] Barrels;
public 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; }
@@ -61,11 +70,13 @@ namespace OpenRA.Mods.RA
public Armament(Actor self, ArmamentInfo info)
{
this.self = self;
Info = info;
// We can't resolve these until runtime
Turret = Lazy.New(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret));
Coords = Lazy.New(() => self.Trait<IBodyOrientation>());
limitedAmmo = Lazy.New(() => self.TraitOrDefault<LimitedAmmo>());
Weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()];
Burst = Weapon.Burst;
@@ -94,27 +105,44 @@ namespace OpenRA.Mods.RA
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 void CheckFire(Actor self, AttackBase attack, IFacing facing, Target target)
public Barrel CheckFire(Actor self, IFacing facing, Target target)
{
if (FireDelay > 0)
return;
return null;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
return;
if (limitedAmmo.Value != null && !limitedAmmo.Value.HasAmmo())
return null;
if (!target.IsInRange(self.CenterPosition, Weapon.Range))
return;
return null;
if (Weapon.MinRange != WRange.Zero && target.IsInRange(self.CenterPosition, Weapon.MinRange))
return;
return null;
if (!Weapon.IsValidAgainst(target, self.World))
return;
return null;
var barrel = Barrels[Burst % Barrels.Length];
var muzzlePosition = self.CenterPosition + MuzzleOffset(self, barrel);
@@ -134,7 +162,7 @@ namespace OpenRA.Mods.RA
GuidedTarget = target
};
attack.ScheduleDelayedAction(Info.FireDelay, () =>
ScheduleDelayedAction(Info.FireDelay, () =>
{
if (args.Weapon.Projectile != null)
{
@@ -159,9 +187,12 @@ namespace OpenRA.Mods.RA
FireDelay = Weapon.ROF;
Burst = Weapon.Burst;
}
return barrel;
}
public bool IsReloading { get { return FireDelay > 0; } }
public bool ShouldExplode(Actor self) { return !IsReloading; }
public WVec MuzzleOffset(Actor self, Barrel b)
{
@@ -184,5 +215,7 @@ namespace OpenRA.Mods.RA
orientation += Turret.Value.LocalOrientation(self);
return orientation;
}
public Actor Actor { get { return self; } }
}
}

View File

@@ -13,37 +13,45 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public abstract class AttackBaseInfo : ITraitInfo
{
[Desc("Armament names")]
public readonly string[] Armaments = { "primary", "secondary" };
public readonly string Cursor = "attack";
public readonly string OutsideRangeCursor = "attackoutsiderange";
public abstract object Create(ActorInitializer init);
}
public abstract class AttackBase : IIssueOrder, IResolveOrder, ITick, IExplodeModifier, IOrderVoice, ISync
public abstract class AttackBase : IIssueOrder, IResolveOrder, IOrderVoice, ISync
{
[Sync] public bool IsAttacking { get; internal set; }
public IEnumerable<Armament> Armaments { get { return GetArmaments(); } }
protected Lazy<IFacing> facing;
protected Lazy<Building> building;
protected Func<IEnumerable<Armament>> GetArmaments;
readonly Actor self;
readonly AttackBaseInfo info;
protected Lazy<IFacing> facing;
Lazy<IEnumerable<Armament>> armaments;
protected IEnumerable<Armament> Armaments { get { return armaments.Value; } }
List<Pair<int, Action>> delayedActions = new List<Pair<int, Action>>();
public AttackBase(Actor self, AttackBaseInfo info)
{
this.self = self;
this.info = info;
armaments = Lazy.New(() => self.TraitsImplementing<Armament>());
var armaments = Lazy.New(() => self.TraitsImplementing<Armament>()
.Where(a => info.Armaments.Contains(a.Info.Name)));
GetArmaments = () => armaments.Value;
facing = Lazy.New(() => self.TraitOrDefault<IFacing>());
building = Lazy.New(() => self.TraitOrDefault<Building>());
}
protected virtual bool CanAttack(Actor self, Target target)
@@ -51,6 +59,10 @@ namespace OpenRA.Mods.RA
if (!self.IsInWorld)
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;
@@ -63,38 +75,13 @@ namespace OpenRA.Mods.RA
return true;
}
public bool ShouldExplode(Actor self) { return !IsReloading(); }
public bool IsReloading() { return Armaments.Any(a => a.IsReloading); }
public virtual void Tick(Actor self)
{
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);
}
internal void ScheduleDelayedAction(int t, Action a)
{
if (t > 0)
delayedActions.Add(Pair.New(t, a));
else
a();
}
public virtual void DoAttack(Actor self, Target target)
{
if (!CanAttack(self, target))
return;
foreach (var a in Armaments)
a.CheckFire(self, this, facing.Value, target);
a.CheckFire(self, facing.Value, target);
}
public IEnumerable<IOrderTargeter> Orders
@@ -148,7 +135,13 @@ namespace OpenRA.Mods.RA
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove);
public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World)); }
public WRange GetMaximumRange() { return Armaments.Max(a => a.Weapon.Range); }
public WRange GetMaximumRange()
{
if (!Armaments.Any())
return WRange.Zero;
return Armaments.Max(a => a.Weapon.Range);
}
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World)); }

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA
class AttackCharge : AttackOmni, ITick, INotifyAttack, ISync
{
readonly AttackChargeInfo aci;
readonly AttackChargeInfo info;
[Sync] int charges;
[Sync] int timeToRecharge;
@@ -37,27 +37,25 @@ namespace OpenRA.Mods.RA
public AttackCharge(Actor self, AttackChargeInfo info)
: base(self, info)
{
aci = self.Info.Traits.Get<AttackChargeInfo>();
charges = aci.MaxCharges;
this.info = info;
charges = info.MaxCharges;
}
public override void Tick(Actor self)
public void Tick(Actor self)
{
if (--timeToRecharge <= 0)
charges = aci.MaxCharges;
base.Tick(self);
charges = info.MaxCharges;
}
public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
--charges;
timeToRecharge = aci.ReloadTime;
timeToRecharge = info.ReloadTime;
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
return new ChargeAttack(newTarget);
return new ChargeAttack(this, newTarget);
}
public override void ResolveOrder(Actor self, Order order)
@@ -70,9 +68,12 @@ namespace OpenRA.Mods.RA
class ChargeAttack : Activity
{
readonly AttackCharge attack;
readonly Target target;
public ChargeAttack(Target target)
public ChargeAttack(AttackCharge attack, Target target)
{
this.attack = attack;
this.target = target;
}
@@ -80,23 +81,22 @@ namespace OpenRA.Mods.RA
{
if (IsCanceled || !target.IsValidFor(self))
return NextActivity;
var initDelay = self.Info.Traits.Get<AttackChargeInfo>().InitialChargeDelay;
var attack = self.Trait<AttackCharge>();
if (attack.charges == 0 || !attack.CanAttack(self, target))
return this;
self.Trait<RenderBuildingCharge>().PlayCharge(self);
return Util.SequenceActivities(new Wait(initDelay), new ChargeFire(target), this);
return Util.SequenceActivities(new Wait(attack.info.InitialChargeDelay), new ChargeFire(attack, target), this);
}
}
class ChargeFire : Activity
{
readonly AttackCharge attack;
readonly Target target;
public ChargeFire(Target target)
public ChargeFire(AttackCharge attack, Target target)
{
this.attack = attack;
this.target = target;
}
@@ -105,15 +105,12 @@ namespace OpenRA.Mods.RA
if (IsCanceled || !target.IsValidFor(self))
return NextActivity;
var chargeDelay = self.Info.Traits.Get<AttackChargeInfo>().ChargeDelay;
var attack = self.Trait<AttackCharge>();
if (attack.charges == 0)
return NextActivity;
attack.DoAttack(self, target);
return Util.SequenceActivities(new Wait(chargeDelay), this);
return Util.SequenceActivities(new Wait(attack.info.ChargeDelay), this);
}
}
}

View 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;
using System.Collections.Generic;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Move;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
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;
const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */
var weapon = attack.ChooseArmamentForTarget(target);
if (weapon != null)
{
var range = WRange.FromCells(Math.Max(0, weapon.Weapon.Range.Range / 1024 - RangeTolerance));
attack.Target = target;
if (move != null)
return Util.SequenceActivities(move.MoveFollow(self, target, range), this);
}
return NextActivity;
}
}
}
}

View File

@@ -0,0 +1,192 @@
#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.FileFormats;
using OpenRA.Graphics;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Move;
using OpenRA.Mods.RA.Render;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class FirePort
{
public WVec Offset;
public WAngle Yaw;
public WAngle Cone;
}
public class AttackGarrisonedInfo : AttackFollowInfo, Requires<CargoInfo>
{
[Desc("Fire port offsets in local coordinates")]
public readonly WRange[] PortOffsets = {};
[Desc("Fire port yaw angles")]
public readonly WAngle[] PortYaws = {};
[Desc("Fire port yaw cone angle")]
public readonly WAngle[] PortCones = {};
public override object Create(ActorInitializer init) { return new AttackGarrisoned(init.self, this); }
}
public class AttackGarrisoned : AttackFollow, INotifyPassengerEntered, INotifyPassengerExited, IRender
{
public readonly FirePort[] Ports;
AttackGarrisonedInfo info;
Lazy<IBodyOrientation> coords;
List<Armament> armaments;
List<AnimationWithOffset> muzzles;
Dictionary<Actor, IFacing> paxFacing;
Dictionary<Actor, IPositionable> paxPos;
Dictionary<Actor, RenderSprites> paxRender;
public AttackGarrisoned(Actor self, AttackGarrisonedInfo info)
: base(self, info)
{
this.info = info;
coords = Lazy.New(() => self.Trait<IBodyOrientation>());
armaments = new List<Armament>();
muzzles = new List<AnimationWithOffset>();
paxFacing = new Dictionary<Actor, IFacing>();
paxPos = new Dictionary<Actor, IPositionable>();
paxRender = new Dictionary<Actor, RenderSprites>();
GetArmaments = () => armaments;
if (info.PortOffsets.Length % 3 != 0 || info.PortOffsets.Length == 0)
throw new InvalidOperationException("PortOffsets array length must be a multiple of three");
if (info.PortYaws.Length * 3 != info.PortOffsets.Length)
throw new InvalidOperationException("FireYaw must define an angle for each port");
if (info.PortCones.Length * 3 != info.PortOffsets.Length)
throw new InvalidOperationException("PortCones must define an angle for each port");
var p = new List<FirePort>();
for (var i = 0; i < info.PortOffsets.Length / 3; i++)
{
p.Add(new FirePort
{
Offset = new WVec(info.PortOffsets[3*i], info.PortOffsets[3*i + 1], info.PortOffsets[3*i + 2]),
Yaw = info.PortYaws[i],
Cone = info.PortCones[i],
});
}
Ports = p.ToArray();
}
public void PassengerEntered(Actor self, Actor passenger)
{
paxFacing.Add(passenger, passenger.Trait<IFacing>());
paxPos.Add(passenger, passenger.Trait<IPositionable>());
paxRender.Add(passenger, passenger.Trait<RenderSprites>());
armaments = armaments.Append(passenger.TraitsImplementing<Armament>()
.Where(a => info.Armaments.Contains(a.Info.Name))
.ToArray()).ToList();
}
public void PassengerExited(Actor self, Actor passenger)
{
paxFacing.Remove(passenger);
paxPos.Remove(passenger);
paxRender.Remove(passenger);
armaments.RemoveAll(a => a.Actor == passenger);
}
FirePort SelectFirePort(Actor self, WAngle targetYaw)
{
// Pick a random port that faces the target
var bodyYaw = facing.Value != null ? WAngle.FromFacing(facing.Value.Facing) : WAngle.Zero;
var indices = Exts.MakeArray(Ports.Length, i => i).Shuffle(self.World.SharedRandom);
foreach (var i in indices)
{
var yaw = bodyYaw + Ports[i].Yaw;
var leftTurn = (yaw - targetYaw).Angle;
var rightTurn = (targetYaw - yaw).Angle;
if (Math.Min(leftTurn, rightTurn) <= Ports[i].Cone.Angle)
return Ports[i];
}
return null;
}
WVec PortOffset(Actor self, FirePort p)
{
var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation);
return coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation));
}
public override void DoAttack(Actor self, Target target)
{
if (!CanAttack(self, target))
return;
var pos = self.CenterPosition;
var targetYaw = WAngle.FromFacing(Traits.Util.GetFacing(target.CenterPosition - self.CenterPosition, 0));
foreach (var a in Armaments)
{
var port = SelectFirePort(self, targetYaw);
if (port == null)
return;
var muzzleFacing = targetYaw.Angle / 4;
paxFacing[a.Actor].Facing = muzzleFacing;
paxPos[a.Actor].SetVisualPosition(a.Actor, pos + PortOffset(self, port));
var barrel = a.CheckFire(a.Actor, facing.Value, target);
if (barrel != null && a.Info.MuzzleSequence != null)
{
// Muzzle facing is fixed once the firing starts
var muzzleAnim = new Animation(paxRender[a.Actor].GetImage(a.Actor), () => muzzleFacing);
var sequence = a.Info.MuzzleSequence;
if (a.Info.MuzzleSplitFacings > 0)
sequence += Traits.Util.QuantizeFacing(muzzleFacing, a.Info.MuzzleSplitFacings).ToString();
var muzzleFlash = new AnimationWithOffset(muzzleAnim,
() => PortOffset(self, port),
() => false,
p => WithTurret.ZOffsetFromCenter(self, p, 1024));
muzzles.Add(muzzleFlash);
muzzleAnim.PlayThen(sequence, () => muzzles.Remove(muzzleFlash));
}
}
}
public IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
{
// Display muzzle flashes
foreach (var m in muzzles)
foreach (var r in m.Render(self, wr, wr.Palette("effect"), 1f))
yield return r;
}
public override void Tick(Actor self)
{
base.Tick(self);
// Take a copy so that Tick() can remove animations
foreach (var m in muzzles.ToList())
m.Animation.Tick();
}
}
}

View File

@@ -25,7 +25,8 @@ namespace OpenRA.Mods.RA
public override void DoAttack(Actor self, Target target)
{
if (!CanAttack(self, target)) return;
if (!CanAttack(self, target))
return;
var arm = Armaments.FirstOrDefault();
if (arm == null)
@@ -34,9 +35,8 @@ namespace OpenRA.Mods.RA
if (!target.IsInRange(self.CenterPosition, arm.Weapon.Range))
return;
var facing = self.TraitOrDefault<IFacing>();
foreach (var a in Armaments)
a.CheckFire(self, this, facing, target);
a.CheckFire(self, facing.Value, target);
if (target.Actor != null)
target.Actor.ChangeOwner(self.Owner);

View File

@@ -8,7 +8,7 @@
*/
#endregion
using OpenRA.Mods.RA.Buildings;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
@@ -18,34 +18,25 @@ namespace OpenRA.Mods.RA
public override object Create(ActorInitializer init) { return new AttackOmni(init.self, this); }
}
class AttackOmni : AttackBase, INotifyBuildComplete, ISync
class AttackOmni : AttackBase, ISync
{
[Sync] bool buildComplete = false;
public void BuildingComplete(Actor self) { buildComplete = true; }
public AttackOmni(Actor self, AttackOmniInfo info)
: base(self, info) { }
protected override bool CanAttack(Actor self, Target target)
{
var isBuilding = self.HasTrait<Building>() && !buildComplete;
return base.CanAttack(self, target) && !isBuilding;
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
return new SetTarget(newTarget, this);
return new SetTarget(this, newTarget);
}
class SetTarget : Activity
{
readonly Target target;
readonly AttackOmni ao;
readonly AttackOmni attack;
public SetTarget(Target target, AttackOmni ao)
public SetTarget(AttackOmni attack, Target target)
{
this.target = target;
this.ao = ao;
this.attack = attack;
}
public override Activity Tick(Actor self)
@@ -53,7 +44,7 @@ namespace OpenRA.Mods.RA
if (IsCanceled || !target.IsValidFor(self))
return NextActivity;
ao.DoAttack(self, target);
attack.DoAttack(self, target);
return this;
}
}

View File

@@ -35,19 +35,20 @@ namespace OpenRA.Mods.RA
int idleTicks = 0;
PopupState state = PopupState.Open;
Turreted turret;
bool skippedMakeAnimation;
public AttackPopupTurreted(ActorInitializer init, AttackPopupTurretedInfo info)
: base(init.self, info)
{
this.info = info;
buildComplete = init.Contains<SkipMakeAnimsInit>();
turret = turrets.FirstOrDefault();
rb = init.self.Trait<RenderBuilding>();
skippedMakeAnimation = init.Contains<SkipMakeAnimsInit>();
}
protected override bool CanAttack(Actor self, Target target)
{
if (state == PopupState.Transitioning || !buildComplete)
if (state == PopupState.Transitioning || !building.Value.BuildComplete)
return false;
if (!base.CanAttack(self, target))
@@ -90,17 +91,14 @@ namespace OpenRA.Mods.RA
}
}
public override void BuildingComplete(Actor self)
public void BuildingComplete(Actor self)
{
// Set true for SkipMakeAnimsInit
if (buildComplete)
if (!skippedMakeAnimation)
{
state = PopupState.Closed;
rb.PlayCustomAnimRepeating(self, "closed-idle");
turret.desiredFacing = null;
}
buildComplete = true;
}
public float GetDamageModifier(Actor attacker, WarheadInfo warhead)

View File

@@ -10,23 +10,21 @@
using System;
using System.Collections.Generic;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Move;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
class AttackTurretedInfo : AttackBaseInfo, Requires<TurretedInfo>
class AttackTurretedInfo : AttackFollowInfo, Requires<TurretedInfo>
{
public override object Create(ActorInitializer init) { return new AttackTurreted(init.self, this); }
}
class AttackTurreted : AttackBase, INotifyBuildComplete, ISync
class AttackTurreted : AttackFollow, ITick, ISync
{
public Target Target { get; protected set; }
protected IEnumerable<Turreted> turrets;
[Sync] protected bool buildComplete;
public AttackTurreted(Actor self, AttackTurretedInfo info)
: base(self, info)
@@ -36,10 +34,7 @@ namespace OpenRA.Mods.RA
protected override bool CanAttack(Actor self, Target target)
{
if (self.HasTrait<Building>() && !buildComplete)
return false;
if (!target.IsValidFor(self))
if (!base.CanAttack(self, target))
return false;
var canAttack = false;
@@ -47,70 +42,7 @@ namespace OpenRA.Mods.RA
if (t.FaceTarget(self, target))
canAttack = true;
if (!canAttack)
return false;
return base.CanAttack(self, target);
}
public override void Tick(Actor self)
{
base.Tick(self);
DoAttack(self, Target);
IsAttacking = Target.IsValidFor(self);
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
return new AttackActivity(newTarget, allowMove);
}
public override void ResolveOrder(Actor self, Order order)
{
base.ResolveOrder(self, order);
if (order.OrderString == "Stop")
Target = Target.Invalid;
}
public virtual void BuildingComplete(Actor self) { buildComplete = true; }
class AttackActivity : Activity
{
readonly Target target;
readonly bool allowMove;
public AttackActivity(Target newTarget, bool allowMove)
{
this.target = newTarget;
this.allowMove = allowMove;
}
public override Activity Tick(Actor self)
{
if (IsCanceled || !target.IsValidFor(self))
return NextActivity;
if (self.IsDisabled())
return this;
var attack = self.Trait<AttackTurreted>();
const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */
var weapon = attack.ChooseArmamentForTarget(target);
if (weapon != null)
{
var range = WRange.FromCells(Math.Max(0, weapon.Weapon.Range.Range / 1024 - RangeTolerance));
attack.Target = target;
var mobile = self.TraitOrDefault<Mobile>();
if (allowMove && mobile != null && !mobile.Info.OnRails)
return Util.SequenceActivities(mobile.MoveFollow(self, target, range), this);
}
return NextActivity;
}
return canAttack;
}
}
}

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA
public override object Create(ActorInitializer init) { return new AttackBomber(init.self, this); }
}
class AttackBomber : AttackBase, ISync, INotifyRemovedFromWorld
class AttackBomber : AttackBase, ITick, ISync, INotifyRemovedFromWorld
{
AttackBomberInfo info;
[Sync] Target target;
@@ -43,10 +43,8 @@ namespace OpenRA.Mods.RA
this.info = info;
}
public override void Tick(Actor self)
public void Tick(Actor self)
{
base.Tick(self);
var cp = self.CenterPosition;
var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z));
var wasInAttackRange = inAttackRange;
@@ -59,7 +57,7 @@ namespace OpenRA.Mods.RA
continue;
inAttackRange = true;
a.CheckFire(self, this, facing.Value, bombTarget);
a.CheckFire(self, facing.Value, bombTarget);
}
// Guns only fire when approaching the target
@@ -74,7 +72,7 @@ namespace OpenRA.Mods.RA
var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Range / 2, cp.Z).Rotate(WRot.FromFacing(f)));
inAttackRange = true;
a.CheckFire(self, this, facing.Value, t);
a.CheckFire(self, facing.Value, t);
}
}

View File

@@ -72,27 +72,33 @@ namespace OpenRA.Mods.RA
if (!self.IsIdle || !info.TargetWhenDamaged)
return;
if (e.Attacker.Destroyed)
var attacker = e.Attacker;
if (attacker.Destroyed || Stance < UnitStance.ReturnFire)
return;
if (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(e.Attacker)))
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 (e.Attacker.AppearsFriendlyTo(self))
if (attacker.AppearsFriendlyTo(self))
return;
// don't retaliate against healers
if (e.Damage < 0)
return;
Aggressor = e.Attacker;
Aggressor = attacker;
if (at == null || !at.IsReachableTarget(at.Target, info.AllowMovement && Stance != UnitStance.Defend))
Attack(self, e.Attacker);
Attack(self, Aggressor);
}
public void TickIdle(Actor self)

View File

@@ -98,19 +98,23 @@ namespace OpenRA.Mods.RA.Buildings
}
}
public class Building : INotifyDamage, IOccupySpace, INotifyCapture, ISync, ITechTreePrerequisite, INotifyAddedToWorld, INotifyRemovedFromWorld
public class Building : INotifyDamage, IOccupySpace, INotifyCapture, INotifyBuildComplete, INotifySold, ISync, ITechTreePrerequisite, INotifyAddedToWorld, INotifyRemovedFromWorld
{
readonly Actor self;
public readonly BuildingInfo Info;
public bool BuildComplete { get; private set; }
[Sync] readonly CPos topLeft;
readonly Actor self;
PowerManager PlayerPower;
[Sync] public bool Locked; /* shared activity lock: undeploy, sell, capture, etc */
/* shared activity lock: undeploy, sell, capture, etc */
[Sync] public bool Locked = true;
public bool Lock()
{
if (Locked) return false;
if (Locked)
return false;
Locked = true;
return true;
}
@@ -133,6 +137,7 @@ namespace OpenRA.Mods.RA.Buildings
.Select(c => Pair.New(c, SubCell.FullCell)).ToArray();
CenterPosition = topLeft.CenterPosition + FootprintUtils.CenterOffset(Info);
BuildComplete = init.Contains<SkipMakeAnimsInit>();
}
public int GetPowerUsage()
@@ -172,5 +177,14 @@ namespace OpenRA.Mods.RA.Buildings
self.World.ActorMap.RemovePosition(self, this);
self.World.ScreenMap.Remove(self);
}
public void BuildingComplete(Actor self)
{
BuildComplete = true;
Locked = false;
}
public void Selling(Actor self) { BuildComplete = false; }
public void Sold(Actor self) { }
}
}

View File

@@ -44,9 +44,21 @@ namespace OpenRA.Mods.RA
self = init.self;
Info = info;
if (init.Contains<CargoInit>())
if (init.Contains<RuntimeCargoInit>())
{
cargo = init.Get<CargoInit, Actor[]>().ToList();
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
@@ -56,9 +68,10 @@ namespace OpenRA.Mods.RA
var unit = self.World.CreateActor(false, u.ToLowerInvariant(),
new TypeDictionary { new OwnerInit(self.Owner) });
if (CanLoad(self, unit))
Load(self, unit);
cargo.Add(unit);
}
totalWeight = cargo.Sum(c => GetWeight(c));
}
}
@@ -131,6 +144,7 @@ namespace OpenRA.Mods.RA
foreach (var npe in self.TraitsImplementing<INotifyPassengerExited>())
npe.PassengerExited(self, a);
a.Trait<Passenger>().Transport = null;
return a;
}
@@ -165,6 +179,8 @@ namespace OpenRA.Mods.RA
foreach (var npe in self.TraitsImplementing<INotifyPassengerEntered>())
npe.PassengerEntered(self, a);
a.Trait<Passenger>().Transport = self;
}
public void Killed(Actor self, AttackInfo e)
@@ -186,8 +202,22 @@ namespace OpenRA.Mods.RA
});
}
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.CenterPosition.ToCPos();
if (currentCell != cell)
{
@@ -200,12 +230,21 @@ namespace OpenRA.Mods.RA
public interface INotifyPassengerEntered { void PassengerEntered(Actor self, Actor passenger); }
public interface INotifyPassengerExited { void PassengerExited(Actor self, Actor passenger); }
public class CargoInit : IActorInit<Actor[]>
public class RuntimeCargoInit : IActorInit<Actor[]>
{
[FieldFromYamlKey]
public readonly Actor[] value = { };
public CargoInit() { }
public CargoInit(Actor[] init) { value = init; }
public RuntimeCargoInit() { }
public RuntimeCargoInit(Actor[] init) { value = init; }
public Actor[] Value(World world) { return value; }
}
public class CargoInit : IActorInit<string[]>
{
[FieldFromYamlKey]
public readonly string[] value = { };
public CargoInit() { }
public CargoInit(string[] init) { value = init; }
public string[] Value(World world) { return value; }
}
}

View File

@@ -23,13 +23,15 @@ namespace OpenRA.Mods.RA
public class CombatDebugOverlay : IPostRender
{
Lazy<IEnumerable<Armament>> armaments;
Lazy<AttackBase> attack;
Lazy<IBodyOrientation> coords;
Lazy<Health> health;
DeveloperMode devMode;
public CombatDebugOverlay(Actor self)
{
armaments = Lazy.New(() => self.TraitsImplementing<Armament>());
attack = Lazy.New(() => self.TraitOrDefault<AttackBase>());
coords = Lazy.New(() => self.Trait<IBodyOrientation>());
health = Lazy.New(() => self.TraitOrDefault<Health>());
var localPlayer = self.World.LocalPlayer;
@@ -44,10 +46,35 @@ namespace OpenRA.Mods.RA
if (health.Value != null)
wr.DrawRangeCircle(self.CenterPosition, health.Value.Info.Radius, Color.Red);
// No armaments to draw
if (attack.Value == null)
return;
var wlr = Game.Renderer.WorldLineRenderer;
var c = Color.White;
foreach (var a in armaments.Value)
// Fire ports on garrisonable structures
var garrison = attack.Value as AttackGarrisoned;
if (garrison != null)
{
var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation);
foreach (var p in garrison.Ports)
{
var pos = self.CenterPosition + coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation));
var da = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw + p.Cone)).Rotate(bodyOrientation));
var db = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw - p.Cone)).Rotate(bodyOrientation));
var o = wr.ScreenPosition(pos);
var a = wr.ScreenPosition(pos + da * 224 / da.Length);
var b = wr.ScreenPosition(pos + db * 224 / db.Length);
wlr.DrawLine(o, a, c, c);
wlr.DrawLine(o, b, c, c);
}
return;
}
foreach (var a in attack.Value.Armaments)
{
foreach (var b in a.Barrels)
{

View File

@@ -485,6 +485,8 @@
<Compile Include="World\BuildableTerrainLayer.cs" />
<Compile Include="Buildings\LaysTerrain.cs" />
<Compile Include="RemoveImmediately.cs" />
<Compile Include="Attack\AttackFollow.cs" />
<Compile Include="Attack\AttackGarrisoned.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -30,6 +30,7 @@ namespace OpenRA.Mods.RA
{
public readonly PassengerInfo info;
public Passenger( PassengerInfo info ) { this.info = info; }
public Actor Transport;
public IEnumerable<IOrderTargeter> Orders
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* 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,
@@ -8,25 +8,39 @@
*/
#endregion
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Render
{
class RenderUnitReloadInfo : RenderUnitInfo
class RenderUnitReloadInfo : RenderUnitInfo, Requires<ArmamentInfo>, Requires<AttackBaseInfo>
{
public override object Create(ActorInitializer init) { return new RenderUnitReload(init.self); }
[Desc("Armament name")]
public readonly string Armament = "primary";
public override object Create(ActorInitializer init) { return new RenderUnitReload(init.self, this); }
}
class RenderUnitReload : RenderUnit
{
public RenderUnitReload(Actor self)
: base(self) { }
readonly AttackBase attack;
readonly Armament armament;
public RenderUnitReload(Actor self, RenderUnitReloadInfo info)
: base(self)
{
attack = self.Trait<AttackBase>();
armament = self.TraitsImplementing<Armament>()
.Single(a => a.Info.Name == info.Armament);
}
public override void Tick(Actor self)
{
var attack = self.TraitOrDefault<AttackBase>();
var sequence = (armament.IsReloading ? "empty-" : "") + (attack.IsAttacking ? "aim" : "idle");
if (sequence != anim.CurrentSequence.Name)
anim.ReplaceAnim(sequence);
if (attack != null)
anim.ReplaceAnim((attack.IsReloading() ? "empty-" : "")
+ (attack.IsAttacking ? "aim" : "idle"));
base.Tick(self);
}
}

View File

@@ -19,67 +19,56 @@ namespace OpenRA.Mods.RA.Render
{
class WithMuzzleFlashInfo : ITraitInfo, Requires<RenderSpritesInfo>, Requires<AttackBaseInfo>, Requires<ArmamentInfo>
{
[Desc("Sequence name to use")]
public readonly string Sequence = "muzzle";
[Desc("Armament name")]
public readonly string Armament = "primary";
[Desc("Are the muzzle facings split into multiple shps?")]
public readonly bool SplitFacings = false;
[Desc("Number of separate facing images that are defined in the sequences.")]
public readonly int FacingCount = 8;
public object Create(ActorInitializer init) { return new WithMuzzleFlash(init.self, this); }
public object Create(ActorInitializer init) { return new WithMuzzleFlash(init.self); }
}
class WithMuzzleFlash : INotifyAttack, IRender, ITick
{
readonly WithMuzzleFlashInfo info;
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)
public WithMuzzleFlash(Actor self)
{
this.info = info;
var render = self.Trait<RenderSprites>();
var facing = self.TraitOrDefault<IFacing>();
var arm = self.TraitsImplementing<Armament>()
.Single(a => a.Info.Name == info.Armament);
foreach (var b in arm.Barrels)
foreach (var arm in self.TraitsImplementing<Armament>())
{
var barrel = b;
var turreted = self.TraitsImplementing<Turreted>()
.FirstOrDefault(t => t.Name == arm.Info.Turret);
// Skip armaments that don't define muzzles
if (arm.Info.MuzzleSequence == null)
continue;
getFacing = turreted != null ? () => turreted.turretFacing :
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
foreach (var b in arm.Barrels)
{
var barrel = b;
var turreted = self.TraitsImplementing<Turreted>()
.FirstOrDefault(t => t.Name == arm.Info.Turret);
var muzzleFlash = new Animation(render.GetImage(self), getFacing);
visible.Add(barrel, false);
anims.Add(barrel,
new AnimationWithOffset(muzzleFlash,
() => arm.MuzzleOffset(self, barrel),
() => !visible[barrel],
p => WithTurret.ZOffsetFromCenter(self, p, 2)));
getFacing = turreted != null ? () => turreted.turretFacing :
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
var muzzleFlash = new Animation(render.GetImage(self), getFacing);
visible.Add(barrel, false);
anims.Add(barrel,
new AnimationWithOffset(muzzleFlash,
() => arm.MuzzleOffset(self, barrel),
() => !visible[barrel],
p => WithTurret.ZOffsetFromCenter(self, p, 2)));
}
}
}
public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
if (a.Info.Name != info.Armament)
var sequence = a.Info.MuzzleSequence;
if (sequence == null)
return;
if (a.Info.MuzzleSplitFacings > 0)
sequence += Traits.Util.QuantizeFacing(getFacing(), a.Info.MuzzleSplitFacings).ToString();
visible[barrel] = true;
var sequence = info.Sequence;
if (info.SplitFacings)
sequence += Traits.Util.QuantizeFacing(getFacing(), info.FacingCount).ToString();
anims[barrel].Animation.PlayThen(sequence, () => visible[barrel] = false);
}

View File

@@ -10,6 +10,7 @@
using System.Drawing;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -20,15 +21,23 @@ namespace OpenRA.Mods.RA
void Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition);
}
class RenderRangeCircleInfo : ITraitInfo, IPlaceBuildingDecoration
class RenderRangeCircleInfo : ITraitInfo, IPlaceBuildingDecoration, Requires<AttackBaseInfo>
{
public readonly string RangeCircleType = null;
[Desc("Range to draw if no armaments are available")]
public readonly WRange FallbackRange = WRange.Zero;
public void Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition)
{
var range = ai.Traits.WithInterface<ArmamentInfo>()
.Select(a => Rules.Weapons[a.Weapon.ToLowerInvariant()].Range)
.Max();
var armaments = ai.Traits.WithInterface<ArmamentInfo>();
var range = FallbackRange;
if (armaments.Any())
range = armaments.Select(a => Rules.Weapons[a.Weapon.ToLowerInvariant()].Range).Max();
if (range == WRange.Zero)
return;
wr.DrawRangeCircleWithContrast(
centerPosition,
@@ -49,8 +58,13 @@ namespace OpenRA.Mods.RA
class RenderRangeCircle : IPostRenderSelection
{
Actor self;
AttackBase attack;
public RenderRangeCircle(Actor self) { this.self = self; }
public RenderRangeCircle(Actor self)
{
this.self = self;
attack = self.Trait<AttackBase>();
}
public void RenderAfterWorld(WorldRenderer wr)
{
@@ -59,7 +73,7 @@ namespace OpenRA.Mods.RA
wr.DrawRangeCircleWithContrast(
self.CenterPosition,
self.Trait<AttackBase>().GetMaximumRange(),
attack.GetMaximumRange(),
Color.FromArgb(128, Color.Yellow),
Color.FromArgb(96, Color.Black)
);

View File

@@ -174,6 +174,54 @@ namespace OpenRA.Utility
i.Value.Nodes.Add(new MiniYamlNode("OccupiesSpace", "false"));
}
// Armaments and muzzleflashes were reworked to support garrisoning
if (engineVersion < 20140321)
{
if (depth == 0)
{
var muzzles = node.Value.Nodes.Where(n => n.Key.StartsWith("WithMuzzleFlash"));
var armaments = node.Value.Nodes.Where(n => n.Key.StartsWith("Armament"));
// Shift muzzle flash definitions to Armament
foreach (var m in muzzles)
{
var muzzleArmNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Armament");
var muzzleSequenceNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Sequence");
var muzzleSplitFacingsNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "SplitFacings");
var muzzleFacingsCountNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "FacingCount");
var muzzleArmName = muzzleArmNode != null ? muzzleArmNode.Value.Value.Trim() : "primary";
var muzzleSequence = muzzleSequenceNode != null ? muzzleSequenceNode.Value.Value.Trim() : "muzzle";
var muzzleSplitFacings = muzzleSplitFacingsNode != null ? FieldLoader.GetValue<bool>("SplitFacings", muzzleSplitFacingsNode.Value.Value) : false;
var muzzleFacingsCount = muzzleFacingsCountNode != null ? FieldLoader.GetValue<int>("FacingsCount", muzzleFacingsCountNode.Value.Value) : 8;
foreach (var a in armaments)
{
var armNameNode = m.Value.Nodes.SingleOrDefault(n => n.Key == "Name");
var armName = armNameNode != null ? armNameNode.Value.Value.Trim() : "primary";
if (muzzleArmName == armName)
{
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSequence", muzzleSequence));
if (muzzleSplitFacings)
a.Value.Nodes.Add(new MiniYamlNode("MuzzleSplitFacings", muzzleFacingsCount.ToString()));
}
}
}
foreach (var m in muzzles.ToList().Skip(1))
node.Value.Nodes.Remove(m);
}
// Remove all but the first muzzle flash definition
if (depth == 1 && node.Key.StartsWith("WithMuzzleFlash"))
{
node.Key = "WithMuzzleFlash";
node.Value.Nodes.RemoveAll(n => n.Key == "Armament");
node.Value.Nodes.RemoveAll(n => n.Key == "Sequence");
}
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}

View File

@@ -68,10 +68,12 @@ HELI:
Armament@PRIMARY:
Weapon: HeliAGGun
LocalOffset: 128,-213,-85, 128,213,-85
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: HeliAAGun
LocalOffset: 128,-213,-85, 128,213,-85
MuzzleSequence: muzzle
AttackHeli:
FacingTolerance: 20
LimitedAmmo:
@@ -83,9 +85,7 @@ HELI:
RenderUnit:
WithRotor:
Offset: 0,0,85
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash@SECONDARY:
Armament: secondary
WithMuzzleFlash:
WithShadow:
LeavesHusk:
HuskActor: HELI.Husk
@@ -197,14 +197,14 @@ A10:
RenderUnit:
WithShadow:
AttackBomber:
Armaments: gun, bombs
Guns: gun
Bombs: bombs
Armament@GUNS:
Name: gun
Weapon: Vulcan
LocalOffset: 1024,0,-85
WithMuzzleFlash@SECONDARY:
Armament: gun
WithMuzzleFlash:
Armament@BOMBS:
Name: bombs
Weapon: Napalm

View File

@@ -441,6 +441,8 @@ VICE:
Armament:
Weapon: Chemspray
LocalOffset: 384,0,0
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
AttackFrontal:
AttackWander:
RenderUnit:

View File

@@ -103,6 +103,8 @@ E4:
Weapon: Flamethrower
LocalOffset: 341,0,256
FireDelay: 3
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
AttackFrontal:
WithMuzzleFlash:
SplitFacings: true
@@ -138,6 +140,8 @@ E5:
Weapon: Chemspray
LocalOffset: 341,0,256
FireDelay: 3
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
AttackFrontal:
WithMuzzleFlash:
SplitFacings: true

View File

@@ -524,6 +524,7 @@ GUN:
Armament:
Weapon: TurretGun
LocalOffset: 512,0,112
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
AutoTarget:
@@ -566,6 +567,7 @@ SAM:
RenderBuildingTurreted:
Armament:
Weapon: SAMMissile
MuzzleSequence: muzzle
AttackPopupTurreted:
WithMuzzleFlash:
AutoTarget:
@@ -639,6 +641,7 @@ GTWR:
Armament:
Weapon: HighV
LocalOffset: 256,0,256
MuzzleSequence: muzzle
AttackTurreted:
BodyOrientation:
QuantizedFacings: 8

View File

@@ -98,16 +98,16 @@ APC:
Recoil: 96
RecoilRecovery: 18
LocalOffset: 85,85,299, 85,-85,299
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: APCGun.AA
Recoil: 96
RecoilRecovery: 18
LocalOffset: 85,85,299, 85,-85,299
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash@SECONDARY:
Armament: secondary
WithMuzzleFlash:
RenderUnit:
WithTurret:
AutoTarget:
@@ -141,6 +141,7 @@ ARTY:
Armament:
Weapon: ArtilleryShell
LocalOffset: 624,0,208
MuzzleSequence: muzzle
AttackFrontal:
WithMuzzleFlash:
RenderUnit:
@@ -175,6 +176,8 @@ FTNK:
Armament:
Weapon: BigFlamer
LocalOffset: 512,128,42, 512,-128,42
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
AttackFrontal:
RenderUnit:
AutoTarget:
@@ -212,6 +215,7 @@ BGGY:
Armament:
Weapon: MachineGun
LocalOffset: 171,0,43
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -283,6 +287,7 @@ JEEP:
Armament:
Weapon: MachineGun
LocalOffset: 171,0,85
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -318,6 +323,7 @@ LTNK:
Recoil: 85
RecoilRecovery: 17
LocalOffset: 720,0,90
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -355,6 +361,7 @@ MTNK:
Recoil: 128
RecoilRecovery: 26
LocalOffset: 768,0,90
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -396,14 +403,16 @@ HTNK:
LocalOffset: 900,180,340, 900,-180,340
Recoil: 170
RecoilRecovery: 42
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: MammothMissiles
LocalOffset: -85,384,340, -85,-384,340
LocalYaw: -100, 100
Recoil: 42
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash:
RenderUnit:
WithTurret:
AutoTarget:

View File

@@ -190,6 +190,7 @@ DEVAST:
Armament:
Weapon: DevBullet
LocalOffset: 256,0,32
MuzzleSequence: muzzle
AttackFrontal:
WithMuzzleFlash:
AutoTarget:

View File

@@ -172,6 +172,7 @@ RAIDER:
Armament:
Weapon: HMGo
LocalOffset: 256,0,128
MuzzleSequence: muzzle
AttackFrontal:
AutoTarget:
Explodes:
@@ -206,6 +207,7 @@ STEALTHRAIDER:
Armament:
Weapon: HMGo
LocalOffset: 256,0,128
MuzzleSequence: muzzle
AttackFrontal:
Explodes:
Weapon: UnitExplodeTiny

View File

@@ -471,6 +471,7 @@ WALL:
Armament:
Weapon: TurretGun
LocalOffset: 448,0,128
MuzzleSequence: muzzle
AttackTurreted:
AutoTarget:
RenderDetectionCircle:

View File

@@ -130,6 +130,7 @@ TRIKE:
Armament:
Weapon: HMG
LocalOffset: -416,0,0
MuzzleSequence: muzzle
AttackFrontal:
AutoTarget:
Explodes:
@@ -218,6 +219,7 @@ QUAD.starport:
Recoil: 128
RecoilRecovery: 32
LocalOffset: 256,0,0
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -267,6 +269,7 @@ SIEGETANK:
Recoil: 150
RecoilRecovery: 19
LocalOffset: 512,0,320
MuzzleSequence: muzzle
AttackFrontal:
WithMuzzleFlash:
RenderUnit:

View File

@@ -147,10 +147,12 @@ YAK:
Armament@PRIMARY:
Weapon: ChainGun.Yak
LocalOffset: 256,-213,0
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: ChainGun.Yak
LocalOffset: 256,213,0
MuzzleSequence: muzzle
AttackPlane:
FacingTolerance: 20
Plane:
@@ -171,9 +173,7 @@ YAK:
ReloadTicks: 11
IronCurtainable:
ReturnOnIdle:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash@SECONDARY:
Armament: secondary
WithMuzzleFlash:
Contrail:
Offset: -853,0,0
LeavesHusk:
@@ -295,10 +295,12 @@ HIND:
Armament@PRIMARY:
Weapon: ChainGun
LocalOffset: 85,-213,-85
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: ChainGun
LocalOffset: 85,213,-85
MuzzleSequence: muzzle
AttackHeli:
FacingTolerance: 20
Helicopter:
@@ -321,9 +323,7 @@ HIND:
IronCurtainable:
Selectable:
Bounds: 38,32,0,0
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash@SECONDARY:
Armament: secondary
WithMuzzleFlash:
LeavesHusk:
HuskActor: HIND.Husk
SmokeTrailWhenDamaged:

View File

@@ -47,8 +47,12 @@ E1:
HP: 50
Mobile:
Speed: 56
Armament:
Armament@PRIMARY:
Weapon: M1Carbine
Armament@GARRISONED:
Name: garrisoned
Weapon: Vulcan
MuzzleSequence: garrison-muzzle
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -75,10 +79,14 @@ E2:
HP: 50
Mobile:
Speed: 71
Armament:
Armament@PRIMARY:
Weapon: Grenade
LocalOffset: 0,0,555
FireDelay: 15
Armament@GARRISONED:
Name: garrisoned
Weapon: Grenade
FireDelay: 15
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -113,6 +121,9 @@ E3:
Armament@SECONDARY:
Weapon: Dragon
LocalOffset: 0,0,555
Armament@GARRISONED:
Name: garrisoned
Weapon: Dragon
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -139,10 +150,13 @@ E4:
HP: 40
Mobile:
Speed: 56
Armament:
Armament@PRIMARY:
Weapon: Flamer
LocalOffset: 427,0,341
FireDelay: 8
Armament@GARRISONED:
Name: garrisoned
Weapon: Flamer
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -252,6 +266,10 @@ E7:
Weapon: Colt45
Armament@SECONDARY:
Weapon: Colt45
Armament@GARRISONED:
Name: garrisoned
Weapon: Colt45
MuzzleSequence: garrison-muzzle
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -429,9 +447,12 @@ SHOK:
Speed: 56
RevealsShroud:
Range: 4c0
Armament:
Armament@PRIMARY:
Weapon: PortaTesla
LocalOffset: 427,0,341
Armament@GARRISONED:
Name: garrisoned
Weapon: PortaTesla
AttackFrontal:
TakeCover:
-RenderInfantry:
@@ -465,8 +486,12 @@ SNIPER:
Range: 6c0
AutoTarget:
InitialStance: ReturnFire
Armament:
Armament@PRIMARY:
Weapon: Sniper
Armament@GARRISONED:
Name: garrisoned
Weapon: Sniper
MuzzleSequence: garrison-muzzle
AttackFrontal:
TakeCover:
-RenderInfantry:

View File

@@ -181,6 +181,7 @@ CA:
LocalOffset: 480,-100,40, 480,100,40
Recoil: 171
RecoilRecovery: 34
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Turret: secondary
@@ -188,10 +189,9 @@ CA:
LocalOffset: 480,-100,40, 480,100,40
Recoil: 171
RecoilRecovery: 34
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash@SECONDARY:
Armament: secondary
WithMuzzleFlash:
Selectable:
Bounds: 44,44
RenderUnit:
@@ -262,11 +262,13 @@ PT:
Armament@PRIMARY:
Weapon: 2Inch
LocalOffset: 208,0,48
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: DepthCharge
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash:
Selectable:
Bounds: 32,32
RenderUnit:

View File

@@ -351,6 +351,7 @@ AGUN:
Armament:
Weapon: ZSU-23
LocalOffset: 432,150,-30, 432,-150,-30
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
AutoTarget:
@@ -400,9 +401,15 @@ DOME:
PBOX:
Inherits: ^Building
Tooltip:
Name: Pillbox (Unarmed)
Name: Pillbox
Building:
Power: -15
Buildable:
Queue: Defense
BuildPaletteOrder: 20
Prerequisites: tent
Owner: allies
Hotkey: p
-GivesBuildableArea:
Valued:
Cost: 400
@@ -422,133 +429,67 @@ PBOX:
Types: Infantry
MaxWeight: 1
PipCount: 1
InitialUnits: e1
-EmitInfantryOnSell:
DrawLineToTarget:
TransformOnPassenger@e1:
PassengerTypes: e1
OnEnter: pbox.e1
OnExit: pbox
SkipMakeAnims: true
TransformOnPassenger@e3:
PassengerTypes: e3
OnEnter: pbox.e3
OnExit: pbox
SkipMakeAnims: true
TransformOnPassenger@e4:
PassengerTypes: e4
OnEnter: pbox.e4
OnExit: pbox
SkipMakeAnims: true
TransformOnPassenger@e7:
PassengerTypes: e7
OnEnter: pbox.e7
OnExit: pbox
SkipMakeAnims: true
TransformOnPassenger@SHOK:
PassengerTypes: shok
OnEnter: pbox.shok
OnExit: pbox
SkipMakeAnims: true
TransformOnPassenger@Sniper:
PassengerTypes: sniper
OnEnter: pbox.sniper
OnExit: pbox
SkipMakeAnims: true
AttackGarrisoned:
Armaments: garrisoned
PortOffsets: 384,0,128, 224,-341,128, -224,-341,128, -384,0,128, -224,341,128, 224,341,128
PortYaws: 0, 176, 341, 512, 682, 853
PortCones: 86, 86, 86, 86, 86, 86
RenderRangeCircle:
FallbackRange: 6c0
AutoTarget:
# Legacy definitions to keep compatibility with outdated maps
PBOX.E1:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
PBOX.E3:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
PBOX.E4:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
PBOX.E7:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
PBOX.SHOK:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
PBOX.SNIPER:
Inherits: PBOX
-Buildable:
RenderBuilding:
Image: PBOX
HBOX:
Inherits: ^Building
Tooltip:
Name: Camo Pillbox
Building:
Power: -15
Buildable:
Queue: Defense
BuildPaletteOrder: 20
Prerequisites: tent
Owner: allies
Hotkey: p
Tooltip:
Name: Pillbox (Guns)
Description: Basic defensive structure.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Vulcan
LocalOffset: 384,0,88
AttackTurreted:
WithMuzzleFlash:
Cargo:
InitialUnits: e1
PBOX.E3:
Inherits: PBOX
Tooltip:
Name: Pillbox (Rockets)
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Dragon
LocalOffset: 384,0,88
AttackTurreted:
PBOX.E4:
Inherits: PBOX
Tooltip:
Name: Pillbox (Flamethrower)
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Flamer
LocalOffset: 384,0,88
AttackTurreted:
PBOX.E7:
Inherits: PBOX
Tooltip:
Name: Pillbox (Tanya)
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Colt45
LocalOffset: 384,0,88
AttackTurreted:
PBOX.SHOK:
Inherits: PBOX
Tooltip:
Name: Pillbox (Tesla)
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: PortaTesla
LocalOffset: 384,0,88
AttackTurreted:
PBOX.SNIPER:
Inherits: PBOX
Tooltip:
Name: Pillbox (Sniper)
RenderBuilding:
Image: PBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Sniper
LocalOffset: 384,0,88
AttackTurreted:
HBOX:
Inherits: ^Building
Tooltip:
Name: Camo Pillbox (Unarmed)
Building:
Power: -15
Hotkey: l
-GivesBuildableArea:
Valued:
Cost: 600
@@ -571,128 +512,56 @@ HBOX:
Types: Infantry
MaxWeight: 1
PipCount: 1
InitialUnits: e1
-EmitInfantryOnSell:
DrawLineToTarget:
TransformOnPassenger@e1:
PassengerTypes: e1
OnEnter: HBOX.e1
OnExit: HBOX
SkipMakeAnims: true
TransformOnPassenger@e3:
PassengerTypes: e3
OnEnter: HBOX.e3
OnExit: HBOX
SkipMakeAnims: true
TransformOnPassenger@e4:
PassengerTypes: e4
OnEnter: HBOX.e4
OnExit: HBOX
SkipMakeAnims: true
TransformOnPassenger@e7:
PassengerTypes: e7
OnEnter: HBOX.e7
OnExit: HBOX
SkipMakeAnims: true
TransformOnPassenger@SHOK:
PassengerTypes: shok
OnEnter: HBOX.shok
OnExit: HBOX
SkipMakeAnims: true
TransformOnPassenger@Sniper:
PassengerTypes: sniper
OnEnter: HBOX.sniper
OnExit: HBOX
SkipMakeAnims: true
DetectCloaked:
Range: 6
RenderRangeCircle:
FallbackRange: 6c0
AutoTarget:
AttackGarrisoned:
Armaments: garrisoned
PortOffsets: 384,0,128, 224,-341,128, -224,-341,128, -384,0,128, -224,341,128, 224,341,128
PortYaws: 0, 176, 341, 512, 682, 853
PortCones: 86, 86, 86, 86, 86, 86
# Legacy definitions to keep compatibility with outdated maps
HBOX.E1:
Inherits: HBOX
Buildable:
Queue: Defense
BuildPaletteOrder: 20
Prerequisites: tent
Owner: allies
Hotkey: l
Tooltip:
Name: Camo Pillbox (Guns)
Description: Hidden defensive structure.\nCan detect cloaked units.\n Strong vs Infantry, Light Vehicles\n Weak vs Tanks, Aircraft
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Vulcan
LocalOffset: 400,0,48
AttackTurreted:
WithMuzzleFlash:
Cargo:
InitialUnits: e1
HBOX.E3:
Inherits: HBOX
Tooltip:
Name: Camo Pillbox (Rockets)
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Dragon
LocalOffset: 400,0,48
AttackTurreted:
HBOX.E4:
Inherits: HBOX
Tooltip:
Name: Camo Pillbox (Flamethrower)
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Flamer
LocalOffset: 400,0,48
AttackTurreted:
HBOX.E7:
Inherits: HBOX
Tooltip:
Name: Camo Pillbox (Tanya)
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Colt45
LocalOffset: 400,0,48
AttackTurreted:
HBOX.SHOK:
Inherits: HBOX
Tooltip:
Name: Camo Pillbox (Tesla)
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: PortaTesla
LocalOffset: 400,0,48
AttackTurreted:
HBOX.SNIPER:
Inherits: HBOX
Tooltip:
Name: Camo Pillbox (Sniper)
-Buildable:
RenderBuilding:
Image: HBOX
RenderRangeCircle:
AutoTarget:
Armament:
Weapon: Sniper
LocalOffset: 400,0,48
AttackTurreted:
GUN:
Inherits: ^Building
@@ -723,6 +592,7 @@ GUN:
Armament:
Weapon: TurretGun
LocalOffset: 512,0,112
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
AutoTarget:
@@ -802,6 +672,7 @@ SAM:
RenderBuildingTurreted:
Armament:
Weapon: Nike
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
AutoTarget:

View File

@@ -55,6 +55,7 @@ V2RL:
Recoil: 85
RecoilRecovery: 25
LocalOffset: 768,0,90
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -95,6 +96,7 @@ V2RL:
Recoil: 128
RecoilRecovery: 38
LocalOffset: 720,0,80
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -137,6 +139,7 @@ V2RL:
Recoil: 128
RecoilRecovery: 38
LocalOffset: 768,85,90, 768,-85,90
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -181,14 +184,16 @@ V2RL:
LocalOffset: 900,180,340, 900,-180,340
Recoil: 171
RecoilRecovery: 30
MuzzleSequence: muzzle
Armament@SECONDARY:
Name: secondary
Weapon: MammothTusk
LocalOffset: -85,384,340, -85,-384,340
LocalYaw: -100,100
Recoil: 43
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash@PRIMARY:
WithMuzzleFlash:
RenderUnit:
WithTurret:
AutoTarget:
@@ -230,6 +235,7 @@ ARTY:
Armament:
Weapon: 155mm
LocalOffset: 624,0,208
MuzzleSequence: muzzle
AttackFrontal:
WithMuzzleFlash:
RenderUnit:
@@ -344,6 +350,7 @@ JEEP:
Offset: 0,0,85
Armament:
Weapon: M60mg
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -377,6 +384,7 @@ APC:
Armament:
Weapon: M60mg
LocalOffset: 0,0,171
MuzzleSequence: muzzle
AttackFrontal:
RenderUnit:
WithMuzzleFlash:
@@ -608,6 +616,7 @@ FTRK:
Weapon: FLAK-23
Recoil: 85
LocalOffset: 512,0,192
MuzzleSequence: muzzle
AttackTurreted:
WithMuzzleFlash:
RenderUnit:
@@ -717,6 +726,7 @@ QTNK:
-EjectOnDeath:
TargetableUnit:
TargetTypes: Ground, MADTank
STNK:
Inherits: ^Vehicle
Buildable:
@@ -761,3 +771,4 @@ STNK:
UncloakOnUnload: True
DetectCloaked:
Range: 6

View File

@@ -64,6 +64,10 @@ e1:
Start: 0
Length: 6
Tick: 1600
garrison-muzzle: minigun
Start: 0
Length: 6
Facings: 8
icon: e1icon
Start: 0
@@ -132,6 +136,11 @@ sniper:
Start: 0
Length: 6
Tick: 1600
garrison-muzzle: minigun
Start: 0
Length: 3
Stride: 6
Facings: 8
icon: snipericon
Start: 0
@@ -644,6 +653,11 @@ e7:
Start: 176
Length: 7
Facings: 8
garrison-muzzle: minigun
Start: 0
Length: 3
Stride: 6
Facings: 8
icon: e7icon
Start: 0