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(); }
|
public interface IFacingInfo : ITraitInfoInterface { int GetInitialFacing(); }
|
||||||
|
|
||||||
|
[RequireExplicitImplementation]
|
||||||
public interface ICrushable
|
public interface ICrushable
|
||||||
{
|
{
|
||||||
void OnCrush(Actor crusher);
|
bool CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses);
|
||||||
void WarnCrush(Actor crusher);
|
}
|
||||||
bool CrushableBy(HashSet<string> crushClasses, Player owner);
|
|
||||||
|
[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 { }
|
public interface ITraitInfoInterface { }
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
bool IOccupySpaceInfo.SharesCell { get { return false; } }
|
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 Actor self;
|
||||||
readonly CrateInfo info;
|
readonly CrateInfo info;
|
||||||
@@ -56,9 +57,46 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
SetPosition(self, init.Get<LocationInit, CPos>());
|
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)
|
if (collected)
|
||||||
return;
|
return;
|
||||||
@@ -82,39 +120,12 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
s.First.Activate(crusher);
|
s.First.Activate(crusher);
|
||||||
return;
|
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)
|
public void Tick(Actor self)
|
||||||
{
|
{
|
||||||
if (info.Lifetime != 0 && self.IsInWorld && ++ticks >= info.Lifetime * 25)
|
if (info.Lifetime != 0 && self.IsInWorld && ++ticks >= info.Lifetime * 25)
|
||||||
@@ -178,8 +189,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return SubCell.FullCell;
|
return SubCell.FullCell;
|
||||||
|
|
||||||
return !self.World.ActorMap.GetActorsAt(cell)
|
return !self.World.ActorMap.GetActorsAt(cell)
|
||||||
.Where(x => x != ignoreActor)
|
.Any(x => x != ignoreActor)
|
||||||
.Any() ? SubCell.FullCell : SubCell.Invalid;
|
? SubCell.FullCell : SubCell.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true)
|
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;
|
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.
|
// Crate can only be crushed if it is not in the air.
|
||||||
return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass);
|
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); }
|
public object Create(ActorInitializer init) { return new Crushable(init.Self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Crushable : ICrushable
|
class Crushable : ICrushable, INotifyCrushed
|
||||||
{
|
{
|
||||||
readonly Actor self;
|
readonly Actor self;
|
||||||
readonly CrushableInfo info;
|
readonly CrushableInfo info;
|
||||||
@@ -40,31 +40,32 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
this.info = info;
|
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>();
|
var mobile = self.TraitOrDefault<Mobile>();
|
||||||
if (mobile != null && self.World.SharedRandom.Next(100) <= info.WarnProbability)
|
if (mobile != null && self.World.SharedRandom.Next(100) <= info.WarnProbability)
|
||||||
mobile.Nudge(self, crusher, true);
|
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);
|
if (!CrushableInner(crushClasses, crusher.Owner))
|
||||||
var wda = self.TraitsImplementing<WithDeathAnimation>()
|
return;
|
||||||
.FirstOrDefault(s => s.Info.CrushedSequence != null);
|
|
||||||
if (wda != null)
|
|
||||||
{
|
|
||||||
var palette = wda.Info.CrushedSequencePalette;
|
|
||||||
if (wda.Info.CrushedPaletteIsPlayerPalette)
|
|
||||||
palette += self.Owner.InternalName;
|
|
||||||
|
|
||||||
wda.SpawnDeathAnimation(self, wda.Info.CrushedSequence, palette);
|
Game.Sound.Play(info.CrushSound, crusher.CenterPosition);
|
||||||
}
|
|
||||||
|
|
||||||
self.Kill(crusher);
|
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.
|
// Only make actor crushable if it is on the ground.
|
||||||
if (!self.IsAtGroundLevel())
|
if (!self.IsAtGroundLevel())
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
foreach (var crushable in crushables)
|
foreach (var crushable in crushables)
|
||||||
{
|
{
|
||||||
lacksCrushability = false;
|
lacksCrushability = false;
|
||||||
if (!crushable.CrushableBy(Crushes, self.Owner))
|
if (!crushable.CrushableBy(otherActor, self, Crushes))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,10 +594,13 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (!self.IsAtGroundLevel())
|
if (!self.IsAtGroundLevel())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self)
|
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||||
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(b => b.CrushableBy(Info.Crushes, self.Owner)));
|
if (!AnyCrushables(actors))
|
||||||
foreach (var crushable in crushables)
|
return;
|
||||||
crushable.WarnCrush(self);
|
|
||||||
|
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)
|
public void FinishedMoving(Actor self)
|
||||||
@@ -606,10 +609,26 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (!self.IsAtGroundLevel())
|
if (!self.IsAtGroundLevel())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self)
|
var actors = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self).ToList();
|
||||||
.SelectMany(a => a.TraitsImplementing<ICrushable>().Where(c => c.CrushableBy(Info.Crushes, self.Owner)));
|
if (!AnyCrushables(actors))
|
||||||
foreach (var crushable in crushables)
|
return;
|
||||||
crushable.OnCrush(self);
|
|
||||||
|
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)
|
public int MovementSpeedForCell(Actor self, CPos cell)
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
"Is only used if UseDeathTypeSuffix is `True`.")]
|
"Is only used if UseDeathTypeSuffix is `True`.")]
|
||||||
public readonly Dictionary<string, int> DeathTypes = new Dictionary<string, int>();
|
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)
|
public static object LoadDeathTypes(MiniYaml yaml)
|
||||||
{
|
{
|
||||||
var md = yaml.ToDictionary();
|
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 object Create(ActorInitializer init) { return new WithDeathAnimation(init.Self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WithDeathAnimation : INotifyKilled
|
public class WithDeathAnimation : INotifyKilled, INotifyCrushed
|
||||||
{
|
{
|
||||||
public readonly WithDeathAnimationInfo Info;
|
public readonly WithDeathAnimationInfo Info;
|
||||||
readonly RenderSprites rs;
|
readonly RenderSprites rs;
|
||||||
|
bool crushed;
|
||||||
|
|
||||||
public WithDeathAnimation(Actor self, WithDeathAnimationInfo info)
|
public WithDeathAnimation(Actor self, WithDeathAnimationInfo info)
|
||||||
{
|
{
|
||||||
@@ -70,11 +74,23 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
public void Killed(Actor self, AttackInfo e)
|
public void Killed(Actor self, AttackInfo e)
|
||||||
{
|
{
|
||||||
// Killed by some non-standard means. This includes being crushed
|
// Actors with Crushable trait will spawn CrushedSequence.
|
||||||
// by a vehicle (Actors with Crushable trait will spawn CrushedSequence instead).
|
if (crushed)
|
||||||
if (e.Warhead == null || !(e.Warhead is DamageWarhead))
|
|
||||||
return;
|
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;
|
var sequence = Info.DeathSequence;
|
||||||
if (Info.UseDeathTypeSuffix)
|
if (Info.UseDeathTypeSuffix)
|
||||||
{
|
{
|
||||||
@@ -86,20 +102,28 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
sequence += Info.DeathTypes[damageType];
|
sequence += Info.DeathTypes[damageType];
|
||||||
}
|
}
|
||||||
|
|
||||||
var palette = Info.DeathSequencePalette;
|
SpawnDeathAnimation(self, self.CenterPosition, rs.GetImage(self), sequence, palette);
|
||||||
if (Info.DeathPaletteIsPlayerPalette)
|
|
||||||
palette += self.Owner.InternalName;
|
|
||||||
|
|
||||||
SpawnDeathAnimation(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 =>
|
self.World.AddFrameEndTask(w => w.Add(new Corpse(w, pos, image, sequence, palette)));
|
||||||
{
|
|
||||||
if (!self.Disposed)
|
|
||||||
w.Add(new Corpse(w, self.CenterPosition, rs.GetImage(self), 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 bool AvoidFriendly = true;
|
||||||
public readonly HashSet<string> DetonateClasses = new HashSet<string>();
|
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;
|
readonly MineInfo info;
|
||||||
|
|
||||||
public Mine(ActorInitializer init, MineInfo info)
|
public Mine(MineInfo info)
|
||||||
{
|
{
|
||||||
self = init.Self;
|
|
||||||
this.info = info;
|
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))
|
if (crusher.Info.HasTraitInfo<MineImmuneInfo>() || (self.Owner.Stances[crusher.Owner] == Stance.Ally && info.AvoidFriendly))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
self.Kill(crusher);
|
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);
|
return info.CrushClasses.Overlaps(crushClasses);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user