diff --git a/OpenRA.Mods.Common/Traits/Player/DamgeNotifier.cs b/OpenRA.Mods.Common/Traits/Player/DamgeNotifier.cs new file mode 100644 index 0000000000..87f6520607 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Player/DamgeNotifier.cs @@ -0,0 +1,122 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [TraitLocation(SystemActors.Player)] + [Desc("Plays an audio notification and shows a radar ping when attacked.", + "Attach this to the player actor.")] + public class DamageNotifierInfo : TraitInfo + { + [Desc("Target types to notify about.", + "Leave empty to notify about all target types.")] + public readonly BitSet ValidTargets = default; + [Desc("Target types to ignore.", + "This overrides ValidTargets.", + "Leave empty to notify about all target types.")] + public readonly BitSet InvalidTargets = default; + + [Desc("Minimum duration (in milliseconds) between notification events.", + "Set to -1 to make notifications to play only once.")] + public readonly int NotifyInterval = 30000; + + public readonly Color RadarPingColor = Color.Red; + + [Desc("Length of time (in ticks) to display a location ping in the minimap.")] + public readonly int RadarPingDuration = 250; + + [NotificationReference("Speech")] + [Desc("Speech notification type to play.")] + public readonly string Notification = null; + + [FluentReference(optional: true)] + [Desc("Text notification to display.")] + public readonly string TextNotification = null; + + [NotificationReference("Speech")] + [Desc("Speech notification to play to allies when under attack.", + "Won't play a notification to allies if this is null.")] + public readonly string AllyNotification = null; + + [FluentReference(optional: true)] + [Desc("Text notification to display to allies when under attack.")] + public readonly string AllyTextNotification = null; + + public override object Create(ActorInitializer init) { return new DamageNotifier(init.Self, this); } + } + + public class DamageNotifier : INotifyDamage + { + readonly RadarPings radarPings; + readonly DamageNotifierInfo info; + + long nextActiveTick = 0; + + public DamageNotifier(Actor self, DamageNotifierInfo info) + { + radarPings = self.World.WorldActor.TraitOrDefault(); + this.info = info; + } + + void INotifyDamage.Damaged(Actor self, AttackInfo e) + { + var localPlayer = self.World.LocalPlayer; + + if (localPlayer == null || localPlayer.Spectating) + return; + + if (e.Attacker == null) + return; + + if (e.Attacker.Owner == self.Owner) + return; + + if (e.Attacker == self.World.WorldActor) + return; + + if (!info.ValidTargets.IsEmpty && !self.GetEnabledTargetTypes().Overlaps(info.ValidTargets)) + return; + + if (!info.InvalidTargets.IsEmpty && self.GetEnabledTargetTypes().Overlaps(info.InvalidTargets)) + return; + + if (e.Attacker.Owner.IsAlliedWith(self.Owner) && e.Damage.Value <= 0) + return; + + if (Game.RunTime >= nextActiveTick) + { + var visible = self.Owner.IsAlliedWith(self.World.RenderPlayer); + radarPings?.Add(() => visible, self.CenterPosition, info.RadarPingColor, info.RadarPingDuration); + if (!visible) + return; + + var rules = self.World.Map.Rules; + if (self.Owner == localPlayer) + { + Game.Sound.PlayNotification(rules, self.Owner, "Speech", info.Notification, self.Owner.Faction.InternalName); + TextNotificationsManager.AddTransientLine(self.Owner, info.TextNotification); + } + else if (localPlayer.IsAlliedWith(self.Owner) && localPlayer != e.Attacker.Owner) + { + Game.Sound.PlayNotification(rules, localPlayer, "Speech", info.AllyNotification, localPlayer.Faction.InternalName); + TextNotificationsManager.AddTransientLine(localPlayer, info.AllyTextNotification); + } + + nextActiveTick = Game.RunTime + info.NotifyInterval; + if (info.NotifyInterval < 0 || nextActiveTick < 0) // Notify once if NotifyInterval is negative or correct overflow + nextActiveTick = long.MaxValue; + } + } + } +}