1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System.Collections.Generic;
13 using System.Linq;
14 using OpenRA.GameRules;
15 using OpenRA.Mods.Common.Traits;
16 using OpenRA.Primitives;
17 using OpenRA.Traits;
18 
19 namespace OpenRA.Mods.Common.Warheads
20 {
21 	public abstract class DamageWarhead : Warhead
22 	{
23 		[Desc("How much (raw) damage to deal.")]
24 		public readonly int Damage = 0;
25 
26 		[Desc("Types of damage that this warhead causes. Leave empty for no damage types.")]
27 		public readonly BitSet<DamageType> DamageTypes = default(BitSet<DamageType>);
28 
29 		[Desc("Damage percentage versus each armortype.")]
30 		public readonly Dictionary<string, int> Versus = new Dictionary<string, int>();
31 
IsValidAgainst(Actor victim, Actor firedBy)32 		public override bool IsValidAgainst(Actor victim, Actor firedBy)
33 		{
34 			// Cannot be damaged without a Health trait
35 			if (!victim.Info.HasTraitInfo<IHealthInfo>())
36 				return false;
37 
38 			return base.IsValidAgainst(victim, firedBy);
39 		}
40 
DamageVersus(Actor victim, HitShapeInfo shapeInfo)41 		public int DamageVersus(Actor victim, HitShapeInfo shapeInfo)
42 		{
43 			// If no Versus values are defined, DamageVersus would return 100 anyway, so we might as well do that early.
44 			if (Versus.Count == 0)
45 				return 100;
46 
47 			var armor = victim.TraitsImplementing<Armor>()
48 				.Where(a => !a.IsTraitDisabled && a.Info.Type != null && Versus.ContainsKey(a.Info.Type) &&
49 					(shapeInfo.ArmorTypes == default(BitSet<ArmorType>) || shapeInfo.ArmorTypes.Contains(a.Info.Type)))
50 				.Select(a => Versus[a.Info.Type]);
51 
52 			return Util.ApplyPercentageModifiers(100, armor);
53 		}
54 
InflictDamage(Actor victim, Actor firedBy, HitShapeInfo hitshapeInfo, IEnumerable<int> damageModifiers)55 		protected virtual void InflictDamage(Actor victim, Actor firedBy, HitShapeInfo hitshapeInfo, IEnumerable<int> damageModifiers)
56 		{
57 			var damage = Util.ApplyPercentageModifiers(Damage, damageModifiers.Append(DamageVersus(victim, hitshapeInfo)));
58 			victim.InflictDamage(firedBy, new Damage(damage, DamageTypes));
59 		}
60 
DoImpact(Target target, WarheadArgs args)61 		public override void DoImpact(Target target, WarheadArgs args)
62 		{
63 			var firedBy = args.SourceActor;
64 
65 			// Used by traits or warheads that damage a single actor, rather than a position
66 			if (target.Type == TargetType.Actor)
67 			{
68 				var victim = target.Actor;
69 
70 				if (!IsValidAgainst(victim, firedBy))
71 					return;
72 
73 				var closestActiveShape = victim.TraitsImplementing<HitShape>().Where(Exts.IsTraitEnabled)
74 					.MinByOrDefault(t => t.DistanceFromEdge(victim, victim.CenterPosition));
75 
76 				// Cannot be damaged without an active HitShape
77 				if (closestActiveShape == null)
78 					return;
79 
80 				InflictDamage(victim, firedBy, closestActiveShape.Info, args.DamageModifiers);
81 			}
82 			else if (target.Type != TargetType.Invalid)
83 				DoImpact(target.CenterPosition, firedBy, args.DamageModifiers);
84 		}
85 
DoImpact(WPos pos, Actor firedBy, IEnumerable<int> damageModifiers)86 		public abstract void DoImpact(WPos pos, Actor firedBy, IEnumerable<int> damageModifiers);
87 	}
88 }
89