Custom Warheads refactor

Changes included:

Warhead code split out of weapon code and refactored.
Warhead functionality now split into several classes, each handling one effect/impact.

Additional custom warheads can now be defined and called via yaml.
Custom warheads inherit the abstract class Warhead,
which provides target check functions.

Custom warheads have to define their own impact functions,
and can also define their own replacement for check
functions.
This commit is contained in:
UberWaffe
2014-07-09 17:58:06 +02:00
parent f84b1c145e
commit c972b39687
59 changed files with 2275 additions and 1233 deletions

View File

@@ -8,6 +8,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Effects;
@@ -15,83 +16,6 @@ using OpenRA.Traits;
namespace OpenRA.GameRules
{
public class WarheadInfo
{
[Desc("Distance from the explosion center at which damage is 1/2.")]
public readonly WRange Spread = new WRange(43);
[Desc("Maximum Spread. If a value >= Spread is set, this sets a fixed maximum area of damage.")]
public readonly WRange MaxSpread = new WRange(0);
[FieldLoader.LoadUsing("LoadVersus")]
[Desc("Damage vs each armortype. 0% = can't target.")]
public readonly Dictionary<string, float> Versus;
[Desc("Can this damage resource patches?")]
public readonly bool DestroyResources = false;
[Desc("Will this splatter resources and which?")]
public readonly string AddsResourceType = null;
[Desc("Explosion effect to use.")]
public readonly string Explosion = null;
[Desc("Palette to use for explosion effect.")]
public readonly string ExplosionPalette = "effect";
[Desc("Explosion effect on hitting water (usually a splash).")]
public readonly string WaterExplosion = null;
[Desc("Palette to use for effect on hitting water (usually a splash).")]
public readonly string WaterExplosionPalette = "effect";
[Desc("Type of smudge to apply to terrain.")]
public readonly string[] SmudgeType = { };
[Desc("Size of the explosion. provide 2 values for a ring effect (outer/inner).")]
public readonly int[] Size = { 0, 0 };
[Desc("Infantry death animation to use")]
public readonly string InfDeath = "1";
[Desc("Sound to play on impact.")]
public readonly string ImpactSound = null;
[Desc("Sound to play on impact with water")]
public readonly string WaterImpactSound = null;
[Desc("How much (raw) damage to deal")]
public readonly int Damage = 0;
[Desc("Delay in ticks before dealing the damage, 0 = instant (old model).")]
public readonly int Delay = 0;
[Desc("Which damage model to use.")]
public readonly DamageModel DamageModel = DamageModel.Normal;
[Desc("Whether we should prevent prone response for infantry.")]
public readonly bool PreventProne = false;
[Desc("By what percentage should damage be modified against prone infantry.")]
public readonly int ProneModifier = 50;
public float EffectivenessAgainst(ActorInfo ai)
{
var health = ai.Traits.GetOrDefault<HealthInfo>();
if (health == null)
return 0f;
var armor = ai.Traits.GetOrDefault<ArmorInfo>();
if (armor == null || armor.Type == null)
return 1;
float versus;
return Versus.TryGetValue(armor.Type, out versus) ? versus : 1;
}
public WarheadInfo(MiniYaml yaml)
{
FieldLoader.Load(this, yaml);
}
static object LoadVersus(MiniYaml y)
{
var nd = y.ToDictionary();
return nd.ContainsKey("Versus")
? nd["Versus"].ToDictionary(my => FieldLoader.GetValue<float>("(value)", my.Value))
: new Dictionary<string, float>();
}
}
public enum DamageModel
{
Normal, // classic RA damage model: point actors, distance-based falloff
PerCell, // like RA's "nuke damage"
HealthPercentage // for MAD Tank
}
public class ProjectileArgs
{
public WeaponInfo Weapon;
@@ -107,20 +31,38 @@ namespace OpenRA.GameRules
public 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("Rate of Fire")]
[Desc("Rate of Fire = Delay in ticks between reloading ammo magazines.")]
public readonly int ROF = 1;
[Desc("Number of shots in a single ammo magazine.")]
public readonly int Burst = 1;
public readonly bool Charges = false;
public readonly string Palette = "effect";
[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 IProjectileInfo Projectile;
[FieldLoader.LoadUsing("LoadWarheads")] public List<WarheadInfo> Warheads;
[FieldLoader.LoadUsing("LoadProjectile")]
public readonly IProjectileInfo Projectile;
[FieldLoader.LoadUsing("LoadWarheads")]
public readonly List<Warhead> Warheads = new List<Warhead>();
public WeaponInfo(string name, MiniYaml content)
{
@@ -139,47 +81,24 @@ namespace OpenRA.GameRules
static object LoadWarheads(MiniYaml yaml)
{
var ret = new List<WarheadInfo>();
foreach (var w in yaml.Nodes)
if (w.Key.Split('@')[0] == "Warhead")
ret.Add(new WarheadInfo(w.Value));
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 ret;
return retList;
}
public bool IsValidAgainst(Actor a)
{
var targetable = a.TraitOrDefault<ITargetable>();
if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any()
|| InvalidTargets.Intersect(targetable.TargetTypes).Any())
return false;
if (Warheads.All(w => w.EffectivenessAgainst(a.Info) <= 0))
return false;
return true;
}
public bool IsValidAgainst(FrozenActor a)
{
var targetable = a.Info.Traits.GetOrDefault<ITargetableInfo>();
if (targetable == null || !ValidTargets.Intersect(targetable.GetTargetTypes()).Any()
|| InvalidTargets.Intersect(targetable.GetTargetTypes()).Any())
return false;
if (Warheads.All(w => w.EffectivenessAgainst(a.Info) <= 0))
return false;
return true;
}
public bool IsValidAgainst(Target target, World world)
public bool IsValidAgainst(Target target, World world, Actor firedBy)
{
if (target.Type == TargetType.Actor)
return IsValidAgainst(target.Actor);
return IsValidAgainst(target.Actor, firedBy);
if (target.Type == TargetType.FrozenActor)
return IsValidAgainst(target.FrozenActor);
return IsValidAgainst(target.FrozenActor, firedBy);
if (target.Type == TargetType.Terrain)
{
@@ -197,5 +116,47 @@ namespace OpenRA.GameRules
return false;
}
public bool IsValidAgainst(Actor victim, Actor firedBy)
{
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
return false;
var targetable = victim.TraitOrDefault<ITargetable>();
if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any()
|| InvalidTargets.Intersect(targetable.TargetTypes).Any())
return false;
return true;
}
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
{
// Frozen Actors are treated slightly differently.
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
return false;
var targetable = victim.Info.Traits.GetOrDefault<ITargetableInfo>();
if (targetable == null || !ValidTargets.Intersect(targetable.GetTargetTypes()).Any()
|| InvalidTargets.Intersect(targetable.GetTargetTypes()).Any())
return false;
return true;
}
public void Impact(WPos pos, Actor firedBy, float damageModifier)
{
foreach (var wh in Warheads)
{
Action a;
a = () => wh.DoImpact(Target.FromPos(pos), firedBy, damageModifier);
if (wh.Delay > 0)
firedBy.World.AddFrameEndTask(
w => w.Add(new DelayedAction(wh.Delay, a)));
else
a();
}
}
}
}