Merge pull request #10191 from abcdefg30/backfallDeathAnim

Add support for a FallbackSequence in WithDeathAnimation
This commit is contained in:
Matthias Mailänder
2016-02-21 10:18:47 +01:00
6 changed files with 146 additions and 84 deletions

View File

@@ -301,11 +301,17 @@ namespace OpenRA.Traits
public interface IFacingInfo : ITraitInfoInterface { int GetInitialFacing(); }
[RequireExplicitImplementation]
public interface ICrushable
{
void OnCrush(Actor crusher);
void WarnCrush(Actor crusher);
bool CrushableBy(HashSet<string> crushClasses, Player owner);
bool CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses);
}
[RequireExplicitImplementation]
public interface INotifyCrushed
{
void OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses);
void WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses);
}
public interface ITraitInfoInterface { }

View File

@@ -38,7 +38,8 @@ namespace OpenRA.Mods.Common.Traits
bool IOccupySpaceInfo.SharesCell { get { return false; } }
}
class Crate : ITick, IPositionable, ICrushable, ISync, INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld
class Crate : ITick, IPositionable, ICrushable, ISync,
INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyCrushed
{
readonly Actor self;
readonly CrateInfo info;
@@ -56,9 +57,46 @@ namespace OpenRA.Mods.Common.Traits
SetPosition(self, init.Get<LocationInit, CPos>());
}
public void WarnCrush(Actor crusher) { }
void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses) { }
public void OnCrush(Actor crusher)
void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
{
// Crate can only be crushed if it is not in the air.
if (!self.IsAtGroundLevel() || !crushClasses.Contains(info.CrushClass))
return;
OnCrushInner(crusher);
}
public void OnLanded()
{
// Check whether the crate landed on anything
var landedOn = self.World.ActorMap.GetActorsAt(self.Location)
.Where(a => a != self);
if (!landedOn.Any())
return;
var collector = landedOn.FirstOrDefault(a =>
{
// Mobile is (currently) the only trait that supports crushing
var mi = a.Info.TraitInfoOrDefault<MobileInfo>();
if (mi == null)
return false;
// Make sure that the actor can collect this crate type
// Crate can only be crushed if it is not in the air.
return self.IsAtGroundLevel() && mi.Crushes.Contains(info.CrushClass);
});
// Destroy the crate if none of the units in the cell are valid collectors
if (collector != null)
OnCrushInner(collector);
else
self.Dispose();
}
void OnCrushInner(Actor crusher)
{
if (collected)
return;
@@ -82,39 +120,12 @@ namespace OpenRA.Mods.Common.Traits
s.First.Activate(crusher);
return;
}
else
n -= s.Second;
n -= s.Second;
}
}
}
public void OnLanded()
{
// Check whether the crate landed on anything
var landedOn = self.World.ActorMap.GetActorsAt(self.Location)
.Where(a => a != self);
if (!landedOn.Any())
return;
var collector = landedOn.FirstOrDefault(a =>
{
// Mobile is (currently) the only trait that supports crushing
var mi = a.Info.TraitInfoOrDefault<MobileInfo>();
if (mi == null)
return false;
// Make sure that the actor can collect this crate type
return CrushableBy(mi.Crushes, a.Owner);
});
// Destroy the crate if none of the units in the cell are valid collectors
if (collector != null)
OnCrush(collector);
else
self.Dispose();
}
public void Tick(Actor self)
{
if (info.Lifetime != 0 && self.IsInWorld && ++ticks >= info.Lifetime * 25)
@@ -178,8 +189,8 @@ namespace OpenRA.Mods.Common.Traits
return SubCell.FullCell;
return !self.World.ActorMap.GetActorsAt(cell)
.Where(x => x != ignoreActor)
.Any() ? SubCell.FullCell : SubCell.Invalid;
.Any(x => x != ignoreActor)
? SubCell.FullCell : SubCell.Invalid;
}
public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true)
@@ -187,7 +198,7 @@ namespace OpenRA.Mods.Common.Traits
return GetAvailableSubCell(a, SubCell.Any, ignoreActor, checkTransientActors) != SubCell.Invalid;
}
public bool CrushableBy(HashSet<string> crushClasses, Player owner)
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)
{
// Crate can only be crushed if it is not in the air.
return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass);

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new Crushable(init.Self, this); }
}
class Crushable : ICrushable
class Crushable : ICrushable, INotifyCrushed
{
readonly Actor self;
readonly CrushableInfo info;
@@ -40,31 +40,32 @@ namespace OpenRA.Mods.Common.Traits
this.info = info;
}
public void WarnCrush(Actor crusher)
void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
{
if (!CrushableInner(crushClasses, crusher.Owner))
return;
var mobile = self.TraitOrDefault<Mobile>();
if (mobile != null && self.World.SharedRandom.Next(100) <= info.WarnProbability)
mobile.Nudge(self, crusher, true);
}
public void OnCrush(Actor crusher)
void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
{
Game.Sound.Play(info.CrushSound, crusher.CenterPosition);
var wda = self.TraitsImplementing<WithDeathAnimation>()
.FirstOrDefault(s => s.Info.CrushedSequence != null);
if (wda != null)
{
var palette = wda.Info.CrushedSequencePalette;
if (wda.Info.CrushedPaletteIsPlayerPalette)
palette += self.Owner.InternalName;
if (!CrushableInner(crushClasses, crusher.Owner))
return;
wda.SpawnDeathAnimation(self, wda.Info.CrushedSequence, palette);
}
Game.Sound.Play(info.CrushSound, crusher.CenterPosition);
self.Kill(crusher);
}
public bool CrushableBy(HashSet<string> crushClasses, Player crushOwner)
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)
{
return CrushableInner(crushClasses, crusher.Owner);
}
bool CrushableInner(HashSet<string> crushClasses, Player crushOwner)
{
// Only make actor crushable if it is on the ground.
if (!self.IsAtGroundLevel())

View File

@@ -260,7 +260,7 @@ namespace OpenRA.Mods.Common.Traits
foreach (var crushable in crushables)
{
lacksCrushability = false;
if (!crushable.CrushableBy(Crushes, self.Owner))
if (!crushable.CrushableBy(otherActor, self, Crushes))
return true;
}
@@ -594,10 +594,13 @@ namespace OpenRA.Mods.Common.Traits
if (!self.IsAtGroundLevel())
return;
var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self)
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(b => b.CrushableBy(Info.Crushes, self.Owner)));
foreach (var crushable in crushables)
crushable.WarnCrush(self);
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
if (!AnyCrushables(actors))
return;
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
foreach (var notifyCrushed in notifiers)
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes);
}
public void FinishedMoving(Actor self)
@@ -606,10 +609,26 @@ namespace OpenRA.Mods.Common.Traits
if (!self.IsAtGroundLevel())
return;
var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self)
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(c => c.CrushableBy(Info.Crushes, self.Owner)));
foreach (var crushable in crushables)
crushable.OnCrush(self);
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
if (!AnyCrushables(actors))
return;
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
foreach (var notifyCrushed in notifiers)
notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.Crushes);
}
bool AnyCrushables(List<Actor> actors)
{
var crushables = actors.SelectMany(a => a.TraitsImplementing<ICrushable>().Select(t => new TraitPair<ICrushable>(a, t))).ToList();
if (crushables.Count == 0)
return false;
foreach (var crushes in crushables)
if (!crushes.Trait.CrushableBy(crushes.Actor, self, Info.Crushes))
return false;
return true;
}
public int MovementSpeedForCell(Actor self, CPos cell)

View File

@@ -45,6 +45,9 @@ namespace OpenRA.Mods.Common.Traits
"Is only used if UseDeathTypeSuffix is `True`.")]
public readonly Dictionary<string, int> DeathTypes = new Dictionary<string, int>();
[Desc("Sequence to use when the actor is killed by some non-standard means (e.g. suicide).")]
[SequenceReference] public readonly string FallbackSequence = null;
public static object LoadDeathTypes(MiniYaml yaml)
{
var md = yaml.ToDictionary();
@@ -57,10 +60,11 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new WithDeathAnimation(init.Self, this); }
}
public class WithDeathAnimation : INotifyKilled
public class WithDeathAnimation : INotifyKilled, INotifyCrushed
{
public readonly WithDeathAnimationInfo Info;
readonly RenderSprites rs;
bool crushed;
public WithDeathAnimation(Actor self, WithDeathAnimationInfo info)
{
@@ -70,11 +74,23 @@ namespace OpenRA.Mods.Common.Traits
public void Killed(Actor self, AttackInfo e)
{
// Killed by some non-standard means. This includes being crushed
// by a vehicle (Actors with Crushable trait will spawn CrushedSequence instead).
if (e.Warhead == null || !(e.Warhead is DamageWarhead))
// Actors with Crushable trait will spawn CrushedSequence.
if (crushed)
return;
var palette = Info.DeathSequencePalette;
if (Info.DeathPaletteIsPlayerPalette)
palette += self.Owner.InternalName;
// Killed by some non-standard means
if (e.Warhead == null || !(e.Warhead is DamageWarhead))
{
if (Info.FallbackSequence != null)
SpawnDeathAnimation(self, self.CenterPosition, rs.GetImage(self), Info.FallbackSequence, palette);
return;
}
var sequence = Info.DeathSequence;
if (Info.UseDeathTypeSuffix)
{
@@ -86,20 +102,28 @@ namespace OpenRA.Mods.Common.Traits
sequence += Info.DeathTypes[damageType];
}
var palette = Info.DeathSequencePalette;
if (Info.DeathPaletteIsPlayerPalette)
palette += self.Owner.InternalName;
SpawnDeathAnimation(self, sequence, palette);
SpawnDeathAnimation(self, self.CenterPosition, rs.GetImage(self), sequence, palette);
}
public void SpawnDeathAnimation(Actor self, string sequence, string palette)
public void SpawnDeathAnimation(Actor self, WPos pos, string image, string sequence, string palette)
{
self.World.AddFrameEndTask(w =>
{
if (!self.Disposed)
w.Add(new Corpse(w, self.CenterPosition, rs.GetImage(self), sequence, palette));
});
self.World.AddFrameEndTask(w => w.Add(new Corpse(w, pos, image, sequence, palette)));
}
void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
{
crushed = true;
if (Info.CrushedSequence == null)
return;
var crushPalette = Info.CrushedSequencePalette;
if (Info.CrushedPaletteIsPlayerPalette)
crushPalette += self.Owner.InternalName;
SpawnDeathAnimation(self, self.CenterPosition, rs.GetImage(self), Info.CrushedSequence, crushPalette);
}
void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses) { }
}
}

View File

@@ -20,24 +20,25 @@ namespace OpenRA.Mods.RA.Traits
public readonly bool AvoidFriendly = true;
public readonly HashSet<string> DetonateClasses = new HashSet<string>();
public object Create(ActorInitializer init) { return new Mine(init, this); }
public object Create(ActorInitializer init) { return new Mine(this); }
}
class Mine : ICrushable
class Mine : ICrushable, INotifyCrushed
{
readonly Actor self;
readonly MineInfo info;
public Mine(ActorInitializer init, MineInfo info)
public Mine(MineInfo info)
{
self = init.Self;
this.info = info;
}
public void WarnCrush(Actor crusher) { }
void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet<string> crushClasses) { }
public void OnCrush(Actor crusher)
void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet<string> crushClasses)
{
if (!info.CrushClasses.Overlaps(crushClasses))
return;
if (crusher.Info.HasTraitInfo<MineImmuneInfo>() || (self.Owner.Stances[crusher.Owner] == Stance.Ally && info.AvoidFriendly))
return;
@@ -48,7 +49,7 @@ namespace OpenRA.Mods.RA.Traits
self.Kill(crusher);
}
public bool CrushableBy(HashSet<string> crushClasses, Player owner)
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)
{
return info.CrushClasses.Overlaps(crushClasses);
}