From 72e8e08f488c7c97750d6d67637e1a18d6f37aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Husti=C4=87?= Date: Thu, 18 Jun 2015 00:51:33 +0100 Subject: [PATCH 1/3] Step in the direction of RA2 paratroopers. --- OpenRA.Mods.Common/Activities/Parachute.cs | 94 +++++++++++ OpenRA.Mods.Common/Effects/Parachute.cs | 114 ------------- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 3 +- OpenRA.Mods.Common/Traits/Crates/Crate.cs | 53 ++++-- OpenRA.Mods.Common/Traits/Crushable.cs | 4 + OpenRA.Mods.Common/Traits/EjectOnDeath.cs | 17 +- OpenRA.Mods.Common/Traits/Mobile.cs | 14 +- OpenRA.Mods.Common/Traits/ParaDrop.cs | 8 +- OpenRA.Mods.Common/Traits/Parachutable.cs | 20 +-- .../Traits/Render/WithInfantryBody.cs | 13 +- .../Traits/Render/WithParachute.cs | 154 ++++++++++++++++++ .../UtilityCommands/UpgradeRules.cs | 83 ++++++++++ 12 files changed, 419 insertions(+), 158 deletions(-) create mode 100644 OpenRA.Mods.Common/Activities/Parachute.cs delete mode 100644 OpenRA.Mods.Common/Effects/Parachute.cs create mode 100644 OpenRA.Mods.Common/Traits/Render/WithParachute.cs diff --git a/OpenRA.Mods.Common/Activities/Parachute.cs b/OpenRA.Mods.Common/Activities/Parachute.cs new file mode 100644 index 0000000000..17d45a5744 --- /dev/null +++ b/OpenRA.Mods.Common/Activities/Parachute.cs @@ -0,0 +1,94 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using OpenRA.Activities; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Activities +{ + public class Parachute : Activity + { + readonly UpgradeManager um; + readonly IPositionable pos; + readonly ParachutableInfo para; + readonly WVec fallVector; + + WPos dropPosition; + WPos currentPosition; + bool triggered = false; + + public Parachute(Actor self, WPos dropPosition) + { + um = self.TraitOrDefault(); + pos = self.TraitOrDefault(); + + // Parachutable trait is a prerequisite for running this activity + para = self.Info.Traits.Get(); + fallVector = new WVec(0, 0, para.FallRate); + this.dropPosition = dropPosition; + } + + Activity FirstTick(Actor self) + { + triggered = true; + + if (um != null) + foreach (var u in para.ParachuteUpgrade) + um.GrantUpgrade(self, u, this); + + // Place the actor and retrieve its visual position (CenterPosition) + pos.SetPosition(self, dropPosition); + currentPosition = self.CenterPosition; + + return this; + } + + Activity LastTick(Actor self) + { + pos.SetPosition(self, currentPosition - new WVec(0, 0, currentPosition.Z)); + + if (um != null) + foreach (var u in para.ParachuteUpgrade) + um.RevokeUpgrade(self, u, this); + + foreach (var npl in self.TraitsImplementing()) + npl.OnLanded(); + + return NextActivity; + } + + public override Activity Tick(Actor self) + { + // If this is the first tick + if (!triggered) + return FirstTick(self); + + currentPosition -= fallVector; + + // If the unit has landed, this will be the last tick + if (currentPosition.Z <= 0) + return LastTick(self); + + pos.SetVisualPosition(self, currentPosition); + + return this; + } + + // Only the last queued activity (given order) is kept + public override void Queue(Activity activity) + { + NextActivity = activity; + } + + // Cannot be cancelled + public override void Cancel(Actor self) { } + } +} diff --git a/OpenRA.Mods.Common/Effects/Parachute.cs b/OpenRA.Mods.Common/Effects/Parachute.cs deleted file mode 100644 index 526ef9a8a1..0000000000 --- a/OpenRA.Mods.Common/Effects/Parachute.cs +++ /dev/null @@ -1,114 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System.Collections.Generic; -using System.Linq; -using OpenRA.Effects; -using OpenRA.Graphics; -using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Effects -{ - public class Parachute : IEffect - { - readonly ParachutableInfo parachutableInfo; - readonly Animation parachute; - readonly Animation shadow; - readonly WVec parachuteOffset; - readonly Actor cargo; - WPos pos; - WVec fallVector; - - public Parachute(Actor cargo, WPos dropPosition) - { - this.cargo = cargo; - - parachutableInfo = cargo.Info.Traits.GetOrDefault(); - - if (parachutableInfo != null) - fallVector = new WVec(0, 0, parachutableInfo.FallRate); - - var parachuteSprite = parachutableInfo != null ? parachutableInfo.ParachuteSequence : null; - if (parachuteSprite != null) - { - parachute = new Animation(cargo.World, parachuteSprite); - parachute.PlayThen(parachutableInfo.ParachuteOpenSequence, () => parachute.PlayRepeating(parachutableInfo.ParachuteIdleSequence)); - } - - var shadowSprite = parachutableInfo != null ? parachutableInfo.ShadowSequence : null; - if (shadowSprite != null) - { - shadow = new Animation(cargo.World, shadowSprite); - shadow.PlayRepeating(parachutableInfo.ParachuteIdleSequence); - } - - if (parachutableInfo != null) - parachuteOffset = parachutableInfo.ParachuteOffset; - - // Adjust x,y to match the target subcell - cargo.Trait().SetPosition(cargo, cargo.World.Map.CellContaining(dropPosition)); - var cp = cargo.CenterPosition; - pos = new WPos(cp.X, cp.Y, dropPosition.Z); - } - - public void Tick(World world) - { - if (parachute != null) - parachute.Tick(); - - if (shadow != null) - shadow.Tick(); - - pos -= fallVector; - - if (pos.Z <= 0) - { - world.AddFrameEndTask(w => - { - w.Remove(this); - cargo.CancelActivity(); - w.Add(cargo); - - foreach (var npl in cargo.TraitsImplementing()) - npl.OnLanded(); - }); - } - } - - public IEnumerable Render(WorldRenderer wr) - { - var rc = cargo.Render(wr); - - // Don't render anything if the cargo is invisible (e.g. under fog) - if (!rc.Any()) - yield break; - - var parachuteShadowPalette = wr.Palette(parachutableInfo.ParachuteShadowPalette); - foreach (var c in rc) - { - if (!c.IsDecoration && shadow == null) - yield return c.WithPalette(parachuteShadowPalette).WithZOffset(c.ZOffset - 1).AsDecoration(); - - yield return c.OffsetBy(pos - c.Pos); - } - - var shadowPalette = !string.IsNullOrEmpty(parachutableInfo.ShadowPalette) ? wr.Palette(parachutableInfo.ShadowPalette) : rc.First().Palette; - if (shadow != null) - foreach (var r in shadow.Render(pos - new WVec(0, 0, pos.Z), WVec.Zero, 1, shadowPalette, 1f)) - yield return r; - - var parachutePalette = !string.IsNullOrEmpty(parachutableInfo.ParachutePalette) ? wr.Palette(parachutableInfo.ParachutePalette) : rc.First().Palette; - if (parachute != null) - foreach (var r in parachute.Render(pos, parachuteOffset, 1, parachutePalette, 1f)) - yield return r; - } - } -} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 3285b6d43b..a35bf7ce2c 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -122,6 +122,7 @@ + @@ -159,7 +160,6 @@ - @@ -433,6 +433,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index cdf0bea797..85863a65da 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -125,8 +125,40 @@ namespace OpenRA.Mods.Common.Traits public IEnumerable> OccupiedCells() { return new[] { Pair.New(Location, SubCell.FullCell) }; } public WPos CenterPosition { get; private set; } - public void SetPosition(Actor self, WPos pos) { SetPosition(self, self.World.Map.CellContaining(pos)); } - public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, self.World.Map.CellContaining(pos)); } + + // Sets the location (Location) and visual position (CenterPosition) + public void SetPosition(Actor self, WPos pos) + { + var cell = self.World.Map.CellContaining(pos); + SetLocation(self, cell); + SetVisualPosition(self, self.World.Map.CenterOfCell(cell) + new WVec(0, 0, pos.Z)); + } + + // Sets the location (Location) and visual position (CenterPosition) + public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) + { + SetLocation(self, cell, subCell); + SetVisualPosition(self, self.World.Map.CenterOfCell(cell)); + } + + // Sets only the visual position (CenterPosition) + public void SetVisualPosition(Actor self, WPos pos) + { + CenterPosition = pos; + if (self.IsInWorld) + { + self.World.ScreenMap.Update(self); + self.World.ActorMap.UpdatePosition(self, this); + } + } + + // Sets only the location (Location) + void SetLocation(Actor self, CPos cell, SubCell subCell = SubCell.Any) + { + self.World.ActorMap.RemoveInfluence(self, this); + Location = cell; + self.World.ActorMap.AddInfluence(self, this); + } public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return self.Location == location && ticks + 1 == info.Lifetime * 25; } public SubCell GetValidSubCell(SubCell preferred = SubCell.Any) { return SubCell.FullCell; } @@ -155,21 +187,12 @@ namespace OpenRA.Mods.Common.Traits return GetAvailableSubCell(a, SubCell.Any, ignoreActor, checkTransientActors) != SubCell.Invalid; } - public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) - { - self.World.ActorMap.RemoveInfluence(self, this); - Location = cell; - CenterPosition = self.World.Map.CenterOfCell(cell); - - if (self.IsInWorld) - { - self.World.ActorMap.UpdatePosition(self, this); - self.World.ScreenMap.Update(self); - } - } - public bool CrushableBy(string[] crushClasses, Player owner) { + // Crate can only be crushed if it is not in the air or underground + if (CenterPosition.Z != 0) + return false; + return crushClasses.Contains(info.CrushClass); } diff --git a/OpenRA.Mods.Common/Traits/Crushable.cs b/OpenRA.Mods.Common/Traits/Crushable.cs index abd20f4129..b088ec9d4a 100644 --- a/OpenRA.Mods.Common/Traits/Crushable.cs +++ b/OpenRA.Mods.Common/Traits/Crushable.cs @@ -64,6 +64,10 @@ namespace OpenRA.Mods.Common.Traits public bool CrushableBy(string[] crushClasses, Player crushOwner) { + // Only make actor crushable if it is on the ground + if (self.CenterPosition.Z != 0) + return false; + if (!info.CrushedByFriendlies && crushOwner.IsAlliedWith(self.Owner)) return false; diff --git a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs index 9d756a732a..da94d94546 100644 --- a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs +++ b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs @@ -8,7 +8,7 @@ */ #endregion -using OpenRA.Mods.Common.Effects; +using OpenRA.Mods.Common.Activities; using OpenRA.Primitives; using OpenRA.Traits; @@ -18,10 +18,19 @@ namespace OpenRA.Mods.Common.Traits public class EjectOnDeathInfo : ITraitInfo { [ActorReference] + [Desc("Name of the unit to eject. This actor type needs to have the Parachutable trait defined.")] public readonly string PilotActor = "E1"; + + [Desc("Probability that the aircraft's pilot gets ejected once the aircraft is destroyed.")] public readonly int SuccessRate = 50; + + [Desc("Sound to play when ejecting the pilot from the aircraft.")] public readonly string ChuteSound = "chute1.aud"; + + [Desc("Can a destroyed aircraft eject its pilot while it has not yet fallen to ground level?")] public readonly bool EjectInAir = false; + + [Desc("Can a destroyed aircraft eject its pilot when it falls to ground level?")] public readonly bool EjectOnGround = false; [Desc("Risks stuck units when they don't have the Paratrooper trait.")] @@ -66,7 +75,11 @@ namespace OpenRA.Mods.Common.Traits { if (cp.Z > 0) { - self.World.AddFrameEndTask(w => w.Add(new Parachute(pilot, cp))); + self.World.AddFrameEndTask(w => + { + w.Add(pilot); + pilot.QueueActivity(new Parachute(pilot, cp)); + }); Sound.Play(info.ChuteSound, cp); } else diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index bf828f5aae..d6efce25a8 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -308,6 +308,7 @@ namespace OpenRA.Mods.Common.Traits [Sync] public int PathHash; // written by Move.EvalPath, to temporarily debug this crap. + // Sets only the location (fromCell, toCell, FromSubCell, ToSubCell) public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub) { if (FromCell == from && ToCell == to && FromSubCell == fromSub && ToSubCell == toSub) @@ -368,6 +369,7 @@ namespace OpenRA.Mods.Common.Traits return preferred; } + // Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition) public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) { subCell = GetValidSubCell(subCell); @@ -376,14 +378,16 @@ namespace OpenRA.Mods.Common.Traits FinishedMoving(self); } + // Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and visual position (CenterPosition) public void SetPosition(Actor self, WPos pos) { var cell = self.World.Map.CellContaining(pos); SetLocation(cell, FromSubCell, cell, FromSubCell); - SetVisualPosition(self, pos); + SetVisualPosition(self, self.World.Map.CenterOfSubCell(cell, FromSubCell) + new WVec(0, 0, pos.Z)); FinishedMoving(self); } + // Sets only the visual position (CenterPosition) public void SetVisualPosition(Actor self, WPos pos) { CenterPosition = pos; @@ -548,6 +552,10 @@ namespace OpenRA.Mods.Common.Traits public void EnteringCell(Actor self) { + // Only make actor crush if it is on the ground + if (self.CenterPosition.Z != 0) + return; + var crushables = self.World.ActorMap.GetUnitsAt(ToCell).Where(a => a != self) .SelectMany(a => a.TraitsImplementing().Where(b => b.CrushableBy(Info.Crushes, self.Owner))); foreach (var crushable in crushables) @@ -556,6 +564,10 @@ namespace OpenRA.Mods.Common.Traits public void FinishedMoving(Actor self) { + // Only make actor crush if it is on the ground + if (self.CenterPosition.Z != 0) + return; + var crushables = self.World.ActorMap.GetUnitsAt(ToCell).Where(a => a != self) .SelectMany(a => a.TraitsImplementing().Where(c => c.CrushableBy(Info.Crushes, self.Owner))); foreach (var crushable in crushables) diff --git a/OpenRA.Mods.Common/Traits/ParaDrop.cs b/OpenRA.Mods.Common/Traits/ParaDrop.cs index a1c5d7ec4c..4d399cda50 100644 --- a/OpenRA.Mods.Common/Traits/ParaDrop.cs +++ b/OpenRA.Mods.Common/Traits/ParaDrop.cs @@ -10,7 +10,7 @@ using System; using System.Collections.Generic; -using OpenRA.Mods.Common.Effects; +using OpenRA.Mods.Common.Activities; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -82,7 +82,11 @@ namespace OpenRA.Mods.Common.Traits droppedAt.Add(self.Location); var a = cargo.Unload(self); - self.World.AddFrameEndTask(w => w.Add(new Parachute(a, self.CenterPosition))); + self.World.AddFrameEndTask(w => + { + w.Add(a); + a.QueueActivity(new Parachute(a, self.CenterPosition)); + }); Sound.Play(info.ChuteSound, self.CenterPosition); } diff --git a/OpenRA.Mods.Common/Traits/Parachutable.cs b/OpenRA.Mods.Common/Traits/Parachutable.cs index f4e1213b95..98be7fcccb 100644 --- a/OpenRA.Mods.Common/Traits/Parachutable.cs +++ b/OpenRA.Mods.Common/Traits/Parachutable.cs @@ -30,25 +30,11 @@ namespace OpenRA.Mods.Common.Traits [SequenceReference("CorpseSequenceCollection")] public readonly string WaterCorpseSequence = null; public readonly string WaterCorpsePalette = "effect"; - public readonly string ParachuteSequence = null; - [SequenceReference("ParachuteSequence")] public readonly string ParachuteOpenSequence = null; - [SequenceReference("ParachuteSequence")] public readonly string ParachuteIdleSequence = null; - - [Desc("Optional, otherwise defaults to the palette the actor is using.")] - public readonly string ParachutePalette = null; - - [Desc("Used to clone the actor with this palette and render it with a visual offset below.")] - public readonly string ParachuteShadowPalette = "shadow"; - - public readonly WVec ParachuteOffset = WVec.Zero; - public readonly int FallRate = 13; - [Desc("Alternative to ParachuteShadowPalette which disables it and allows to set a custom sprite sequence instead.")] - public readonly string ShadowSequence = null; - - [Desc("Optional, otherwise defaults to the palette the actor is using.")] - public readonly string ShadowPalette = null; + [UpgradeGrantedReference] + [Desc("Upgrade to grant to this actor when parachuting. Normally used to render the parachute using the WithParachute trait.")] + public readonly string[] ParachuteUpgrade = { "parachute" }; public object Create(ActorInitializer init) { return new Parachutable(init, this); } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs index a2628bfa24..456c085a01 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs @@ -127,16 +127,17 @@ namespace OpenRA.Mods.Common.Traits wasModifying = rsm.IsModifyingSequence; } - if ((state == AnimationState.Moving || dirty) && !move.IsMoving) - { - state = AnimationState.Waiting; - PlayStandAnimation(self); - } - else if ((state != AnimationState.Moving || dirty) && move.IsMoving) + if ((state != AnimationState.Moving || dirty) && move.IsMoving) { state = AnimationState.Moving; DefaultAnimation.PlayRepeating(NormalizeInfantrySequence(self, Info.MoveSequence)); } + else if (((state == AnimationState.Moving || dirty) && !move.IsMoving) + || ((state == AnimationState.Idle || state == AnimationState.IdleAnimating) && !self.IsIdle)) + { + state = AnimationState.Waiting; + PlayStandAnimation(self); + } dirty = false; } diff --git a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs new file mode 100644 index 0000000000..0372e6b727 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs @@ -0,0 +1,154 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Renders a parachute on units.")] + public class WithParachuteInfo : UpgradableTraitInfo, ITraitInfo, IRenderActorPreviewSpritesInfo, Requires, Requires + { + [Desc("The image that contains the parachute sequences.")] + public readonly string Image = null; + + [Desc("Parachute opening sequence.")] + [SequenceReference("Image")] public readonly string OpeningSequence = null; + + [Desc("Parachute idle sequence.")] + [SequenceReference("Image")] public readonly string Sequence = null; + + [Desc("Parachute closing sequence. Defaults to opening sequence played backwards.")] + [SequenceReference("Image")] public readonly string ClosingSequence = null; + + [Desc("Palette used to render the parachute.")] + public readonly string Palette = "player"; + + [Desc("Parachute position relative to the paradropped unit.")] + public readonly WVec Offset = new WVec(0, 0, 384); + + [Desc("The image that contains the shadow sequence for the paradropped unit.")] + public readonly string ShadowImage = null; + + [Desc("Paradropped unit's shadow sequence.")] + [SequenceReference("ShadowImage")] public readonly string ShadowSequence = null; + + [Desc("Palette used to render the paradropped unit's shadow.")] + public readonly string ShadowPalette = "player"; + + [Desc("Shadow position relative to the paradropped unit's intended landing position.")] + public readonly WVec ShadowOffset = new WVec(0, 128, 0); + + [Desc("Z-offset to apply on the shadow sequence.")] + public readonly int ShadowZOffset = 0; + + public override object Create(ActorInitializer init) { return new WithParachute(init.Self, this); } + + public IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p) + { + if (UpgradeMinEnabledLevel > 0) + yield break; + + if (image == null) + yield break; + + // For this, image must not be null + if (Palette != null) + p = init.WorldRenderer.Palette(Palette); + + var anim = new Animation(init.World, image); + anim.PlayThen(OpeningSequence, () => anim.PlayRepeating(Sequence)); + + var body = init.Actor.Traits.Get(); + var facing = init.Contains() ? init.Get() : 0; + var orientation = body.QuantizeOrientation(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing)), facings); + var offset = body.LocalToWorld(Offset.Rotate(orientation)); + yield return new SpriteActorPreview(anim, offset, offset.Y + offset.Z + 1, p, rs.Scale); + } + } + + public class WithParachute : UpgradableTrait, IRender + { + readonly Animation shadow; + readonly AnimationWithOffset anim; + readonly WithParachuteInfo info; + + bool renderProlonged = false; + + public WithParachute(Actor self, WithParachuteInfo info) + : base(info) + { + this.info = info; + + if (info.ShadowImage != null) + { + shadow = new Animation(self.World, info.ShadowImage); + shadow.PlayRepeating(info.ShadowSequence); + } + + if (info.Image == null) + return; + + // For this, info.Image must not be null + var overlay = new Animation(self.World, info.Image); + var body = self.Trait(); + anim = new AnimationWithOffset(overlay, + () => body.LocalToWorld(info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation))), + () => IsTraitDisabled && !renderProlonged, + () => false, + p => WithTurret.ZOffsetFromCenter(self, p, 1)); + + var rs = self.Trait(); + rs.Add(anim, info.Palette); + } + + protected override void UpgradeEnabled(Actor self) + { + if (info.Image == null) + return; + + anim.Animation.PlayThen(info.OpeningSequence, () => anim.Animation.PlayRepeating(info.Sequence)); + } + + protected override void UpgradeDisabled(Actor self) + { + if (info.Image == null) + return; + + renderProlonged = true; + if (!string.IsNullOrEmpty(info.ClosingSequence)) + anim.Animation.PlayThen(info.ClosingSequence, () => renderProlonged = false); + else + anim.Animation.PlayBackwardsThen(info.OpeningSequence, () => renderProlonged = false); + } + + public IEnumerable Render(Actor self, WorldRenderer wr) + { + if (info.ShadowImage == null) + yield break; + + if (IsTraitDisabled) + yield break; + + if (self.IsDead || !self.IsInWorld) + yield break; + + if (self.World.FogObscures(self)) + yield break; + + shadow.Tick(); + var pos = self.CenterPosition - new WVec(0, 0, self.CenterPosition.Z); + yield return new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, wr.Palette(info.ShadowPalette), 1, true); + } + } +} diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 864bf50b71..92cef5890b 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -1524,6 +1524,89 @@ namespace OpenRA.Mods.Common.UtilityCommands } } + if (engineVersion < 20150714) + { + // Move certain properties from Parachutable to new WithParachute trait + // Add dependency traits to actors implementing Parachutable + // Make otherwise targetable parachuting actors untargetable + var par = node.Value.Nodes.FirstOrDefault(n => n.Key == "Parachutable"); + if (par != null) + { + var withParachute = new MiniYamlNode("WithParachute", null, new List + { + new MiniYamlNode("UpgradeTypes", "parachute"), + new MiniYamlNode("UpgradeMinEnabledLevel", "1") + }); + + var copyProp = new Action((srcName, dstName, defValue) => + { + var prop = par.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith(srcName)); + if (prop != null && prop.Value.Value != defValue) + withParachute.Value.Nodes.Add(new MiniYamlNode(dstName, prop.Value.Value)); + }); + + var moveProp = new Action((srcName, dstName, defValue) => + { + copyProp(srcName, dstName, defValue); + par.Value.Nodes.RemoveAll(n => n.Key.StartsWith(srcName)); + }); + + if (par.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("ShadowSequence")) != null) + { + moveProp("ShadowSequence", "ShadowImage", null); + copyProp("ParachuteIdleSequence", "ShadowSequence", null); + } + + moveProp("ParachuteSequence", "Image", null); + moveProp("ParachuteIdleSequence", "Sequence", null); + + moveProp("ParachuteOpenSequence", "OpeningSequence", null); + + moveProp("ParachutePalette", "Palette", "player"); + moveProp("ShadowPalette", "ShadowPalette", "player"); + + moveProp("ParachuteOffset", "Offset", "player"); + + par.Value.Nodes.RemoveAll(n => n.Key.StartsWith("ParachuteShadowPalette")); + + node.Value.Nodes.Add(withParachute); + + var otherNodes = nodes; + var inherits = new Func(traitName => node.Value.Nodes.Where(n => n.Key.StartsWith("Inherits")) + .Any(inh => otherNodes.First(n => n.Key.StartsWith(inh.Value.Value)).Value.Nodes.Any(n => n.Key.StartsWith(traitName)))); + + // For actors that have or inherit a TargetableUnit, disable the trait while parachuting + var tu = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("TargetableUnit")); + if (tu != null) + { + tu.Value.Nodes.Add(new MiniYamlNode("UpgradeTypes", "parachute")); + tu.Value.Nodes.Add(new MiniYamlNode("UpgradeMaxEnabledLevel", "0")); + } + else + { + if (inherits("TargetableUnit")) + { + node.Value.Nodes.Add(new MiniYamlNode("TargetableUnit", null, new List + { + new MiniYamlNode("UpgradeTypes", "parachute"), + new MiniYamlNode("UpgradeMaxEnabledLevel", "0") + })); + break; + } + } + + var has = new Func(traitName => node.Value.Nodes.Any(n => n.Key.StartsWith(traitName))); + + // If actor does not have nor inherits an UpgradeManager, add one + if (!has("UpgradeManager") && !inherits("UpgradeManager")) + node.Value.Nodes.Add(new MiniYamlNode("UpgradeManager", "")); + + // If actor does not have nor inherits a BodyOrientation, add one + if (!has("BodyOrientation") && !inherits("BodyOrientation")) + node.Value.Nodes.Add(new MiniYamlNode("BodyOrientation", "")); + } + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } From 71d73ac7389ba2b9566666b792e1eb7a674e883a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Husti=C4=87?= Date: Tue, 14 Jul 2015 14:20:01 +0100 Subject: [PATCH 2/3] Automatically upgraded YAMLs. --- mods/d2k/rules/defaults.yaml | 5 +++++ mods/ra/maps/survival01/map.yaml | 7 +++++- mods/ra/rules/defaults.yaml | 37 ++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index f0f59ae2da..8a797f9e6c 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -188,6 +188,8 @@ Bounds: 12,18,0,-6 TargetableUnit: TargetTypes: Ground + UpgradeTypes: parachute + UpgradeMaxEnabledLevel: 0 RenderSprites: WithInfantryBody: TakeCover: @@ -225,6 +227,9 @@ Rough: 80 Voiced: VoiceSet: InfantryVoice + WithParachute: + UpgradeTypes: parachute + UpgradeMinEnabledLevel: 1 ^Plane: Inherits@1: ^ExistsInWorld diff --git a/mods/ra/maps/survival01/map.yaml b/mods/ra/maps/survival01/map.yaml index ae3252d6fc..aad84f5e78 100644 --- a/mods/ra/maps/survival01/map.yaml +++ b/mods/ra/maps/survival01/map.yaml @@ -1263,7 +1263,12 @@ Rules: ARTY: Parachutable: KilledOnImpassableTerrain: false - ParachuteSequence: parach + WithParachute: + UpgradeTypes: parachute + UpgradeMinEnabledLevel: 1 + Image: parach + UpgradeManager: + BodyOrientation: AFLD.mission: Inherits: AFLD -AirstrikePower@spyplane: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index f90fd9582c..cbdaadd9b2 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -79,6 +79,8 @@ Bounds: 24, 24 TargetableUnit: TargetTypes: Ground, Repair, Vehicle + UpgradeTypes: parachute + UpgradeMaxEnabledLevel: 0 Repairable: Chronoshiftable: Passenger: @@ -112,10 +114,7 @@ Voiced: VoiceSet: VehicleVoice Parachutable: - ParachuteOffset: 0,0,200 KilledOnImpassableTerrain: true - ParachuteSequence: parach - ShadowSequence: GroundCorpseSequence: GroundCorpsePalette: WaterCorpseSequence: @@ -126,6 +125,11 @@ WithFacingSpriteBody: AutoSelectionSize: RenderSprites: + WithParachute: + UpgradeTypes: parachute + UpgradeMinEnabledLevel: 1 + Image: parach + Offset: 0,0,200 ^Tank: Inherits: ^Vehicle @@ -173,6 +177,8 @@ Bounds: 12,18,0,-8 TargetableUnit: TargetTypes: Ground, Infantry, Disguise + UpgradeTypes: parachute + UpgradeMaxEnabledLevel: 0 RenderSprites: WithInfantryBody: WithDeathAnimation: @@ -217,12 +223,7 @@ Voice: Zapped DeathTypes: ElectricityDeath Parachutable: - ParachuteOffset: 0,0,427 KilledOnImpassableTerrain: true - ParachuteSequence: parach - ParachuteOpenSequence: open - ParachuteIdleSequence: idle - ShadowSequence: parach-shadow GroundImpactSound: squishy2.aud WaterImpactSound: splash9.aud WaterCorpseSequence: small_splash @@ -230,6 +231,15 @@ Types: Infantry Voiced: VoiceSet: GenericVoice + WithParachute: + UpgradeTypes: parachute + UpgradeMinEnabledLevel: 1 + ShadowImage: parach-shadow + ShadowSequence: idle + Image: parach + Sequence: idle + OpeningSequence: open + Offset: 0,0,427 ^Soldier: Inherits: ^Infantry @@ -648,10 +658,15 @@ WaterSequence: water Parachutable: KilledOnImpassableTerrain: false - ParachuteSequence: parach - ParachuteOpenSequence: open - ParachuteIdleSequence: idle Passenger: CustomSelectionSize: CustomBounds: 20,20 + WithParachute: + UpgradeTypes: parachute + UpgradeMinEnabledLevel: 1 + Image: parach + Sequence: idle + OpeningSequence: open + UpgradeManager: + BodyOrientation: From 2f01c389cfbc94667da564d03a5ab3a79a20df94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Husti=C4=87?= Date: Tue, 30 Jun 2015 20:06:31 +0100 Subject: [PATCH 3/3] Manually upgraded YAMLs. --- mods/d2k/bits/parach-shadow.shp | Bin 0 -> 60 bytes mods/d2k/rules/defaults.yaml | 2 ++ mods/d2k/sequences/misc.yaml | 4 ++++ mods/ra/rules/defaults.yaml | 2 ++ 4 files changed, 8 insertions(+) create mode 100644 mods/d2k/bits/parach-shadow.shp diff --git a/mods/d2k/bits/parach-shadow.shp b/mods/d2k/bits/parach-shadow.shp new file mode 100644 index 0000000000000000000000000000000000000000..7d848c88a424dfe29931843356f3ccd99a957af1 GIT binary patch literal 60 wcmZQ%00ABbHXv!kpvJ(^0OH$#2qf^2pMl{Y8v{!LmjmAkX$A+?e*!?20C|)K