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.Traits;
17 
18 namespace OpenRA.Mods.Common.Warheads
19 {
20 	public class FireClusterWarhead : Warhead, IRulesetLoaded<WeaponInfo>
21 	{
22 		[WeaponReference]
23 		[FieldLoader.Require]
24 		[Desc("Has to be defined in weapons.yaml as well.")]
25 		public readonly string Weapon = null;
26 
27 		[Desc("Number of weapons fired at random 'x' cells. Negative values will result in a number equal to 'x' footprint cells fired.")]
28 		public readonly int RandomClusterCount = -1;
29 
30 		[FieldLoader.Require]
31 		[Desc("Size of the cluster footprint")]
32 		public readonly CVec Dimensions = CVec.Zero;
33 
34 		[FieldLoader.Require]
35 		[Desc("Cluster footprint. Cells marked as X will be attacked.",
36 			"Cells marked as x will be attacked randomly until RandomClusterCount is reached.")]
37 		public readonly string Footprint = string.Empty;
38 
39 		WeaponInfo weapon;
40 
RulesetLoaded(Ruleset rules, WeaponInfo info)41 		public void RulesetLoaded(Ruleset rules, WeaponInfo info)
42 		{
43 			if (!rules.Weapons.TryGetValue(Weapon.ToLowerInvariant(), out weapon))
44 				throw new YamlException("Weapons Ruleset does not contain an entry '{0}'".F(Weapon.ToLowerInvariant()));
45 		}
46 
DoImpact(Target target, WarheadArgs args)47 		public override void DoImpact(Target target, WarheadArgs args)
48 		{
49 			var firedBy = args.SourceActor;
50 			if (!target.IsValidFor(firedBy))
51 				return;
52 
53 			var map = firedBy.World.Map;
54 			var targetCell = map.CellContaining(target.CenterPosition);
55 			var damageModifiers = args.DamageModifiers;
56 
57 			var targetCells = CellsMatching(targetCell, false);
58 			foreach (var c in targetCells)
59 				FireProjectileAtCell(map, firedBy, target, c, damageModifiers);
60 
61 			if (RandomClusterCount != 0)
62 			{
63 				var randomTargetCells = CellsMatching(targetCell, true);
64 				var clusterCount = RandomClusterCount < 0 ? randomTargetCells.Count() : RandomClusterCount;
65 				if (randomTargetCells.Any())
66 					for (var i = 0; i < clusterCount; i++)
67 						FireProjectileAtCell(map, firedBy, target, randomTargetCells.Random(firedBy.World.SharedRandom), damageModifiers);
68 			}
69 		}
70 
FireProjectileAtCell(Map map, Actor firedBy, Target target, CPos targetCell, IEnumerable<int> damageModifiers)71 		void FireProjectileAtCell(Map map, Actor firedBy, Target target, CPos targetCell, IEnumerable<int> damageModifiers)
72 		{
73 			var tc = Target.FromCell(firedBy.World, targetCell);
74 
75 			if (!weapon.IsValidAgainst(tc, firedBy.World, firedBy))
76 				return;
77 
78 			var args = new ProjectileArgs
79 			{
80 				Weapon = weapon,
81 				Facing = (map.CenterOfCell(targetCell) - target.CenterPosition).Yaw.Facing,
82 
83 				DamageModifiers = damageModifiers.ToArray(),
84 				InaccuracyModifiers = new int[0],
85 				RangeModifiers = new int[0],
86 
87 				Source = target.CenterPosition,
88 				CurrentSource = () => target.CenterPosition,
89 				SourceActor = firedBy,
90 				PassiveTarget = map.CenterOfCell(targetCell),
91 				GuidedTarget = tc
92 			};
93 
94 			if (args.Weapon.Projectile != null)
95 			{
96 				var projectile = args.Weapon.Projectile.Create(args);
97 				if (projectile != null)
98 					firedBy.World.AddFrameEndTask(w => w.Add(projectile));
99 
100 				if (args.Weapon.Report != null && args.Weapon.Report.Any())
101 					Game.Sound.Play(SoundType.World, args.Weapon.Report, firedBy.World, target.CenterPosition);
102 			}
103 		}
104 
CellsMatching(CPos location, bool random)105 		IEnumerable<CPos> CellsMatching(CPos location, bool random)
106 		{
107 			var cellType = !random ? 'X' : 'x';
108 			var index = 0;
109 			var footprint = Footprint.Where(c => !char.IsWhiteSpace(c)).ToArray();
110 			var x = location.X - (Dimensions.X - 1) / 2;
111 			var y = location.Y - (Dimensions.Y - 1) / 2;
112 			for (var j = 0; j < Dimensions.Y; j++)
113 				for (var i = 0; i < Dimensions.X; i++)
114 					if (footprint[index++] == cellType)
115 						yield return new CPos(x + i, y + j);
116 		}
117 	}
118 }
119