Merge pull request #10191 from abcdefg30/backfallDeathAnim
Add support for a FallbackSequence in WithDeathAnimation
This commit is contained in:
@@ -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 { }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user