Files
OpenRA/OpenRA.Game/GameRules/WeaponInfo.cs
RoosterDragon ab2ef0ba03 Improve performance of target validation.
The Warhead and WeaponInfo classes now maintain preconstructed sets of valid and invalid targets to speed up validation checks since LINQ Intersect no longer has to be called (which recreates the sets internally each time).

Fixed minor bugs in the AI where it was repeating the validation logic but failing to account for invalid targets.
2015-04-02 21:11:12 +01:00

172 lines
4.9 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2015 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Effects;
using OpenRA.Traits;
namespace OpenRA.GameRules
{
public class ProjectileArgs
{
public WeaponInfo Weapon;
public IEnumerable<int> DamageModifiers;
public IEnumerable<int> InaccuracyModifiers;
public int Facing;
public WPos Source;
public Actor SourceActor;
public WPos PassiveTarget;
public Target GuidedTarget;
}
public interface IProjectileInfo { IEffect Create(ProjectileArgs args); }
public sealed class WeaponInfo
{
[Desc("The maximum range the weapon can fire.")]
public readonly WRange Range = WRange.Zero;
[Desc("The sound played when the weapon is fired.")]
public readonly string[] Report = null;
[Desc("Delay in ticks between reloading ammo magazines.")]
public readonly int ReloadDelay = 1;
[Desc("Number of shots in a single ammo magazine.")]
public readonly int Burst = 1;
public readonly bool Charges = false;
[Desc("What types of targets are affected.")]
public readonly string[] ValidTargets = { "Ground", "Water" };
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
public readonly string[] InvalidTargets = { };
[Desc("Delay in ticks between firing shots from the same ammo magazine.")]
public readonly int BurstDelay = 5;
[Desc("The minimum range the weapon can fire.")]
public readonly WRange MinRange = WRange.Zero;
[FieldLoader.LoadUsing("LoadProjectile")]
public readonly IProjectileInfo Projectile;
[FieldLoader.LoadUsing("LoadWarheads")]
public readonly List<Warhead> Warheads = new List<Warhead>();
readonly HashSet<string> validTargetSet;
readonly HashSet<string> invalidTargetSet;
public WeaponInfo(string name, MiniYaml content)
{
FieldLoader.Load(this, content);
validTargetSet = new HashSet<string>(ValidTargets);
invalidTargetSet = new HashSet<string>(InvalidTargets);
}
static object LoadProjectile(MiniYaml yaml)
{
MiniYaml proj;
if (!yaml.ToDictionary().TryGetValue("Projectile", out proj))
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");
FieldLoader.Load(ret, proj);
return ret;
}
static object LoadWarheads(MiniYaml yaml)
{
var retList = new List<Warhead>();
foreach (var node in yaml.Nodes.Where(n => n.Key.StartsWith("Warhead")))
{
var ret = Game.CreateObject<Warhead>(node.Value.Value + "Warhead");
FieldLoader.Load(ret, node.Value);
retList.Add(ret);
}
return retList;
}
public bool IsValidTarget(IEnumerable<string> targetTypes)
{
return validTargetSet.Overlaps(targetTypes) && !invalidTargetSet.Overlaps(targetTypes);
}
/// <summary>Checks if the weapon is valid against (can target) the target.</summary>
public bool IsValidAgainst(Target target, World world, Actor firedBy)
{
if (target.Type == TargetType.Actor)
return IsValidAgainst(target.Actor, firedBy);
if (target.Type == TargetType.FrozenActor)
return IsValidAgainst(target.FrozenActor, firedBy);
if (target.Type == TargetType.Terrain)
{
var cell = world.Map.CellContaining(target.CenterPosition);
if (!world.Map.Contains(cell))
return false;
var cellInfo = world.Map.GetTerrainInfo(cell);
if (!IsValidTarget(cellInfo.TargetTypes))
return false;
return true;
}
return false;
}
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
public bool IsValidAgainst(Actor victim, Actor firedBy)
{
var targetable = victim.TraitOrDefault<ITargetable>();
if (targetable == null || !IsValidTarget(targetable.TargetTypes))
return false;
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
return false;
return true;
}
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
{
var targetable = victim.Info.Traits.GetOrDefault<ITargetableInfo>();
if (targetable == null || !IsValidTarget(targetable.GetTargetTypes()))
return false;
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
return false;
return true;
}
/// <summary>Applies all the weapon's warheads to the target.</summary>
public void Impact(Target target, Actor firedBy, IEnumerable<int> damageModifiers)
{
foreach (var warhead in Warheads)
{
var wh = warhead; // force the closure to bind to the current warhead
Action a = () => wh.DoImpact(target, firedBy, damageModifiers);
if (wh.Delay > 0)
firedBy.World.AddFrameEndTask(w => w.Add(new DelayedAction(wh.Delay, a)));
else
a();
}
}
}
}