diff --git a/CHANGELOG b/CHANGELOG index c148115911..bceae93927 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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: diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 6085f8d3ad..3af9019b68 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -123,7 +123,7 @@ namespace OpenRA.Graphics // added for contrails foreach (var a in world.ActorsWithTrait()) - if (!a.Actor.Destroyed) + if (a.Actor.IsInWorld && !a.Actor.Destroyed) a.Trait.RenderAfterWorld(this, a.Actor); if (world.OrderGenerator != null) diff --git a/OpenRA.Mods.RA/Activities/Transform.cs b/OpenRA.Mods.RA/Activities/Transform.cs index 77d9656bff..bfbeed797e 100644 --- a/OpenRA.Mods.RA/Activities/Transform.cs +++ b/OpenRA.Mods.RA/Activities/Transform.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.RA.Activities var cargo = self.TraitOrDefault(); if (cargo != null) - init.Add( new CargoInit( cargo.Passengers.ToArray() ) ); + init.Add( new RuntimeCargoInit( cargo.Passengers.ToArray() ) ); var a = w.CreateActor( ToActor, init ); diff --git a/OpenRA.Mods.RA/Armament.cs b/OpenRA.Mods.RA/Armament.cs index f58b982bf0..9d669e4a26 100644 --- a/OpenRA.Mods.RA/Armament.cs +++ b/OpenRA.Mods.RA/Armament.cs @@ -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 Turret; Lazy Coords; + Lazy limitedAmmo; + List> delayedActions = new List>(); 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().FirstOrDefault(t => t.Name == info.Turret)); Coords = Lazy.New(() => self.Trait()); + limitedAmmo = Lazy.New(() => self.TraitOrDefault()); 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(); - 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; } } } } diff --git a/OpenRA.Mods.RA/Attack/AttackBase.cs b/OpenRA.Mods.RA/Attack/AttackBase.cs index 1ef88832f4..41df7f8abd 100644 --- a/OpenRA.Mods.RA/Attack/AttackBase.cs +++ b/OpenRA.Mods.RA/Attack/AttackBase.cs @@ -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 Armaments { get { return GetArmaments(); } } + protected Lazy facing; + protected Lazy building; + protected Func> GetArmaments; readonly Actor self; readonly AttackBaseInfo info; - protected Lazy facing; - Lazy> armaments; - protected IEnumerable Armaments { get { return armaments.Value; } } - List> delayedActions = new List>(); - public AttackBase(Actor self, AttackBaseInfo info) { this.self = self; this.info = info; - armaments = Lazy.New(() => self.TraitsImplementing()); + var armaments = Lazy.New(() => self.TraitsImplementing() + .Where(a => info.Armaments.Contains(a.Info.Name))); + + GetArmaments = () => armaments.Value; + facing = Lazy.New(() => self.TraitOrDefault()); + building = Lazy.New(() => self.TraitOrDefault()); } 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 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)); } diff --git a/OpenRA.Mods.RA/Attack/AttackCharge.cs b/OpenRA.Mods.RA/Attack/AttackCharge.cs index a566d89bc0..35fa06a31d 100644 --- a/OpenRA.Mods.RA/Attack/AttackCharge.cs +++ b/OpenRA.Mods.RA/Attack/AttackCharge.cs @@ -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(); - 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().InitialChargeDelay; - - var attack = self.Trait(); + if (attack.charges == 0 || !attack.CanAttack(self, target)) return this; self.Trait().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().ChargeDelay; - - var attack = self.Trait(); 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); } } } diff --git a/OpenRA.Mods.RA/Attack/AttackFollow.cs b/OpenRA.Mods.RA/Attack/AttackFollow.cs new file mode 100644 index 0000000000..248fd77a79 --- /dev/null +++ b/OpenRA.Mods.RA/Attack/AttackFollow.cs @@ -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(); + move = allowMove ? self.TraitOrDefault() : 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; + } + } + } +} diff --git a/OpenRA.Mods.RA/Attack/AttackGarrisoned.cs b/OpenRA.Mods.RA/Attack/AttackGarrisoned.cs new file mode 100644 index 0000000000..254e5391a6 --- /dev/null +++ b/OpenRA.Mods.RA/Attack/AttackGarrisoned.cs @@ -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 + { + [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 coords; + List armaments; + List muzzles; + Dictionary paxFacing; + Dictionary paxPos; + Dictionary paxRender; + + + public AttackGarrisoned(Actor self, AttackGarrisonedInfo info) + : base(self, info) + { + this.info = info; + coords = Lazy.New(() => self.Trait()); + armaments = new List(); + muzzles = new List(); + paxFacing = new Dictionary(); + paxPos = new Dictionary(); + paxRender = new Dictionary(); + + 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(); + 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()); + paxPos.Add(passenger, passenger.Trait()); + paxRender.Add(passenger, passenger.Trait()); + armaments = armaments.Append(passenger.TraitsImplementing() + .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 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(); + } + } +} diff --git a/OpenRA.Mods.RA/Attack/AttackLoyalty.cs b/OpenRA.Mods.RA/Attack/AttackLoyalty.cs index 28537fc62a..b9695347e1 100644 --- a/OpenRA.Mods.RA/Attack/AttackLoyalty.cs +++ b/OpenRA.Mods.RA/Attack/AttackLoyalty.cs @@ -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(); 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); diff --git a/OpenRA.Mods.RA/Attack/AttackOmni.cs b/OpenRA.Mods.RA/Attack/AttackOmni.cs index 03ae111b4a..517527e5a3 100644 --- a/OpenRA.Mods.RA/Attack/AttackOmni.cs +++ b/OpenRA.Mods.RA/Attack/AttackOmni.cs @@ -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() && !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; } } diff --git a/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs b/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs index 3cc3c05bb7..781a6b38f5 100644 --- a/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs +++ b/OpenRA.Mods.RA/Attack/AttackPopupTurreted.cs @@ -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(); turret = turrets.FirstOrDefault(); rb = init.self.Trait(); + skippedMakeAnimation = init.Contains(); } 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) diff --git a/OpenRA.Mods.RA/Attack/AttackTurreted.cs b/OpenRA.Mods.RA/Attack/AttackTurreted.cs index 052bc67486..dee42a5761 100644 --- a/OpenRA.Mods.RA/Attack/AttackTurreted.cs +++ b/OpenRA.Mods.RA/Attack/AttackTurreted.cs @@ -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 + class AttackTurretedInfo : AttackFollowInfo, Requires { 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 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() && !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(); - 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(); - - if (allowMove && mobile != null && !mobile.Info.OnRails) - return Util.SequenceActivities(mobile.MoveFollow(self, target, range), this); - } - - return NextActivity; - } + return canAttack; } } } diff --git a/OpenRA.Mods.RA/AttackBomber.cs b/OpenRA.Mods.RA/AttackBomber.cs index f39df8820c..32716e57ad 100644 --- a/OpenRA.Mods.RA/AttackBomber.cs +++ b/OpenRA.Mods.RA/AttackBomber.cs @@ -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); } } diff --git a/OpenRA.Mods.RA/AutoTarget.cs b/OpenRA.Mods.RA/AutoTarget.cs index 2d59f5bcfe..81760d8746 100644 --- a/OpenRA.Mods.RA/AutoTarget.cs +++ b/OpenRA.Mods.RA/AutoTarget.cs @@ -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(); + 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) diff --git a/OpenRA.Mods.RA/Buildings/Building.cs b/OpenRA.Mods.RA/Buildings/Building.cs index 5ead5ea237..23ee8e5304 100755 --- a/OpenRA.Mods.RA/Buildings/Building.cs +++ b/OpenRA.Mods.RA/Buildings/Building.cs @@ -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(); } 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) { } } } diff --git a/OpenRA.Mods.RA/Cargo.cs b/OpenRA.Mods.RA/Cargo.cs index 49b1fef38a..5f7f6a2663 100644 --- a/OpenRA.Mods.RA/Cargo.cs +++ b/OpenRA.Mods.RA/Cargo.cs @@ -44,9 +44,21 @@ namespace OpenRA.Mods.RA self = init.self; Info = info; - if (init.Contains()) + if (init.Contains()) { - cargo = init.Get().ToList(); + cargo = init.Get().ToList(); + totalWeight = cargo.Sum(c => GetWeight(c)); + } + else if (init.Contains()) + { + foreach (var u in init.Get()) + { + 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()) npe.PassengerExited(self, a); + a.Trait().Transport = null; return a; } @@ -165,6 +179,8 @@ namespace OpenRA.Mods.RA foreach (var npe in self.TraitsImplementing()) npe.PassengerEntered(self, a); + + a.Trait().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().Transport = self; + + foreach (var npe in self.TraitsImplementing()) + 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 + public class RuntimeCargoInit : IActorInit { [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 + { + [FieldFromYamlKey] + public readonly string[] value = { }; + public CargoInit() { } + public CargoInit(string[] init) { value = init; } + public string[] Value(World world) { return value; } + } } diff --git a/OpenRA.Mods.RA/CombatDebugOverlay.cs b/OpenRA.Mods.RA/CombatDebugOverlay.cs index 103a032cf3..b8bb74d926 100644 --- a/OpenRA.Mods.RA/CombatDebugOverlay.cs +++ b/OpenRA.Mods.RA/CombatDebugOverlay.cs @@ -23,13 +23,15 @@ namespace OpenRA.Mods.RA public class CombatDebugOverlay : IPostRender { - Lazy> armaments; + Lazy attack; + Lazy coords; Lazy health; DeveloperMode devMode; public CombatDebugOverlay(Actor self) { - armaments = Lazy.New(() => self.TraitsImplementing()); + attack = Lazy.New(() => self.TraitOrDefault()); + coords = Lazy.New(() => self.Trait()); health = Lazy.New(() => self.TraitOrDefault()); 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) { diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index c19a7a5305..87dd4e031a 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -485,6 +485,8 @@ + + diff --git a/OpenRA.Mods.RA/Passenger.cs b/OpenRA.Mods.RA/Passenger.cs index 4a04de827c..6e4ad6e8dc 100644 --- a/OpenRA.Mods.RA/Passenger.cs +++ b/OpenRA.Mods.RA/Passenger.cs @@ -30,6 +30,7 @@ namespace OpenRA.Mods.RA { public readonly PassengerInfo info; public Passenger( PassengerInfo info ) { this.info = info; } + public Actor Transport; public IEnumerable Orders { diff --git a/OpenRA.Mods.RA/Render/RenderUnitReload.cs b/OpenRA.Mods.RA/Render/RenderUnitReload.cs index ffc5e63f11..b3f9599c4a 100755 --- a/OpenRA.Mods.RA/Render/RenderUnitReload.cs +++ b/OpenRA.Mods.RA/Render/RenderUnitReload.cs @@ -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, Requires { - 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(); + armament = self.TraitsImplementing() + .Single(a => a.Info.Name == info.Armament); + } public override void Tick(Actor self) { - var attack = self.TraitOrDefault(); + 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); } } diff --git a/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs b/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs index e59b182949..0b2e099fda 100644 --- a/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs +++ b/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs @@ -19,67 +19,56 @@ namespace OpenRA.Mods.RA.Render { class WithMuzzleFlashInfo : ITraitInfo, Requires, Requires, Requires { - [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 visible = new Dictionary(); Dictionary anims = new Dictionary(); Func getFacing; - public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info) + public WithMuzzleFlash(Actor self) { - this.info = info; var render = self.Trait(); var facing = self.TraitOrDefault(); - var arm = self.TraitsImplementing() - .Single(a => a.Info.Name == info.Armament); - - foreach (var b in arm.Barrels) + foreach (var arm in self.TraitsImplementing()) { - var barrel = b; - var turreted = self.TraitsImplementing() - .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)(() => facing.Facing) : () => 0; + foreach (var b in arm.Barrels) + { + var barrel = b; + var turreted = self.TraitsImplementing() + .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)(() => 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); } diff --git a/OpenRA.Mods.RA/RenderRangeCircle.cs b/OpenRA.Mods.RA/RenderRangeCircle.cs index 53314bdcdf..6ea99cdbd2 100644 --- a/OpenRA.Mods.RA/RenderRangeCircle.cs +++ b/OpenRA.Mods.RA/RenderRangeCircle.cs @@ -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 { 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() - .Select(a => Rules.Weapons[a.Weapon.ToLowerInvariant()].Range) - .Max(); + var armaments = ai.Traits.WithInterface(); + 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(); + } public void RenderAfterWorld(WorldRenderer wr) { @@ -59,7 +73,7 @@ namespace OpenRA.Mods.RA wr.DrawRangeCircleWithContrast( self.CenterPosition, - self.Trait().GetMaximumRange(), + attack.GetMaximumRange(), Color.FromArgb(128, Color.Yellow), Color.FromArgb(96, Color.Black) ); diff --git a/OpenRA.Utility/UpgradeRules.cs b/OpenRA.Utility/UpgradeRules.cs index 2202893a53..aca505c694 100644 --- a/OpenRA.Utility/UpgradeRules.cs +++ b/OpenRA.Utility/UpgradeRules.cs @@ -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("SplitFacings", muzzleSplitFacingsNode.Value.Value) : false; + var muzzleFacingsCount = muzzleFacingsCountNode != null ? FieldLoader.GetValue("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); } } diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index 788ed31441..f520734ce5 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -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 diff --git a/mods/cnc/rules/civilian.yaml b/mods/cnc/rules/civilian.yaml index ec29780a03..1529c7056f 100644 --- a/mods/cnc/rules/civilian.yaml +++ b/mods/cnc/rules/civilian.yaml @@ -441,6 +441,8 @@ VICE: Armament: Weapon: Chemspray LocalOffset: 384,0,0 + MuzzleSequence: muzzle + MuzzleSplitFacings: 8 AttackFrontal: AttackWander: RenderUnit: diff --git a/mods/cnc/rules/infantry.yaml b/mods/cnc/rules/infantry.yaml index 2b463cdf7b..d89be65ee7 100644 --- a/mods/cnc/rules/infantry.yaml +++ b/mods/cnc/rules/infantry.yaml @@ -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 diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index 42f9ecc89e..09388a2e71 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -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 diff --git a/mods/cnc/rules/vehicles.yaml b/mods/cnc/rules/vehicles.yaml index 4d603697e4..82e94883da 100644 --- a/mods/cnc/rules/vehicles.yaml +++ b/mods/cnc/rules/vehicles.yaml @@ -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: diff --git a/mods/d2k/rules/harkonnen.yaml b/mods/d2k/rules/harkonnen.yaml index a4f692b107..a22a46a697 100644 --- a/mods/d2k/rules/harkonnen.yaml +++ b/mods/d2k/rules/harkonnen.yaml @@ -190,6 +190,7 @@ DEVAST: Armament: Weapon: DevBullet LocalOffset: 256,0,32 + MuzzleSequence: muzzle AttackFrontal: WithMuzzleFlash: AutoTarget: diff --git a/mods/d2k/rules/ordos.yaml b/mods/d2k/rules/ordos.yaml index 53437f9f25..00cb703721 100644 --- a/mods/d2k/rules/ordos.yaml +++ b/mods/d2k/rules/ordos.yaml @@ -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 diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index 9af5cca8d4..859e4474f8 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -471,6 +471,7 @@ WALL: Armament: Weapon: TurretGun LocalOffset: 448,0,128 + MuzzleSequence: muzzle AttackTurreted: AutoTarget: RenderDetectionCircle: diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index 04946705a5..578719fff0 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -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: diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index a49e6e363c..684aa42f3a 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -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: diff --git a/mods/ra/rules/infantry.yaml b/mods/ra/rules/infantry.yaml index 26be6553d6..fe946ee5a3 100644 --- a/mods/ra/rules/infantry.yaml +++ b/mods/ra/rules/infantry.yaml @@ -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: diff --git a/mods/ra/rules/ships.yaml b/mods/ra/rules/ships.yaml index 1e0d425b0b..19a699c4ba 100644 --- a/mods/ra/rules/ships.yaml +++ b/mods/ra/rules/ships.yaml @@ -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: diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 21ed09bb2f..c8a7bb88db 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -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: diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index ba4a4a92d8..6577fad977 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -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 + diff --git a/mods/ra/sequences/infantry.yaml b/mods/ra/sequences/infantry.yaml index f5775bd9f0..37fe1b68b0 100644 --- a/mods/ra/sequences/infantry.yaml +++ b/mods/ra/sequences/infantry.yaml @@ -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