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;
13 using System.Collections.Generic;
14 using System.Linq;
15 using OpenRA.Effects;
16 using OpenRA.Primitives;
17 using OpenRA.Traits;
18 
19 namespace OpenRA.GameRules
20 {
21 	public class ProjectileArgs
22 	{
23 		public WeaponInfo Weapon;
24 		public int[] DamageModifiers;
25 		public int[] InaccuracyModifiers;
26 		public int[] RangeModifiers;
27 		public int Facing;
28 		public Func<int> CurrentMuzzleFacing;
29 		public WPos Source;
30 		public Func<WPos> CurrentSource;
31 		public Actor SourceActor;
32 		public WPos PassiveTarget;
33 		public Target GuidedTarget;
34 	}
35 
36 	public class WarheadArgs
37 	{
38 		public WeaponInfo Weapon;
39 		public int[] DamageModifiers = { };
40 		public WPos? Source;
41 		public Actor SourceActor;
42 		public Target WeaponTarget;
43 
WarheadArgs(ProjectileArgs args)44 		public WarheadArgs(ProjectileArgs args)
45 		{
46 			Weapon = args.Weapon;
47 			DamageModifiers = args.DamageModifiers;
48 			Source = args.Source;
49 			SourceActor = args.SourceActor;
50 			WeaponTarget = args.GuidedTarget;
51 		}
52 
53 		// Default empty constructor for callers that want to initialize fields themselves
WarheadArgs()54 		public WarheadArgs() { }
55 	}
56 
57 	public interface IProjectile : IEffect { }
Create(ProjectileArgs args)58 	public interface IProjectileInfo { IProjectile Create(ProjectileArgs args); }
59 
60 	public sealed class WeaponInfo
61 	{
62 		[Desc("The maximum range the weapon can fire.")]
63 		public readonly WDist Range = WDist.Zero;
64 
65 		[Desc("First burst is aimed at this offset relative to target position.")]
66 		public readonly WVec FirstBurstTargetOffset = WVec.Zero;
67 
68 		[Desc("Each burst after the first lands by this offset away from the previous burst.")]
69 		public readonly WVec FollowingBurstTargetOffset = WVec.Zero;
70 
71 		[Desc("The sound played each time the weapon is fired.")]
72 		public readonly string[] Report = null;
73 
74 		[Desc("Sound played only on first burst in a salvo.")]
75 		public readonly string[] StartBurstReport = null;
76 
77 		[Desc("The sound played when the weapon is reloaded.")]
78 		public readonly string[] AfterFireSound = null;
79 
80 		[Desc("Delay in ticks to play reloading sound.")]
81 		public readonly int AfterFireSoundDelay = 0;
82 
83 		[Desc("Delay in ticks between reloading ammo magazines.")]
84 		public readonly int ReloadDelay = 1;
85 
86 		[Desc("Number of shots in a single ammo magazine.")]
87 		public readonly int Burst = 1;
88 
89 		[Desc("What types of targets are affected.")]
90 		public readonly BitSet<TargetableType> ValidTargets = new BitSet<TargetableType>("Ground", "Water");
91 
92 		[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
93 		public readonly BitSet<TargetableType> InvalidTargets;
94 
95 		[Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.",
96 			"If multiple entries, their number needs to match Burst - 1.")]
97 		public readonly int[] BurstDelays = { 5 };
98 
99 		[Desc("The minimum range the weapon can fire.")]
100 		public readonly WDist MinRange = WDist.Zero;
101 
102 		[Desc("Does this weapon aim at the target's center regardless of other targetable offsets?")]
103 		public readonly bool TargetActorCenter = false;
104 
105 		[FieldLoader.LoadUsing("LoadProjectile")]
106 		public readonly IProjectileInfo Projectile;
107 
108 		[FieldLoader.LoadUsing("LoadWarheads")]
109 		public readonly List<IWarhead> Warheads = new List<IWarhead>();
110 
WeaponInfo(string name, MiniYaml content)111 		public WeaponInfo(string name, MiniYaml content)
112 		{
113 			// Resolve any weapon-level yaml inheritance or removals
114 			// HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing
115 			content.Nodes = MiniYaml.Merge(new[] { content.Nodes });
116 			FieldLoader.Load(this, content);
117 		}
118 
LoadProjectile(MiniYaml yaml)119 		static object LoadProjectile(MiniYaml yaml)
120 		{
121 			MiniYaml proj;
122 			if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
123 				return null;
124 			var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
125 			FieldLoader.Load(ret, proj);
126 			return ret;
127 		}
128 
LoadWarheads(MiniYaml yaml)129 		static object LoadWarheads(MiniYaml yaml)
130 		{
131 			var retList = new List<IWarhead>();
132 			foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
133 			{
134 				var ret = Game.CreateObject<IWarhead>(node.Value.Value + "Warhead");
135 				FieldLoader.Load(ret, node.Value);
136 				retList.Add(ret);
137 			}
138 
139 			return retList;
140 		}
141 
IsValidTarget(BitSet<TargetableType> targetTypes)142 		public bool IsValidTarget(BitSet<TargetableType> targetTypes)
143 		{
144 			return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes);
145 		}
146 
147 		/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
IsValidAgainst(Target target, World world, Actor firedBy)148 		public bool IsValidAgainst(Target target, World world, Actor firedBy)
149 		{
150 			if (target.Type == TargetType.Actor)
151 				return IsValidAgainst(target.Actor, firedBy);
152 
153 			if (target.Type == TargetType.FrozenActor)
154 				return IsValidAgainst(target.FrozenActor, firedBy);
155 
156 			if (target.Type == TargetType.Terrain)
157 			{
158 				var cell = world.Map.CellContaining(target.CenterPosition);
159 				if (!world.Map.Contains(cell))
160 					return false;
161 
162 				var cellInfo = world.Map.GetTerrainInfo(cell);
163 				if (!IsValidTarget(cellInfo.TargetTypes))
164 					return false;
165 
166 				return true;
167 			}
168 
169 			return false;
170 		}
171 
172 		/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
IsValidAgainst(Actor victim, Actor firedBy)173 		public bool IsValidAgainst(Actor victim, Actor firedBy)
174 		{
175 			var targetTypes = victim.GetEnabledTargetTypes();
176 
177 			if (!IsValidTarget(targetTypes))
178 				return false;
179 
180 			// PERF: Avoid LINQ.
181 			foreach (var warhead in Warheads)
182 				if (warhead.IsValidAgainst(victim, firedBy))
183 					return true;
184 
185 			return false;
186 		}
187 
188 		/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
IsValidAgainst(FrozenActor victim, Actor firedBy)189 		public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
190 		{
191 			if (!IsValidTarget(victim.TargetTypes))
192 				return false;
193 
194 			if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
195 				return false;
196 
197 			return true;
198 		}
199 
200 		/// <summary>Applies all the weapon's warheads to the target.</summary>
Impact(Target target, WarheadArgs args)201 		public void Impact(Target target, WarheadArgs args)
202 		{
203 			var world = args.SourceActor.World;
204 			foreach (var warhead in Warheads)
205 			{
206 				if (warhead.Delay > 0)
207 					world.AddFrameEndTask(w => w.Add(new DelayedImpact(warhead.Delay, warhead, target, args)));
208 				else
209 					warhead.DoImpact(target, args);
210 			}
211 		}
212 
213 		/// <summary>Applies all the weapon's warheads to the target. Only use for projectile-less, special-case impacts.</summary>
Impact(Target target, Actor firedBy)214 		public void Impact(Target target, Actor firedBy)
215 		{
216 			// The impact will happen immediately at target.CenterPosition.
217 			var args = new WarheadArgs
218 			{
219 				Weapon = this,
220 				SourceActor = firedBy,
221 				WeaponTarget = target
222 			};
223 
224 			if (firedBy.OccupiesSpace != null)
225 				args.Source = firedBy.CenterPosition;
226 
227 			Impact(target, args);
228 		}
229 	}
230 }
231