#region Copyright & License Information /* * Copyright 2007-2017 The OpenRA Developers (see AUTHORS) * 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 System.Collections.Generic; using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; namespace OpenRA.Mods.Common.Warheads { public class SpreadDamageWarhead : DamageWarhead, IRulesetLoaded { [Desc("Range between falloff steps.")] public readonly WDist Spread = new WDist(43); [Desc("Damage percentage at each range step")] public readonly int[] Falloff = { 100, 37, 14, 5, 0 }; [Desc("Ranges at which each Falloff step is defined. Overrides Spread.")] public WDist[] Range = null; [Desc("Extra search radius beyond maximum spread. If set to a negative value (default), it will automatically scale to the largest health shape.", "Custom overrides should not be necessary under normal circumstances.")] public WDist VictimScanRadius = new WDist(-1); void IRulesetLoaded.RulesetLoaded(Ruleset rules, WeaponInfo info) { if (VictimScanRadius < WDist.Zero) VictimScanRadius = Util.MinimumRequiredVictimScanRadius(rules); if (Range != null) { if (Range.Length != 1 && Range.Length != Falloff.Length) throw new YamlException("Number of range values must be 1 or equal to the number of Falloff values."); for (var i = 0; i < Range.Length - 1; i++) if (Range[i] > Range[i + 1]) throw new YamlException("Range values must be specified in an increasing order."); } else Range = Exts.MakeArray(Falloff.Length, i => i * Spread); } public override void DoImpact(WPos pos, Actor firedBy, IEnumerable damageModifiers) { var world = firedBy.World; var debugVis = world.WorldActor.TraitOrDefault(); if (debugVis != null && debugVis.CombatGeometry) world.WorldActor.Trait().AddImpact(pos, Range, DebugOverlayColor); // This only finds actors where the center is within the search radius, // so we need to search beyond the maximum spread to account for actors with large health radius var hitActors = world.FindActorsInCircle(pos, Range[Range.Length - 1] + VictimScanRadius); foreach (var victim in hitActors) { // Cannot be damaged without a Health trait var healthInfo = victim.Info.TraitInfoOrDefault(); if (healthInfo == null) continue; // Cannot be damaged without an active HitShape var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); if (!activeShapes.Any()) continue; var distance = activeShapes.Min(t => t.Info.Type.DistanceFromEdge(pos, victim)); var localModifiers = damageModifiers.Append(GetDamageFalloff(distance.Length)); DoImpact(victim, firedBy, localModifiers); } } int GetDamageFalloff(int distance) { var inner = Range[0].Length; for (var i = 1; i < Range.Length; i++) { var outer = Range[i].Length; if (outer > distance) return int2.Lerp(Falloff[i - 1], Falloff[i], distance - inner, outer - inner); inner = outer; } return 0; } } }