Merge pull request #8339 from matija-hustic/paradrop_activity

Step in the direction of RA2 paratroopers
This commit is contained in:
Oliver Brakmann
2015-07-14 22:29:02 +02:00
17 changed files with 464 additions and 170 deletions

View File

@@ -125,8 +125,40 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable<Pair<CPos, SubCell>> 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);
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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<ICrushable>().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<ICrushable>().Where(c => c.CrushableBy(Info.Crushes, self.Owner)));
foreach (var crushable in crushables)

View File

@@ -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);
}

View File

@@ -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); }
}

View File

@@ -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;
}

View File

@@ -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<RenderSpritesInfo>, Requires<IBodyOrientationInfo>
{
[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<IActorPreview> 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<BodyOrientationInfo>();
var facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 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<WithParachuteInfo>, 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<IBodyOrientation>();
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<RenderSprites>();
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<IRenderable> 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);
}
}
}